@xo-cash/utils 0.0.6 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { bigIntToVmNumber, binToHex, binToUtf8, createCompilerBch, hexToBin, sha256, utf8ToBin, vmNumberToBigInt } from "@bitauth/libauth";
1
+ import { IdentifierResolutionType, OpcodesBchSpec, bigIntToVmNumber, binToHex, binToUtf8, createCompilerBch, describeExpectedInput, generateBytecodeMap, hexToBin, parseScript, sha256, utf8ToBin, vmNumberToBigInt } from "@bitauth/libauth";
2
2
  import { BchVmVersions, XOTemplateLockingTypes, XOTemplateNftCapabilities, XOTemplatePrimitiveTypes } from "@xo-cash/types";
3
3
  import { z } from "zod";
4
4
  import { FungibleTokenAmount, NFTCommitment, PublicKey, Satoshis, SchnorrSignature, TemplateIdentifier, Timestamp, TokenCategory, TransactionHash } from "@xo-cash/primitives";
@@ -1636,10 +1636,15 @@ const parseTemplate = (inputTemplate) => {
1636
1636
  * Error thrown when a required variable is missing.
1637
1637
  */
1638
1638
  var CashAssemblyRequiredVariableMissingError = class extends Error {
1639
- constructor(variableNames) {
1639
+ /**
1640
+ * Variable names that were required but absent from the variables map.
1641
+ */
1642
+ variableNames;
1643
+ constructor(variableNames = []) {
1640
1644
  const defaultMessage = "Missing required variable";
1641
- if (variableNames !== void 0 && variableNames.length > 0) super(`${defaultMessage}: variableNames [${variableNames.join(", ")}]`);
1645
+ if (variableNames.length > 0) super(`${defaultMessage}: variableNames [${variableNames.join(", ")}]`);
1642
1646
  else super(defaultMessage);
1647
+ this.variableNames = variableNames;
1643
1648
  }
1644
1649
  };
1645
1650
  /**
@@ -1652,6 +1657,87 @@ var CashAssemblyCompilationFailedError = class extends Error {
1652
1657
  }
1653
1658
  };
1654
1659
  /**
1660
+ * Error thrown when a quoted string inside `$()` never closes.
1661
+ */
1662
+ var CashAssemblyQuotedLiteralUnclosedError = class extends Error {
1663
+ /**
1664
+ * Index of the opening quote that never found a closer.
1665
+ */
1666
+ openingQuoteIndex;
1667
+ /**
1668
+ * Quote character that opened the literal.
1669
+ */
1670
+ quoteCharacter;
1671
+ /**
1672
+ * Text from the opening quote through the end of the scanned string.
1673
+ */
1674
+ unclosedText;
1675
+ constructor(openingQuoteIndex, quoteCharacter, cashAssemblyText) {
1676
+ const unclosedText = cashAssemblyText.slice(openingQuoteIndex);
1677
+ const defaultMessage = "Quoted literal in a CashAssembly evaluation is unclosed";
1678
+ const details = [
1679
+ `quoteCharacter ${JSON.stringify(quoteCharacter)}`,
1680
+ `openingQuoteIndex ${String(openingQuoteIndex)}`,
1681
+ `unclosedText ${JSON.stringify(unclosedText)}`
1682
+ ].join(", ");
1683
+ super(`${defaultMessage}: ${details}`);
1684
+ this.openingQuoteIndex = openingQuoteIndex;
1685
+ this.quoteCharacter = quoteCharacter;
1686
+ this.unclosedText = unclosedText;
1687
+ }
1688
+ };
1689
+ /**
1690
+ * Error thrown when a block comment inside `$()` never closes.
1691
+ */
1692
+ var CashAssemblyBlockCommentUnclosedError = class extends Error {
1693
+ /**
1694
+ * Index of the first slash of the block comment opener.
1695
+ */
1696
+ commentStartIndex;
1697
+ /**
1698
+ * Text from the block comment opener through the end of the scanned string.
1699
+ */
1700
+ unclosedText;
1701
+ constructor(commentStartIndex, cashAssemblyText) {
1702
+ const unclosedText = cashAssemblyText.slice(commentStartIndex);
1703
+ const defaultMessage = "Block comment in a CashAssembly evaluation is unclosed";
1704
+ const details = [`commentStartIndex ${String(commentStartIndex)}`, `unclosedText ${JSON.stringify(unclosedText)}`].join(", ");
1705
+ super(`${defaultMessage}: ${details}`);
1706
+ this.commentStartIndex = commentStartIndex;
1707
+ this.unclosedText = unclosedText;
1708
+ }
1709
+ };
1710
+ /**
1711
+ * Error thrown when a `$()` evaluation never finds its matching closer.
1712
+ */
1713
+ var CashAssemblyEvaluationUnclosedError = class extends Error {
1714
+ /**
1715
+ * Index of the `$` that opened the evaluation.
1716
+ */
1717
+ evaluationStartIndex;
1718
+ /**
1719
+ * How many `$()` remain open at the end of the string, including nested evaluations.
1720
+ */
1721
+ remainingOpenEvaluations;
1722
+ /**
1723
+ * Text from the opening `$` through the end of the scanned string.
1724
+ */
1725
+ unclosedText;
1726
+ constructor(evaluationStartIndex, remainingOpenEvaluations, cashAssemblyText) {
1727
+ const unclosedText = cashAssemblyText.slice(evaluationStartIndex);
1728
+ const defaultMessage = "CashAssembly evaluation is unclosed";
1729
+ const details = [
1730
+ `evaluationStartIndex ${String(evaluationStartIndex)}`,
1731
+ `remainingOpenEvaluations ${String(remainingOpenEvaluations)}`,
1732
+ `unclosedText ${JSON.stringify(unclosedText)}`
1733
+ ].join(", ");
1734
+ super(`${defaultMessage}: ${details}`);
1735
+ this.evaluationStartIndex = evaluationStartIndex;
1736
+ this.remainingOpenEvaluations = remainingOpenEvaluations;
1737
+ this.unclosedText = unclosedText;
1738
+ }
1739
+ };
1740
+ /**
1655
1741
  * Error thrown when a variable's runtime type does not match the type required for compilation.
1656
1742
  */
1657
1743
  var CashAssemblyVariableTypeMismatchError = class extends Error {
@@ -1692,6 +1778,24 @@ var CashAssemblyPrimitiveVariableMissingError = class extends Error {
1692
1778
  }
1693
1779
  };
1694
1780
  /**
1781
+ * Error thrown when one identifier would resolve as more than one {@link IdentifierResolutionType}.
1782
+ */
1783
+ var CashAssemblyIdentifierCollisionError = class extends Error {
1784
+ /**
1785
+ * Identifier that more than one resolution type would match.
1786
+ */
1787
+ identifier;
1788
+ /**
1789
+ * The list of resolution types that the identifier matches.
1790
+ */
1791
+ resolutionTypes;
1792
+ constructor(identifier, resolutionTypes) {
1793
+ super(`CashAssembly identifier exists for more than one resolution type: identifier "${identifier}", resolutionTypes [${resolutionTypes.join(", ")}]`);
1794
+ this.identifier = identifier;
1795
+ this.resolutionTypes = resolutionTypes;
1796
+ }
1797
+ };
1798
+ /**
1695
1799
  * Error thrown when compiled evaluation bytes cannot be decoded as a VM number.
1696
1800
  */
1697
1801
  var CashAssemblyVmNumberDecodeError = class extends Error {
@@ -1701,64 +1805,135 @@ var CashAssemblyVmNumberDecodeError = class extends Error {
1701
1805
  };
1702
1806
 
1703
1807
  //#endregion
1704
- //#region source/cash-assembly/defaults.ts
1808
+ //#region source/cash-assembly/collect-evaluations.ts
1705
1809
  /**
1706
- * Detects whether a string is a pure CashAssembly expression.
1707
- *
1708
- * CashAssembly expressions look like `$(<variable>)` or `$(<a> <b>)`. This pattern checks
1709
- * that the entire string is one such expression and nothing else. It will not match if
1710
- * there is other text surrounding the expression.
1810
+ * Enqueues a template script id so its source can be visited when it exists in the scripts map.
1711
1811
  *
1712
- * For example:
1713
- * `$(<fee>)` matches (a full expression)
1714
- * `OP_DUP $(<fee>)` does not match (extra text before it)
1715
- * `$()` does not match (empty expression)
1812
+ * @param {string} scriptIdentifier - Script id from an evaluation or nested script source.
1813
+ * @param {ReadonlySet<string>} knownScriptIdentifiers - Script ids present in the template scripts map.
1814
+ * @param {ReadonlySet<string>} visitedScriptIdentifiers - Script ids whose sources were already visited.
1815
+ * @param {string[]} scriptIdentifiersToVisit - Pending script ids queued to visit.
1716
1816
  */
1717
- const CASHASSEMBLY_EXPRESSION_PATTERN = /^\$\([^)]+\)$/;
1817
+ const enqueueReachableTemplateScriptIdentifier = (scriptIdentifier, knownScriptIdentifiers, visitedScriptIdentifiers, scriptIdentifiersToVisit) => {
1818
+ if (knownScriptIdentifiers.has(scriptIdentifier) === false) return;
1819
+ if (visitedScriptIdentifiers.has(scriptIdentifier) === true) return;
1820
+ if (scriptIdentifiersToVisit.includes(scriptIdentifier) === true) return;
1821
+ scriptIdentifiersToVisit.push(scriptIdentifier);
1822
+ };
1718
1823
  /**
1719
- * Finds all CashAssembly evaluations embedded in a larger string.
1720
- *
1721
- * An evaluation looks like `$(...)`, for example `$(<fee>)` or `$(<a> <b>)`. This pattern
1722
- * locates every occurrence in the input and returns them all (global flag `g`).
1723
- * Empty evaluations `$()` are intentionally excluded because they reference no variables.
1824
+ * Parses CashAssembly source with parseScript and throws when the parse fails.
1724
1825
  *
1725
- * For example, scanning `"OP_DUP <$(<pubkeyHash>)> OP_HASH160 $(<fee>)"` would return
1726
- * `['$(<pubkeyHash>)', '$(<fee>)']`.
1826
+ * @param {string} cashAssemblyText - Evaluation or script source to parse.
1827
+ * @returns {CashAssemblyScriptSegment} - Parsed Script root.
1828
+ * @throws {@link CashAssemblyCompilationFailedError} - When parseScript rejects the source.
1727
1829
  */
1728
- const CASHASSEMBLY_EVALUATION_PATTERN = /\$\([^)]+\)/g;
1830
+ const parseCashAssemblySource = (cashAssemblyText) => {
1831
+ const parseResult = parseScript(cashAssemblyText);
1832
+ if (parseResult.status === false) throw new CashAssemblyCompilationFailedError(`${describeExpectedInput(parseResult.expected)} Line ${String(parseResult.index.line)}, column ${String(parseResult.index.column)}.`);
1833
+ return parseResult.value;
1834
+ };
1729
1835
  /**
1730
- * Extracts variable names from angle-bracket references inside a CashAssembly evaluation.
1731
- *
1732
- * Inside an evaluation like `$(<pubkeyHash> <fee>)`, variables are referenced as `<name>`.
1733
- * This pattern captures the name between the brackets. The global flag `g` allows iterating
1734
- * over every variable reference in a single evaluation string.
1836
+ * Identifier visitor for the parseScript tree.
1837
+ * Records WalletData or queues a nested script from one Identifier node.
1735
1838
  *
1736
- * For example, running this against `$(<pubkeyHash> <fee>)` would return the variable names
1737
- * `["pubkeyHash", "fee"]`.
1839
+ * @param {CollectFromParsedIdentifierParameters} parameters - Identifier text and the parse tree context.
1738
1840
  */
1739
- const CASHASSEMBLY_VARIABLE_PATTERN = /<([^>]+)>/g;
1841
+ const collectFromParsedIdentifier = (parameters) => {
1842
+ const { identifier, isDirectPushContent, knownScriptIdentifiers, visitedScriptIdentifiers, scriptIdentifiersToVisit, variableNames } = parameters;
1843
+ if (knownScriptIdentifiers.has(identifier) === true) {
1844
+ enqueueReachableTemplateScriptIdentifier(identifier, knownScriptIdentifiers, visitedScriptIdentifiers, scriptIdentifiersToVisit);
1845
+ return;
1846
+ }
1847
+ if (isDirectPushContent === true) variableNames.add(identifier);
1848
+ };
1740
1849
  /**
1741
- * Identifies CashAssembly literal tokens that appear inside angle-bracket push statements.
1742
- *
1743
- * Inside an evaluation, not everything between `<` and `>` is a variable name. Literals are
1744
- * also valid push contents: numeric literals (e.g. `<0>`, `<32>`), hex literals (`<0x02>`),
1745
- * binary literals (`<0b1010>`), and string literals (`<"minting">`, `<'hello'>`). This pattern
1746
- * matches any captured token that starts with a digit or a quote character.
1747
- */
1748
- const CASHASSEMBLY_LITERAL_TOKEN_PATTERN = /^[0-9"']/;
1850
+ * Visitor for one Script node in the parseScript tree.
1851
+ * Recurses into Push and Evaluation children and dispatches Identifier nodes.
1852
+ *
1853
+ * @param {CollectFromParsedScriptParameters} parameters - Parsed Script, Push depth flag, and parse tree context.
1854
+ */
1855
+ const collectFromParsedScript = (parameters) => {
1856
+ const { scriptSegment, isDirectPushContent, knownScriptIdentifiers, visitedScriptIdentifiers, scriptIdentifiersToVisit, variableNames } = parameters;
1857
+ for (const child of scriptSegment.value) {
1858
+ if (child.name === "Push") {
1859
+ collectFromParsedScript({
1860
+ scriptSegment: child.value,
1861
+ isDirectPushContent: true,
1862
+ knownScriptIdentifiers,
1863
+ visitedScriptIdentifiers,
1864
+ scriptIdentifiersToVisit,
1865
+ variableNames
1866
+ });
1867
+ continue;
1868
+ }
1869
+ if (child.name === "Evaluation") {
1870
+ collectFromParsedScript({
1871
+ scriptSegment: child.value,
1872
+ isDirectPushContent: false,
1873
+ knownScriptIdentifiers,
1874
+ visitedScriptIdentifiers,
1875
+ scriptIdentifiersToVisit,
1876
+ variableNames
1877
+ });
1878
+ continue;
1879
+ }
1880
+ if (child.name === "Identifier") collectFromParsedIdentifier({
1881
+ identifier: child.value,
1882
+ isDirectPushContent,
1883
+ knownScriptIdentifiers,
1884
+ visitedScriptIdentifiers,
1885
+ scriptIdentifiersToVisit,
1886
+ variableNames
1887
+ });
1888
+ }
1889
+ };
1749
1890
  /**
1750
- * Matches a single dot variable method reference inside an angle-bracket identifier.
1751
- *
1752
- * Used to detect primitive method references such as `expiry.toIso8601`.
1753
- *
1754
- * For example:
1755
- * `expiry.toIso8601` matches (base `expiry`, method `toIso8601`)
1756
- * `requestedSatoshis` does not match (no method)
1757
- * `key.schnorr_signature.all_outputs` does not match (more than one dot)
1758
- * `key.public_key` matches the pattern shape but it is only resolved
1759
- * when `type` maps to a primitive
1760
- */
1761
- const CASHASSEMBLY_VARIABLE_METHOD_REFERENCE_PATTERN = /^([^.]+)\.([^.]+)$/;
1891
+ * Collects WalletData names from the parseScript tree using the visitor pattern.
1892
+ *
1893
+ * parseScript returns a tree of Script, Push, Evaluation, and Identifier nodes. Nested Push and
1894
+ * Evaluation nodes are entered immediately. A template script id is queued whether it appears inside a Push or next to
1895
+ * opcodes. Queued script sources are visited after the current tree finishes, in the order they were
1896
+ * first seen.
1897
+ * A visited id is skipped on later encounters. Opcodes and literals are ignored. Unrelated scripts
1898
+ * in the map are not visited. An Identifier inside a nested Push is collected from that inner Push,
1899
+ * for example `<$(<ownerKey.public_key> OP_HASH160)>` collects `ownerKey.public_key`.
1900
+ *
1901
+ * @param {string} evaluation - CashAssembly evaluation or script id text to scan.
1902
+ * @param {CashAssemblyTemplateScripts} [templateScripts] - Optional template scripts map used to visit nested script ids.
1903
+ * @returns {string[]} - WalletData names from reachable Pushes. Script ids are omitted when
1904
+ * `templateScripts` is provided.
1905
+ * @throws {@link CashAssemblyCompilationFailedError} - When parseScript rejects the starting text or a visited script source.
1906
+ */
1907
+ const collectVariablesUsingParseScript = (evaluation, templateScripts = {}) => {
1908
+ const knownScriptIdentifiers = new Set(Object.keys(templateScripts));
1909
+ const visitedScriptIdentifiers = /* @__PURE__ */ new Set();
1910
+ const scriptIdentifiersToVisit = [];
1911
+ const variableNames = /* @__PURE__ */ new Set();
1912
+ const parseTreeContext = {
1913
+ knownScriptIdentifiers,
1914
+ visitedScriptIdentifiers,
1915
+ scriptIdentifiersToVisit,
1916
+ variableNames
1917
+ };
1918
+ const startingScriptSegment = parseCashAssemblySource(evaluation);
1919
+ collectFromParsedScript({
1920
+ ...parseTreeContext,
1921
+ scriptSegment: startingScriptSegment,
1922
+ isDirectPushContent: false
1923
+ });
1924
+ for (const scriptIdentifier of scriptIdentifiersToVisit) {
1925
+ if (visitedScriptIdentifiers.has(scriptIdentifier) === true) continue;
1926
+ visitedScriptIdentifiers.add(scriptIdentifier);
1927
+ const scriptDefinition = templateScripts[scriptIdentifier];
1928
+ const nestedScriptSegment = parseCashAssemblySource(scriptDefinition);
1929
+ collectFromParsedScript({
1930
+ ...parseTreeContext,
1931
+ scriptSegment: nestedScriptSegment,
1932
+ isDirectPushContent: false
1933
+ });
1934
+ }
1935
+ return [...variableNames];
1936
+ };
1762
1937
 
1763
1938
  //#endregion
1764
1939
  //#region source/cash-assembly/bytes.ts
@@ -1767,9 +1942,9 @@ const CASHASSEMBLY_VARIABLE_METHOD_REFERENCE_PATTERN = /^([^.]+)\.([^.]+)$/;
1767
1942
  *
1768
1943
  * @param {unknown} value - Value to encode, should be one of: Uint8Array, bigint, boolean, string, or a safe integer number.
1769
1944
  * @param {string} valueIdentifier - Identifier used in error messages.
1770
- * @returns {Uint8Array} Bytes representation of the value.
1771
- * @throws {@link CashAssemblyNumberNotSafeIntegerError} When a number is not a safe integer.
1772
- * @throws {@link CashAssemblyUnsupportedValueTypeError} When the value type cannot be resolved.
1945
+ * @returns {Uint8Array} - Bytes representation of the value.
1946
+ * @throws {@link CashAssemblyNumberNotSafeIntegerError} - When a number is not a safe integer.
1947
+ * @throws {@link CashAssemblyUnsupportedValueTypeError} - When the value type cannot be resolved.
1773
1948
  */
1774
1949
  const convertValueToBytes = (value, valueIdentifier) => {
1775
1950
  if (value instanceof Uint8Array) return value;
@@ -1783,6 +1958,46 @@ const convertValueToBytes = (value, valueIdentifier) => {
1783
1958
  throw new CashAssemblyUnsupportedValueTypeError(valueIdentifier, typeof value);
1784
1959
  };
1785
1960
 
1961
+ //#endregion
1962
+ //#region source/cash-assembly/defaults.ts
1963
+ /**
1964
+ * Matches a single dot variable method reference inside an angle bracket identifier.
1965
+ *
1966
+ * Used to detect primitive method references such as `expiry.toIso8601`.
1967
+ *
1968
+ * For example `expiry.toIso8601` matches with base expiry and method toIso8601.
1969
+ * `requestedSatoshis` does not match because it has no method.
1970
+ * `key.schnorr_signature.all_outputs` does not match because it has more than one dot.
1971
+ */
1972
+ const CASHASSEMBLY_VARIABLE_METHOD_REFERENCE_PATTERN = /^([^.]+)\.([^.]+)$/;
1973
+ /**
1974
+ * Character count of the `$(` evaluation opener, which is two characters long.
1975
+ *
1976
+ * Adding this length moves the read position to the first character after that sequence so scanning
1977
+ * starts on the evaluation contents rather than on the opening parenthesis.
1978
+ */
1979
+ const CASHASSEMBLY_EVALUATION_START_LENGTH = 2;
1980
+ /**
1981
+ * Character count of the `//` and `/*` comment openers, which are both two characters long.
1982
+ *
1983
+ * Adding this length moves the read position to the first character after that sequence so the '*'
1984
+ * of a block comment opener cannot be reused as the '*' of its closer.
1985
+ */
1986
+ const CASHASSEMBLY_COMMENT_START_LENGTH = 2;
1987
+ /**
1988
+ * Character count of the block comment closer, which is two characters long.
1989
+ *
1990
+ * Adding this length moves the read position to the first character after that sequence so the
1991
+ * closing slash cannot pair with the next character and open another comment.
1992
+ */
1993
+ const CASHASSEMBLY_BLOCK_COMMENT_END_LENGTH = 2;
1994
+ /**
1995
+ * `$()` with nothing between the parentheses.
1996
+ *
1997
+ * Callers use this to detect an evaluation that has no contents to compile.
1998
+ */
1999
+ const EMPTY_CASHASSEMBLY_EVALUATION = "$()";
2000
+
1786
2001
  //#endregion
1787
2002
  //#region source/cash-assembly/primitive-evaluations.ts
1788
2003
  /**
@@ -1814,7 +2029,7 @@ const isResolvablePrimitiveType = (type) => {
1814
2029
  *
1815
2030
  * @param {ResolvablePrimitiveType} type - Method resolvable template type.
1816
2031
  * @param {string} methodName - Method name from the evaluation text, for example `toSatoshis`.
1817
- * @returns {boolean} True when that class exposes the named method.
2032
+ * @returns {boolean} - True when that class exposes the named method.
1818
2033
  */
1819
2034
  const canResolvePrimitiveMethod = (type, methodName) => {
1820
2035
  const PrimitiveClass = RESOLVABLE_PRIMITIVE_CLASS_BY_TYPE[type];
@@ -1855,61 +2070,232 @@ const callPrimitiveMethod = (parameters) => {
1855
2070
  * @throws {@link CashAssemblyNumberNotSafeIntegerError} When a method return value is a number that is not a safe integer.
1856
2071
  */
1857
2072
  const resolvePrimitiveMethodBytes = (parameters) => {
1858
- const { identifiers, templateVariables, variables } = parameters;
2073
+ const { variableNames, templateVariables, variables } = parameters;
1859
2074
  if (templateVariables === void 0) return {};
1860
2075
  const resolvedBytes = {};
1861
- for (const identifier of identifiers) {
1862
- if (Object.hasOwn(resolvedBytes, identifier) === true) continue;
1863
- const methodReferenceMatch = identifier.match(CASHASSEMBLY_VARIABLE_METHOD_REFERENCE_PATTERN);
2076
+ for (const variableName of variableNames) {
2077
+ if (Object.hasOwn(resolvedBytes, variableName) === true) continue;
2078
+ const methodReferenceMatch = variableName.match(CASHASSEMBLY_VARIABLE_METHOD_REFERENCE_PATTERN);
1864
2079
  if (methodReferenceMatch === null) continue;
1865
2080
  const [, baseName, methodName] = methodReferenceMatch;
1866
2081
  if (Object.hasOwn(templateVariables, baseName) === false) continue;
1867
2082
  const type = templateVariables[baseName].type;
1868
2083
  if (isResolvablePrimitiveType(type) === false) continue;
1869
- if (canResolvePrimitiveMethod(type, methodName) === false) throw new CashAssemblyPrimitiveMethodMissingError(identifier, methodName, type);
1870
- if (Object.hasOwn(variables, baseName) === false) throw new CashAssemblyPrimitiveVariableMissingError(identifier, baseName);
1871
- resolvedBytes[identifier] = convertValueToBytes(callPrimitiveMethod({
1872
- identifier,
2084
+ if (canResolvePrimitiveMethod(type, methodName) === false) throw new CashAssemblyPrimitiveMethodMissingError(variableName, methodName, type);
2085
+ if (Object.hasOwn(variables, baseName) === false) throw new CashAssemblyPrimitiveVariableMissingError(variableName, baseName);
2086
+ resolvedBytes[variableName] = convertValueToBytes(callPrimitiveMethod({
2087
+ identifier: variableName,
1873
2088
  methodName,
1874
2089
  value: variables[baseName],
1875
2090
  type
1876
- }), identifier);
2091
+ }), variableName);
1877
2092
  }
1878
2093
  return resolvedBytes;
1879
2094
  };
1880
2095
 
1881
2096
  //#endregion
1882
- //#region source/cash-assembly/evaluations.ts
2097
+ //#region source/cash-assembly/identifier-collisions.ts
2098
+ /**
2099
+ * Set of opcode names that the compiler recognizes.
2100
+ */
2101
+ const COMPILER_OPCODE_NAMES = new Set(Object.keys(generateBytecodeMap(OpcodesBchSpec)));
1883
2102
  /**
1884
- * Checks if the expression is a CashAssembly expression.
2103
+ * Libauth resolves opcodes, then variables, then scripts, and the first match silently hides the
2104
+ * rest, leading to unexpected compilation results and no errors. This function throws when one identifier matches more than one resolution type.
1885
2105
  *
1886
- * @param {unknown} expression - The expression to check.
1887
- * @returns {boolean} True if the expression is a CashAssembly expression, false otherwise.
2106
+ * @param {AssertNoIdentifierCollisionsParameters} parameters - Provided variables and optional template scripts.
2107
+ * @throws {@link CashAssemblyIdentifierCollisionError} - When one identifier matches more than one resolution type.
1888
2108
  */
1889
- const isCashAssemblyExpression = (expression) => {
1890
- return typeof expression === "string" && CASHASSEMBLY_EXPRESSION_PATTERN.test(expression);
2109
+ const assertNoIdentifierCollisions = (parameters) => {
2110
+ const { variables, templateScripts } = parameters;
2111
+ const scriptIdentifiers = new Set(Object.keys(templateScripts ?? {}));
2112
+ const variableNames = new Set(Object.keys(variables));
2113
+ const declaredIdentifiers = new Set([...variableNames, ...scriptIdentifiers]);
2114
+ for (const identifier of declaredIdentifiers) {
2115
+ const resolutionTypes = [];
2116
+ if (COMPILER_OPCODE_NAMES.has(identifier) === true) resolutionTypes.push(IdentifierResolutionType.opcode);
2117
+ if (variableNames.has(identifier) === true) resolutionTypes.push(IdentifierResolutionType.variable);
2118
+ if (scriptIdentifiers.has(identifier) === true) resolutionTypes.push(IdentifierResolutionType.script);
2119
+ if (resolutionTypes.length > 1) throw new CashAssemblyIdentifierCollisionError(identifier, resolutionTypes);
2120
+ }
2121
+ };
2122
+
2123
+ //#endregion
2124
+ //#region source/cash-assembly/scan-evaluations.ts
2125
+ /**
2126
+ * Skips a quoted CashAssembly string so a `)` inside it cannot end `$()`.
2127
+ *
2128
+ * CashAssembly UTF8 literals use `"..."` or `'...'`. Everything between the quotes is payload,
2129
+ * including parentheses.
2130
+ * For example `$(<"hello)world">)` must close after the literal, not at the `)` inside the quotes.
2131
+ *
2132
+ * @param {string} cashAssemblyText - Text that contains the quoted literal.
2133
+ * @param {number} openingQuoteIndex - Index of the opening `"` or `'`.
2134
+ * @returns {number} - Index of the character after the closing quote.
2135
+ * @throws {@link CashAssemblyQuotedLiteralUnclosedError} - When the quoted literal never closes.
2136
+ */
2137
+ const skipQuotedCashAssemblyLiteral = (cashAssemblyText, openingQuoteIndex) => {
2138
+ const quoteCharacter = cashAssemblyText[openingQuoteIndex];
2139
+ let currentIndex = openingQuoteIndex + 1;
2140
+ while (currentIndex < cashAssemblyText.length) {
2141
+ if (cashAssemblyText[currentIndex] === quoteCharacter) return currentIndex + 1;
2142
+ currentIndex += 1;
2143
+ }
2144
+ throw new CashAssemblyQuotedLiteralUnclosedError(openingQuoteIndex, quoteCharacter, cashAssemblyText);
2145
+ };
2146
+ /**
2147
+ * This function skips a `//` comment so a `)` written in the comment cannot end `$()`.
2148
+ * A line comment runs from `//` to the newline, or to the end of the string when there is no newline.
2149
+ * A `)` in that span is comment text, not the closer of `$()`.
2150
+ *
2151
+ * @param {string} cashAssemblyText - Text that contains the line comment.
2152
+ * @param {number} commentStartIndex - Index of the first `/` of `//`.
2153
+ * @returns {number} - Index after the newline that ends the comment, or the end of the string if there is no newline.
2154
+ */
2155
+ const skipSingleLineCashAssemblyComment = (cashAssemblyText, commentStartIndex) => {
2156
+ let currentIndex = commentStartIndex + CASHASSEMBLY_COMMENT_START_LENGTH;
2157
+ while (currentIndex < cashAssemblyText.length) {
2158
+ if (cashAssemblyText[currentIndex] === "\n") return currentIndex + 1;
2159
+ currentIndex += 1;
2160
+ }
2161
+ return currentIndex;
1891
2162
  };
1892
2163
  /**
1893
- * Extracts all CashAssembly evaluations (i.e., substrings like $(...)) from the input text.
2164
+ * This function skips a block comment so a `)` written in the comment cannot end `$()`.
2165
+ * A `)` inside a block comment is comment text, not the closer of `$()`.
1894
2166
  *
1895
- * @param {string} text - The input string to scan for CashAssembly evaluations.
1896
- * @returns {string[]} An array of evaluation strings found in the input.
2167
+ * @param {string} cashAssemblyText - Text that contains the block comment.
2168
+ * @param {number} commentStartIndex - Index of the first slash of the block comment opener.
2169
+ * @returns {number} - Index of the character after the comment closer.
2170
+ * @throws {@link CashAssemblyBlockCommentUnclosedError} - When the block comment never closes.
2171
+ */
2172
+ const skipBlockCashAssemblyComment = (cashAssemblyText, commentStartIndex) => {
2173
+ let currentIndex = commentStartIndex + CASHASSEMBLY_COMMENT_START_LENGTH;
2174
+ while (currentIndex + 1 < cashAssemblyText.length) {
2175
+ if (cashAssemblyText[currentIndex] === "*" && cashAssemblyText[currentIndex + 1] === "/") return currentIndex + CASHASSEMBLY_BLOCK_COMMENT_END_LENGTH;
2176
+ currentIndex += 1;
2177
+ }
2178
+ throw new CashAssemblyBlockCommentUnclosedError(commentStartIndex, cashAssemblyText);
2179
+ };
2180
+ /**
2181
+ * Parenthesis matching with a depth count.
2182
+ *
2183
+ * Returns the index of the `)` that closes the `$()` starting at `evaluationStartIndex`.
2184
+ * Nested `$()` raise the depth so an inner closer cannot finish the outer evaluation.
2185
+ *
2186
+ * @param {string} cashAssemblyText - Text that contains the evaluation.
2187
+ * @param {number} evaluationStartIndex - Index of the `$` that opens this evaluation.
2188
+ * @returns {number} - Index of the matching closing parenthesis.
2189
+ * @throws {@link CashAssemblyEvaluationUnclosedError} - When the evaluation never closes.
2190
+ * @throws {@link CashAssemblyQuotedLiteralUnclosedError} - When a quoted literal inside the evaluation never closes.
2191
+ * @throws {@link CashAssemblyBlockCommentUnclosedError} - When a block comment inside the evaluation never closes.
2192
+ */
2193
+ const findCashAssemblyEvaluationCloseIndex = (cashAssemblyText, evaluationStartIndex) => {
2194
+ let currentIndex = evaluationStartIndex + CASHASSEMBLY_EVALUATION_START_LENGTH;
2195
+ let remainingOpenEvaluations = 1;
2196
+ while (currentIndex < cashAssemblyText.length) {
2197
+ const currentCharacter = cashAssemblyText[currentIndex];
2198
+ const nextCharacter = cashAssemblyText[currentIndex + 1];
2199
+ if (currentCharacter === "\"" || currentCharacter === "'") {
2200
+ currentIndex = skipQuotedCashAssemblyLiteral(cashAssemblyText, currentIndex);
2201
+ continue;
2202
+ }
2203
+ if (currentCharacter === "/" && nextCharacter === "/") {
2204
+ currentIndex = skipSingleLineCashAssemblyComment(cashAssemblyText, currentIndex);
2205
+ continue;
2206
+ }
2207
+ if (currentCharacter === "/" && nextCharacter === "*") {
2208
+ currentIndex = skipBlockCashAssemblyComment(cashAssemblyText, currentIndex);
2209
+ continue;
2210
+ }
2211
+ if (currentCharacter === "$" && nextCharacter === "(") {
2212
+ remainingOpenEvaluations += 1;
2213
+ currentIndex += CASHASSEMBLY_EVALUATION_START_LENGTH;
2214
+ continue;
2215
+ }
2216
+ if (currentCharacter === ")") {
2217
+ remainingOpenEvaluations -= 1;
2218
+ if (remainingOpenEvaluations === 0) return currentIndex;
2219
+ }
2220
+ currentIndex += 1;
2221
+ }
2222
+ throw new CashAssemblyEvaluationUnclosedError(evaluationStartIndex, remainingOpenEvaluations, cashAssemblyText);
2223
+ };
2224
+ /**
2225
+ * Scans text and returns every `$()` that starts in this string, including empty `$()`.
2226
+ *
2227
+ * Nested `$()` stay inside the outer evaluation.
2228
+ *
2229
+ * @param {string} cashAssemblyText - Text that may contain `$()`.
2230
+ * @returns {CashAssemblyEvaluationScanMatch[]} - Evaluations in left to right order.
2231
+ * @throws {@link CashAssemblyEvaluationUnclosedError} - When an evaluation never closes.
2232
+ * @throws {@link CashAssemblyQuotedLiteralUnclosedError} - When a quoted literal never closes.
2233
+ * @throws {@link CashAssemblyBlockCommentUnclosedError} - When a block comment never closes.
2234
+ */
2235
+ const scanCashAssemblyEvaluations = (cashAssemblyText) => {
2236
+ const scannedEvaluations = [];
2237
+ let currentIndex = 0;
2238
+ while (currentIndex < cashAssemblyText.length) {
2239
+ const currentCharacter = cashAssemblyText[currentIndex];
2240
+ const nextCharacter = cashAssemblyText[currentIndex + 1];
2241
+ if (currentCharacter === "$" && nextCharacter === "(") {
2242
+ const closeIndex = findCashAssemblyEvaluationCloseIndex(cashAssemblyText, currentIndex);
2243
+ const evaluationText = cashAssemblyText.slice(currentIndex, closeIndex + 1);
2244
+ scannedEvaluations.push({
2245
+ closeIndex,
2246
+ evaluationText,
2247
+ startIndex: currentIndex
2248
+ });
2249
+ currentIndex = closeIndex + 1;
2250
+ continue;
2251
+ }
2252
+ currentIndex += 1;
2253
+ }
2254
+ return scannedEvaluations;
2255
+ };
2256
+ /**
2257
+ * This function returns each `$()` that starts in this string.
2258
+ * Empty `$()` is omitted.
1897
2259
  *
1898
- * @example
1899
- * extractCashAssemblyEvaluations("OP_DUP <$(<foo>)> OP_HASH160 $(<bar>)");
1900
- * // returns ['$(<foo>)', '$(<bar>)']
2260
+ * @param {string} cashAssemblyText - Text that may contain `$()`.
2261
+ * @returns {string[]} - Complete non empty evaluation strings in left to right order.
2262
+ * @throws {@link CashAssemblyEvaluationUnclosedError} - When an evaluation never closes.
2263
+ * @throws {@link CashAssemblyQuotedLiteralUnclosedError} - When a quoted literal never closes.
2264
+ * @throws {@link CashAssemblyBlockCommentUnclosedError} - When a block comment never closes.
1901
2265
  */
1902
- const extractCashAssemblyEvaluations = (text) => {
1903
- return text.match(CASHASSEMBLY_EVALUATION_PATTERN) ?? [];
2266
+ const extractCashAssemblyEvaluations = (cashAssemblyText) => {
2267
+ const scannedEvaluations = scanCashAssemblyEvaluations(cashAssemblyText);
2268
+ const evaluations = [];
2269
+ for (const scannedEvaluation of scannedEvaluations) if (scannedEvaluation.evaluationText !== EMPTY_CASHASSEMBLY_EVALUATION) evaluations.push(scannedEvaluation.evaluationText);
2270
+ return evaluations;
1904
2271
  };
1905
2272
  /**
2273
+ * This function returns true when the value is exactly one CashAssembly `$()`.
2274
+ * Text with characters outside `$()`, empty `$()`, or a failed scan returns false.
2275
+ *
2276
+ * @param {unknown} expression - Value to test.
2277
+ * @returns {boolean} - True when the value is a string that is one complete evaluation and nothing else.
2278
+ */
2279
+ const isCashAssemblyExpression = (expression) => {
2280
+ if (typeof expression !== "string") return false;
2281
+ try {
2282
+ const evaluations = extractCashAssemblyEvaluations(expression);
2283
+ return evaluations.length === 1 && evaluations[0] === expression;
2284
+ } catch {
2285
+ return false;
2286
+ }
2287
+ };
2288
+
2289
+ //#endregion
2290
+ //#region source/cash-assembly/evaluations.ts
2291
+ /**
1906
2292
  * Returns the segment of `identifier` before the first `.`.
1907
2293
  *
1908
2294
  * When there is no `.`, returns `identifier` unchanged.
1909
2295
  * Multi segment identifiers such as `foo.bar.baz` resolve to `foo`.
1910
2296
  *
1911
2297
  * @param {string} identifier - Identifier that may contain a dot.
1912
- * @returns {string} The base name before the first `.`.
2298
+ * @returns {string} - The base name before the first `.`.
1913
2299
  */
1914
2300
  const resolveIdentifierBaseName = (identifier) => {
1915
2301
  const firstDotIndex = identifier.indexOf(".");
@@ -1917,23 +2303,6 @@ const resolveIdentifierBaseName = (identifier) => {
1917
2303
  return identifier.slice(0, firstDotIndex);
1918
2304
  };
1919
2305
  /**
1920
- * Extracts unique variable identifiers enclosed in angle brackets from each evaluation string.
1921
- *
1922
- * CashAssembly literal tokens such as hex bytes, numbers, and quoted strings are excluded via
1923
- * {@link CASHASSEMBLY_LITERAL_TOKEN_PATTERN}. For example, `<0x02>` and `<"minting">` are not returned.
1924
- *
1925
- * @param {string[]} evaluations - An array of evaluation strings from which to extract variable names.
1926
- * @returns {string[]} An array of variable names.
1927
- */
1928
- const extractVariablesFromEvaluations = (evaluations) => {
1929
- const uniqueVariables = /* @__PURE__ */ new Set();
1930
- for (const evaluation of evaluations) for (const [, extractedIdentifier] of evaluation.matchAll(CASHASSEMBLY_VARIABLE_PATTERN)) {
1931
- if (CASHASSEMBLY_LITERAL_TOKEN_PATTERN.test(extractedIdentifier)) continue;
1932
- uniqueVariables.add(extractedIdentifier);
1933
- }
1934
- return [...uniqueVariables];
1935
- };
1936
- /**
1937
2306
  * Decodes compiled CashAssembly evaluation bytes into a string representation.
1938
2307
  *
1939
2308
  * 'evaluationDecodeMode' determines how the evaluation bytes are interpreted and presented.
@@ -1941,10 +2310,9 @@ const extractVariablesFromEvaluations = (evaluations) => {
1941
2310
  * such as hashes, and `boolean` to represent boolean values.
1942
2311
  *
1943
2312
  * @param {Uint8Array} compiledResult - The compiled evaluation bytecode.
1944
- * @param {CompiledCashAssemblyDecodeMode} [evaluationDecodeMode='utf8'] - The decode mode used to convert
1945
- * bytes to text.
1946
- * @returns {string} The decoded value as a string suitable for inline replacement.
1947
- * @throws {@link CashAssemblyVmNumberDecodeError} When `evaluationDecodeMode` is `bigint` and the bytes are not a VM number.
2313
+ * @param {CompiledCashAssemblyDecodeMode} evaluationDecodeMode - The decode mode used to convert bytes to text.
2314
+ * @returns {string} - The decoded value as a string suitable for inline replacement.
2315
+ * @throws {@link CashAssemblyVmNumberDecodeError} - When `evaluationDecodeMode` is `bigint` and the bytes are not a VM number.
1948
2316
  */
1949
2317
  const decodeCompiledCashAssemblyEvaluation = (compiledResult, evaluationDecodeMode = "utf8") => {
1950
2318
  if (evaluationDecodeMode === "uint8array") return String(compiledResult);
@@ -1963,26 +2331,26 @@ const decodeCompiledCashAssemblyEvaluation = (compiledResult, evaluationDecodeMo
1963
2331
  * @param {CompilerBch} compiler - The libauth compiler from {@link compileCashAssemblyEvaluations}.
1964
2332
  * @param {string} evaluation - The specific evaluation string to compile.
1965
2333
  * @param {Record<string, Uint8Array>} variables - A record mapping variable names to their values.
1966
- * @returns {Uint8Array} The compiled bytecode.
1967
- * @throws {@link CashAssemblyRequiredVariableMissingError} If a required variable is not present.
1968
- * @throws {@link CashAssemblyVariableTypeMismatchError} If a variable value is not a Uint8Array.
1969
- * @throws {@link CashAssemblyCompilationFailedError} If libauth compilation fails.
1970
- */
1971
- const generateCashAssemblyBytecode = (compiler, evaluation, variables) => {
1972
- const variableNames = extractVariablesFromEvaluations([evaluation]);
1973
- const missingVariables = variableNames.filter((name) => !Object.hasOwn(variables, name));
2334
+ * @param {string[]} requiredVariableNames - WalletData names from {@link collectVariablesUsingParseScript} that must be present.
2335
+ * @returns {Uint8Array} - The compiled bytecode.
2336
+ * @throws {@link CashAssemblyRequiredVariableMissingError} - If a required variable is not present.
2337
+ * @throws {@link CashAssemblyVariableTypeMismatchError} - If a variable value is not a Uint8Array.
2338
+ * @throws {@link CashAssemblyCompilationFailedError} - If libauth compilation fails.
2339
+ */
2340
+ const generateCashAssemblyBytecode = (compiler, evaluation, variables, requiredVariableNames) => {
2341
+ const missingVariables = requiredVariableNames.filter((name) => Object.hasOwn(variables, name) === false);
1974
2342
  if (missingVariables.length > 0) throw new CashAssemblyRequiredVariableMissingError(missingVariables);
1975
2343
  const bytecode = {};
1976
- for (const variableName of variableNames) {
2344
+ for (const variableName of requiredVariableNames) {
1977
2345
  const value = variables[variableName];
1978
- if (!(value instanceof Uint8Array)) throw new CashAssemblyVariableTypeMismatchError(variableName, "Uint8Array", typeof value);
2346
+ if (value instanceof Uint8Array === false) throw new CashAssemblyVariableTypeMismatchError(variableName, "Uint8Array", typeof value);
1979
2347
  bytecode[variableName] = value;
1980
2348
  }
1981
2349
  const compiledBytecode = compiler.generateBytecode({
1982
2350
  data: { bytecode },
1983
2351
  scriptId: evaluation
1984
2352
  });
1985
- if (!compiledBytecode.success) {
2353
+ if (compiledBytecode.success === false) {
1986
2354
  let compilationFailureMessage = "unknown compilation failure";
1987
2355
  if ("errors" in compiledBytecode && compiledBytecode.errors.length > 0) compilationFailureMessage = compiledBytecode.errors.map((compilationError) => compilationError.error).join("; ");
1988
2356
  throw new CashAssemblyCompilationFailedError(compilationFailureMessage);
@@ -1990,68 +2358,168 @@ const generateCashAssemblyBytecode = (compiler, evaluation, variables) => {
1990
2358
  return compiledBytecode.bytecode;
1991
2359
  };
1992
2360
  /**
1993
- * Prepares a compiler for the provided CashAssembly evaluations, setting required variables as 'WalletData'.
2361
+ * Prepares a compiler for the provided CashAssembly source strings, setting required variables as 'WalletData'.
2362
+ *
2363
+ * Each evaluation string is registered as a script whose id and source are that string. Template
2364
+ * scripts are then copied onto the same table. If a source is a template id such as `scriptA`, that
2365
+ * id must compile the template source. Leaving the id as its own source would compile the text
2366
+ * `scriptA` and include itself.
1994
2367
  *
1995
- * @param {string[]} evaluations - Array of evaluation strings (e.g., ['$(<var1>)', '$(<var2> <var3>)']).
1996
- * @returns {CompilerBch} A Libauth compiler instance for use with these evaluations.
2368
+ * @param {CompileCashAssemblyEvaluationsParameters} parameters - Source strings, optional template scripts, and already collected WalletData names.
2369
+ * @returns {CompilerBch} - A Libauth compiler instance for use with these source strings.
1997
2370
  */
1998
- const compileCashAssemblyEvaluations = (evaluations) => {
2371
+ const compileCashAssemblyEvaluations = (parameters) => {
2372
+ const { evaluations, templateScripts, variableNames } = parameters;
1999
2373
  const scripts = {};
2000
2374
  for (const evaluation of evaluations) scripts[evaluation] = evaluation;
2001
- const variableNames = extractVariablesFromEvaluations(evaluations);
2375
+ const knownScriptIdentifiers = /* @__PURE__ */ new Set();
2376
+ if (templateScripts !== void 0) for (const [scriptIdentifier, scriptDefinition] of Object.entries(templateScripts)) {
2377
+ scripts[scriptIdentifier] = scriptDefinition;
2378
+ knownScriptIdentifiers.add(scriptIdentifier);
2379
+ }
2002
2380
  const variables = {};
2003
- for (const variableName of variableNames) variables[resolveIdentifierBaseName(variableName)] = { type: "WalletData" };
2381
+ for (const variableName of variableNames) {
2382
+ if (knownScriptIdentifiers.has(variableName) === true) continue;
2383
+ variables[resolveIdentifierBaseName(variableName)] = { type: "WalletData" };
2384
+ }
2004
2385
  return createCompilerBch({
2005
2386
  scripts,
2006
2387
  variables
2007
2388
  });
2008
2389
  };
2009
2390
  /**
2010
- * Compiles all CashAssembly evaluations in a text string and replaces each evaluation
2011
- * with a decoded string representation.
2391
+ * Compiles one CashAssembly source string to bytecode.
2392
+ *
2393
+ * The source may be an evaluation such as `$(scriptA)` or CashAssembly such as
2394
+ * `scriptA`. WalletData names come from {@link collectVariablesUsingParseScript}.
2395
+ *
2396
+ * @param {CompileCashAssemblySourceToBytesParameters} parameters - Source text and compilation context.
2397
+ * @returns {Uint8Array} - Compiled bytecode for that source.
2398
+ * @throws {@link CashAssemblyRequiredVariableMissingError} - When a required variable is not present in the variables map.
2399
+ * @throws {@link CashAssemblyPrimitiveMethodMissingError} - When a supported primitive hint has an unknown method.
2400
+ * @throws {@link CashAssemblyPrimitiveVariableMissingError} - When a supported primitive method is missing its runtime value.
2401
+ * @throws {@link CashAssemblyUnsupportedValueTypeError} - When a primitive method return type cannot be embedded as bytes.
2402
+ * @throws {@link CashAssemblyNumberNotSafeIntegerError} - When a number variable is not a safe integer.
2403
+ * @throws {@link CashAssemblyIdentifierCollisionError} - When one identifier matches two resolution types.
2404
+ * @throws {@link CashAssemblyCompilationFailedError} - When Libauth compilation fails.
2405
+ */
2406
+ const compileCashAssemblySourceToBytes = (parameters) => {
2407
+ const { cashAssemblySource, variables, templateVariables, templateScripts } = parameters;
2408
+ assertNoIdentifierCollisions({
2409
+ variables,
2410
+ templateScripts
2411
+ });
2412
+ const variableNames = collectVariablesUsingParseScript(cashAssemblySource, templateScripts);
2413
+ const primitiveMethodBytes = resolvePrimitiveMethodBytes({
2414
+ variableNames,
2415
+ templateVariables,
2416
+ variables
2417
+ });
2418
+ const missingVariables = [];
2419
+ for (const variableName of variableNames) {
2420
+ if (Object.hasOwn(primitiveMethodBytes, variableName) === true) continue;
2421
+ if (Object.hasOwn(variables, variableName) === true) continue;
2422
+ missingVariables.push(variableName);
2423
+ }
2424
+ if (missingVariables.length > 0) throw new CashAssemblyRequiredVariableMissingError(missingVariables);
2425
+ const variableBytes = {};
2426
+ for (const variableName of variableNames) {
2427
+ if (Object.hasOwn(primitiveMethodBytes, variableName) === true) {
2428
+ variableBytes[variableName] = primitiveMethodBytes[variableName];
2429
+ continue;
2430
+ }
2431
+ variableBytes[variableName] = convertValueToBytes(variables[variableName], variableName);
2432
+ }
2433
+ return generateCashAssemblyBytecode(compileCashAssemblyEvaluations({
2434
+ evaluations: [cashAssemblySource],
2435
+ templateScripts,
2436
+ variableNames
2437
+ }), cashAssemblySource, variableBytes, variableNames);
2438
+ };
2439
+ /**
2440
+ * Compiles one CashAssembly source to bytes and decodes those bytes to text.
2441
+ *
2442
+ * @param {CompileCashAssemblySourceToDecodedTextParameters} parameters - Source text, WalletData, optional template context, and decode mode.
2443
+ * @returns {string} - Decoded compilation result for that source.
2444
+ * @throws {@link CashAssemblyRequiredVariableMissingError} - When a required variable is not present in the variables map.
2445
+ * @throws {@link CashAssemblyPrimitiveMethodMissingError} - When a supported primitive hint has an unknown method.
2446
+ * @throws {@link CashAssemblyPrimitiveVariableMissingError} - When a supported primitive method is missing its runtime value.
2447
+ * @throws {@link CashAssemblyUnsupportedValueTypeError} - When a primitive method return type cannot be embedded as bytes.
2448
+ * @throws {@link CashAssemblyNumberNotSafeIntegerError} - When a number variable is not a safe integer.
2449
+ * @throws {@link CashAssemblyVmNumberDecodeError} - When `evaluationDecodeMode` is `bigint` and compiled bytes are not a VM number.
2450
+ * @throws {@link CashAssemblyIdentifierCollisionError} - When one identifier matches two resolution types.
2451
+ * @throws {@link CashAssemblyCompilationFailedError} - When Libauth compilation fails.
2452
+ */
2453
+ const compileCashAssemblySourceToDecodedText = (parameters) => {
2454
+ const { cashAssemblySource, variables, templateVariables, templateScripts, evaluationDecodeMode } = parameters;
2455
+ return decodeCompiledCashAssemblyEvaluation(compileCashAssemblySourceToBytes({
2456
+ cashAssemblySource,
2457
+ variables,
2458
+ templateVariables,
2459
+ templateScripts
2460
+ }), evaluationDecodeMode);
2461
+ };
2462
+ /**
2463
+ * Compiles CashAssembly text. WalletData names come from parseScript. `$()` evaluations in text
2464
+ * are found by scanning the string and matching parentheses.
2465
+ *
2466
+ * When `cashAssemblyText` contains `$()`, each non empty evaluation is compiled and
2467
+ * replaced in place. Empty `$()` is copied through. Surrounding text is kept. Evaluation drops
2468
+ * push opcodes and keeps the stack payload.
2469
+ *
2470
+ * When `cashAssemblyText` contains no `$()` at all, the entire string is compiled as CashAssembly.
2471
+ * Push opcodes from `<...>` remain. Pass a template script id such as `scriptA` or
2472
+ * concatenated ids such as `scriptA scriptB`.
2473
+ *
2474
+ * Required WalletData names come from {@link collectVariablesUsingParseScript}.
2012
2475
  *
2013
2476
  * @param {CompileCashAssemblyStringParameters} parameters - Parameters for compiling the CashAssembly string.
2014
- * @param {string} parameters.cashAssemblyText - The string with CashAssembly evaluations.
2015
- * @param {Record<string, XOInvitationVariableValue | Uint8Array>} parameters.variables - Object mapping
2016
- * variable names to values for compilation.
2017
- * @param {CompiledCashAssemblyDecodeMode} [parameters.evaluationDecodeMode='utf8'] - The decode mode used
2018
- * after each evaluation is compiled. See {@link decodeCompiledCashAssemblyEvaluation}.
2019
- * @param {XOTemplate['variables']} [parameters.templateVariables] - Optional template variable definitions
2020
- * used to resolve method resolvable `<name.method>` pushes via each variable's `type`.
2021
- * @returns {string} Compiled text with all evaluations replaced by decoded string values.
2022
- * @throws {@link CashAssemblyRequiredVariableMissingError} When a required variable is not present in the variables map.
2023
- * @throws {@link CashAssemblyPrimitiveMethodMissingError} When a method resolvable primitive type has an unknown method.
2024
- * @throws {@link CashAssemblyPrimitiveVariableMissingError} When a method resolvable primitive method is missing its runtime value.
2025
- * @throws {@link CashAssemblyUnsupportedValueTypeError} When a primitive method return type cannot be embedded as bytes.
2026
- * @throws {@link CashAssemblyNumberNotSafeIntegerError} When a number variable is not a safe integer.
2027
- * @throws {@link CashAssemblyVmNumberDecodeError} When `evaluationDecodeMode` is `bigint` and an evaluation is not a VM number.
2477
+ * @returns {string} - Compiled text with evaluations replaced, or decoded bytecode when the whole string is compiled as CashAssembly.
2478
+ * @throws {@link CashAssemblyRequiredVariableMissingError} - When a required variable is not present in the variables map.
2479
+ * @throws {@link CashAssemblyPrimitiveMethodMissingError} - When a supported primitive hint has an unknown method.
2480
+ * @throws {@link CashAssemblyPrimitiveVariableMissingError} - When a supported primitive method is missing its runtime value.
2481
+ * @throws {@link CashAssemblyUnsupportedValueTypeError} - When a primitive method return type cannot be embedded as bytes.
2482
+ * @throws {@link CashAssemblyNumberNotSafeIntegerError} - When a number variable is not a safe integer.
2483
+ * @throws {@link CashAssemblyVmNumberDecodeError} - When `evaluationDecodeMode` is `bigint` and compiled bytes are not a VM number.
2484
+ * @throws {@link CashAssemblyIdentifierCollisionError} - When one identifier matches two resolution types.
2485
+ * @throws {@link CashAssemblyCompilationFailedError} - When Libauth compilation fails.
2486
+ * @throws {@link CashAssemblyEvaluationUnclosedError} - When an evaluation never closes.
2487
+ * @throws {@link CashAssemblyQuotedLiteralUnclosedError} - When a quoted literal never closes.
2488
+ * @throws {@link CashAssemblyBlockCommentUnclosedError} - When a block comment never closes.
2028
2489
  */
2029
2490
  const compileCashAssemblyString = (parameters) => {
2030
- const { cashAssemblyText, variables, evaluationDecodeMode = "utf8", templateVariables } = parameters;
2031
- return cashAssemblyText.replace(CASHASSEMBLY_EVALUATION_PATTERN, (evaluation) => {
2032
- const variableNames = extractVariablesFromEvaluations([evaluation]);
2033
- const primitiveMethodBytes = resolvePrimitiveMethodBytes({
2034
- identifiers: variableNames,
2035
- templateVariables,
2036
- variables
2037
- });
2038
- const missingVariables = variableNames.filter((variableName) => {
2039
- if (Object.hasOwn(primitiveMethodBytes, variableName) === true) return false;
2040
- return Object.hasOwn(variables, variableName) === false;
2041
- });
2042
- if (missingVariables.length > 0) throw new CashAssemblyRequiredVariableMissingError(missingVariables);
2043
- const variableBytes = {};
2044
- for (const variableName of variableNames) {
2045
- if (Object.hasOwn(primitiveMethodBytes, variableName) === true) {
2046
- variableBytes[variableName] = primitiveMethodBytes[variableName];
2047
- continue;
2048
- }
2049
- variableBytes[variableName] = convertValueToBytes(variables[variableName], variableName);
2050
- }
2051
- return decodeCompiledCashAssemblyEvaluation(generateCashAssemblyBytecode(compileCashAssemblyEvaluations([evaluation]), evaluation, variableBytes), evaluationDecodeMode);
2491
+ const { cashAssemblyText, variables, evaluationDecodeMode = "utf8", templateVariables, templateScripts } = parameters;
2492
+ const scannedEvaluations = scanCashAssemblyEvaluations(cashAssemblyText);
2493
+ if (scannedEvaluations.length === 0) return compileCashAssemblySourceToDecodedText({
2494
+ cashAssemblySource: cashAssemblyText,
2495
+ variables,
2496
+ templateVariables,
2497
+ templateScripts,
2498
+ evaluationDecodeMode
2052
2499
  });
2500
+ let textWithCompiledEvaluations = "";
2501
+ let nextTextStartIndex = 0;
2502
+ let hasCompiledNonEmptyEvaluation = false;
2503
+ for (const scannedEvaluation of scannedEvaluations) {
2504
+ textWithCompiledEvaluations += cashAssemblyText.slice(nextTextStartIndex, scannedEvaluation.startIndex);
2505
+ if (scannedEvaluation.evaluationText === EMPTY_CASHASSEMBLY_EVALUATION) textWithCompiledEvaluations += scannedEvaluation.evaluationText;
2506
+ else {
2507
+ hasCompiledNonEmptyEvaluation = true;
2508
+ textWithCompiledEvaluations += compileCashAssemblySourceToDecodedText({
2509
+ cashAssemblySource: scannedEvaluation.evaluationText,
2510
+ variables,
2511
+ templateVariables,
2512
+ templateScripts,
2513
+ evaluationDecodeMode
2514
+ });
2515
+ }
2516
+ nextTextStartIndex = scannedEvaluation.closeIndex + 1;
2517
+ }
2518
+ textWithCompiledEvaluations += cashAssemblyText.slice(nextTextStartIndex);
2519
+ if (hasCompiledNonEmptyEvaluation === false) return cashAssemblyText;
2520
+ return textWithCompiledEvaluations;
2053
2521
  };
2054
2522
 
2055
2523
  //#endregion
2056
- export { AsyncPushIterator, CASHASSEMBLY_EVALUATION_PATTERN, CASHASSEMBLY_EXPRESSION_PATTERN, CASHASSEMBLY_LITERAL_TOKEN_PATTERN, CASHASSEMBLY_VARIABLE_METHOD_REFERENCE_PATTERN, CASHASSEMBLY_VARIABLE_PATTERN, CashAssemblyCompilationFailedError, CashAssemblyNumberNotSafeIntegerError, CashAssemblyPrimitiveMethodMissingError, CashAssemblyPrimitiveVariableMissingError, CashAssemblyRequiredVariableMissingError, CashAssemblyUnsupportedValueTypeError, CashAssemblyVariableTypeMismatchError, CashAssemblyVmNumberDecodeError, EventEmitter, ExponentialBackoff, ExponentialBackoffExternallyAbortable, ExponentialBackoffMaxRetriesHitError, ExponentialBackoffNonIntegerError, ExponentialBackoffNumberNotFiniteError, ExponentialBackoffNumberOutOfBoundsError, ExponentialBackoffNumberTooSmallError, ExponentialBackoffStoppedRetriesError, ExternallyAbortedExponentialBackoffExternalSignalAbortedError, ExternallyAbortedExponentialBackoffInternalSignalAbortedError, SSEEventParser, TemplateInvalidError, TemplateJsonMalformedError, TemplateSerializationFailedError, VIEW_PROPERTIES_DESCRIPTION_MAX_LENGTH, VIEW_PROPERTIES_ICON_MAX_LENGTH, VIEW_PROPERTIES_NAME_MAX_LENGTH, WaitForTimeoutError, bchVmVersionSchema, buildErrorDescription, compileCashAssemblyEvaluations, compileCashAssemblyString, convertValueToBytes, decodeCompiledCashAssemblyEvaluation, extendedJsonReplacer, extendedJsonReviver, extractCashAssemblyEvaluations, extractVariablesFromEvaluations, fromExtendedJson, generateCashAssemblyBytecode, generateTemplateIdentifier, isCashAssemblyExpression, parseTemplate, resolvePrimitiveMethodBytes, satoshisSchema, scriptToScriptHash, serializeTemplate, toExtendedJson, uint8ArraySchema, xoTemplateActionIntentSchema, xoTemplateActionRequirementsSchema, xoTemplateActionRoleRequirementsSchema, xoTemplateActionRoleSchema, xoTemplateActionSchema, xoTemplateAssetAmountsSchema, xoTemplateConstantSchema, xoTemplateDataSchema, xoTemplateDefaultsSchema, xoTemplateIconSchema, xoTemplateImportDefaultValueSchema, xoTemplateInputSchema, xoTemplateIntentSchema, xoTemplateLockingScriptIntentSchema, xoTemplateLockingScriptRoleSchema, xoTemplateLockingScriptSchema, xoTemplateLockingTypeSchema, xoTemplateNftCapabilitySchema, xoTemplateNonFungibleTokenDetailsSchema, xoTemplateOutputIntentSchema, xoTemplateOutputSchema, xoTemplatePrimitiveTypeSchema, xoTemplateResourceSchema, xoTemplateRoleSlotSchema, xoTemplateRoleSlotsRequirementsSchema, xoTemplateSchema, xoTemplateStateSchema, xoTemplateTokenSchema, xoTemplateTransactionInputSchema, xoTemplateTransactionOutputSchema, xoTemplateTransactionRoleDataSchema, xoTemplateTransactionSchema, xoTemplateVariableSchema, xoTemplateViewPropertiesSchema };
2524
+ export { AsyncPushIterator, CashAssemblyBlockCommentUnclosedError, CashAssemblyCompilationFailedError, CashAssemblyEvaluationUnclosedError, CashAssemblyIdentifierCollisionError, CashAssemblyNumberNotSafeIntegerError, CashAssemblyPrimitiveMethodMissingError, CashAssemblyPrimitiveVariableMissingError, CashAssemblyQuotedLiteralUnclosedError, CashAssemblyRequiredVariableMissingError, CashAssemblyUnsupportedValueTypeError, CashAssemblyVariableTypeMismatchError, CashAssemblyVmNumberDecodeError, EventEmitter, ExponentialBackoff, ExponentialBackoffExternallyAbortable, ExponentialBackoffMaxRetriesHitError, ExponentialBackoffNonIntegerError, ExponentialBackoffNumberNotFiniteError, ExponentialBackoffNumberOutOfBoundsError, ExponentialBackoffNumberTooSmallError, ExponentialBackoffStoppedRetriesError, ExternallyAbortedExponentialBackoffExternalSignalAbortedError, ExternallyAbortedExponentialBackoffInternalSignalAbortedError, SSEEventParser, TemplateInvalidError, TemplateJsonMalformedError, TemplateSerializationFailedError, VIEW_PROPERTIES_DESCRIPTION_MAX_LENGTH, VIEW_PROPERTIES_ICON_MAX_LENGTH, VIEW_PROPERTIES_NAME_MAX_LENGTH, WaitForTimeoutError, bchVmVersionSchema, buildErrorDescription, collectVariablesUsingParseScript, compileCashAssemblyString, extendedJsonReplacer, extendedJsonReviver, extractCashAssemblyEvaluations, fromExtendedJson, generateTemplateIdentifier, isCashAssemblyExpression, parseTemplate, satoshisSchema, scriptToScriptHash, serializeTemplate, toExtendedJson, uint8ArraySchema, xoTemplateActionIntentSchema, xoTemplateActionRequirementsSchema, xoTemplateActionRoleRequirementsSchema, xoTemplateActionRoleSchema, xoTemplateActionSchema, xoTemplateAssetAmountsSchema, xoTemplateConstantSchema, xoTemplateDataSchema, xoTemplateDefaultsSchema, xoTemplateIconSchema, xoTemplateImportDefaultValueSchema, xoTemplateInputSchema, xoTemplateIntentSchema, xoTemplateLockingScriptIntentSchema, xoTemplateLockingScriptRoleSchema, xoTemplateLockingScriptSchema, xoTemplateLockingTypeSchema, xoTemplateNftCapabilitySchema, xoTemplateNonFungibleTokenDetailsSchema, xoTemplateOutputIntentSchema, xoTemplateOutputSchema, xoTemplatePrimitiveTypeSchema, xoTemplateResourceSchema, xoTemplateRoleSlotSchema, xoTemplateRoleSlotsRequirementsSchema, xoTemplateSchema, xoTemplateStateSchema, xoTemplateTokenSchema, xoTemplateTransactionInputSchema, xoTemplateTransactionOutputSchema, xoTemplateTransactionRoleDataSchema, xoTemplateTransactionSchema, xoTemplateVariableSchema, xoTemplateViewPropertiesSchema };
2057
2525
  //# sourceMappingURL=index.mjs.map