@dcasegr/amm-client 0.0.0-stage → 0.38.1

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.
Files changed (121) hide show
  1. package/README.md +107 -2
  2. package/dist/client.d.ts +534 -0
  3. package/dist/client.js +1319 -0
  4. package/dist/errors.d.ts +17 -0
  5. package/dist/errors.js +75 -0
  6. package/dist/index.d.ts +51 -0
  7. package/dist/index.js +53 -0
  8. package/dist/lib/config.d.ts +42 -0
  9. package/dist/lib/config.js +64 -0
  10. package/dist/lib/keys.d.ts +42 -0
  11. package/dist/lib/keys.js +48 -0
  12. package/dist/lib/overlay.d.ts +190 -0
  13. package/dist/lib/overlay.js +302 -0
  14. package/dist/lib/skein.d.ts +82 -0
  15. package/dist/lib/skein.js +142 -0
  16. package/dist/lib/tokenId.d.ts +72 -0
  17. package/dist/lib/tokenId.js +121 -0
  18. package/dist/lp/amounts.d.ts +12 -0
  19. package/dist/lp/amounts.js +38 -0
  20. package/dist/lp/close.d.ts +90 -0
  21. package/dist/lp/close.js +140 -0
  22. package/dist/lp/images.d.ts +11 -0
  23. package/dist/lp/images.js +45 -0
  24. package/dist/lp/inventory.d.ts +68 -0
  25. package/dist/lp/inventory.js +84 -0
  26. package/dist/lp/poolDeploy.d.ts +220 -0
  27. package/dist/lp/poolDeploy.js +291 -0
  28. package/dist/lp/poolRows.d.ts +13 -0
  29. package/dist/lp/poolRows.js +19 -0
  30. package/dist/lp/positions.d.ts +81 -0
  31. package/dist/lp/positions.js +144 -0
  32. package/dist/lp/rebalance.d.ts +101 -0
  33. package/dist/lp/rebalance.js +168 -0
  34. package/dist/lp/wallet.d.ts +12 -0
  35. package/dist/lp/wallet.js +19 -0
  36. package/dist/market/activeSwap.d.ts +158 -0
  37. package/dist/market/activeSwap.js +320 -0
  38. package/dist/market/plan.d.ts +85 -0
  39. package/dist/market/plan.js +149 -0
  40. package/dist/market/prices.d.ts +13 -0
  41. package/dist/market/prices.js +14 -0
  42. package/dist/market/relay.d.ts +84 -0
  43. package/dist/market/relay.js +201 -0
  44. package/dist/market/swapAction.d.ts +306 -0
  45. package/dist/market/swapAction.js +508 -0
  46. package/dist/market/swapFlow.d.ts +58 -0
  47. package/dist/market/swapFlow.js +86 -0
  48. package/dist/market/view.d.ts +58 -0
  49. package/dist/market/view.js +63 -0
  50. package/dist/pool/artifact.d.ts +23 -0
  51. package/dist/pool/artifact.js +38 -0
  52. package/dist/pool/index.d.ts +8 -0
  53. package/dist/pool/index.js +8 -0
  54. package/dist/pool/oneColor.d.ts +19 -0
  55. package/dist/pool/oneColor.js +39 -0
  56. package/dist/pool/pool.artifact.json +5069 -0
  57. package/dist/pool/runar-ir-schema.d.ts +21 -0
  58. package/dist/pool/runar-ir-schema.js +18 -0
  59. package/dist/pool/template.d.ts +295 -0
  60. package/dist/pool/template.js +715 -0
  61. package/dist/vendor/runar/LICENSE +21 -0
  62. package/dist/vendor/runar/NOTICE +15 -0
  63. package/dist/vendor/runar/runar-ir-schema/src/abi-type-encoding.d.ts +42 -0
  64. package/dist/vendor/runar/runar-ir-schema/src/abi-type-encoding.js +53 -0
  65. package/dist/vendor/runar/runar-ir-schema/src/anf-ir.d.ts +221 -0
  66. package/dist/vendor/runar/runar-ir-schema/src/anf-ir.js +33 -0
  67. package/dist/vendor/runar/runar-ir-schema/src/artifact.d.ts +288 -0
  68. package/dist/vendor/runar/runar-ir-schema/src/artifact.js +8 -0
  69. package/dist/vendor/runar/runar-ir-schema/src/canonical-json.d.ts +43 -0
  70. package/dist/vendor/runar/runar-ir-schema/src/canonical-json.js +204 -0
  71. package/dist/vendor/runar/runar-ir-schema/src/input-limits.d.ts +89 -0
  72. package/dist/vendor/runar/runar-ir-schema/src/input-limits.js +100 -0
  73. package/dist/vendor/runar/runar-ir-schema/src/stack-ir.d.ts +118 -0
  74. package/dist/vendor/runar/runar-ir-schema/src/stack-ir.js +8 -0
  75. package/dist/vendor/runar/runar-ir-schema/src/state-layout.d.ts +56 -0
  76. package/dist/vendor/runar/runar-ir-schema/src/state-layout.js +145 -0
  77. package/dist/vendor/runar/runar-ir-schema/src/unknown-anf-kind-error.d.ts +18 -0
  78. package/dist/vendor/runar/runar-ir-schema/src/unknown-anf-kind-error.js +25 -0
  79. package/dist/vendor/runar/runar-sdk/src/anf-interpreter.d.ts +160 -0
  80. package/dist/vendor/runar/runar-sdk/src/anf-interpreter.js +1149 -0
  81. package/dist/vendor/runar/runar-sdk/src/calling.d.ts +98 -0
  82. package/dist/vendor/runar/runar-sdk/src/calling.js +209 -0
  83. package/dist/vendor/runar/runar-sdk/src/contract.d.ts +443 -0
  84. package/dist/vendor/runar/runar-sdk/src/contract.js +2516 -0
  85. package/dist/vendor/runar/runar-sdk/src/deployment.d.ts +36 -0
  86. package/dist/vendor/runar/runar-sdk/src/deployment.js +119 -0
  87. package/dist/vendor/runar/runar-sdk/src/errors.d.ts +46 -0
  88. package/dist/vendor/runar/runar-sdk/src/errors.js +59 -0
  89. package/dist/vendor/runar/runar-sdk/src/oppushtx.d.ts +23 -0
  90. package/dist/vendor/runar/runar-sdk/src/oppushtx.js +95 -0
  91. package/dist/vendor/runar/runar-sdk/src/ordinals/envelope.d.ts +31 -0
  92. package/dist/vendor/runar/runar-sdk/src/ordinals/envelope.js +272 -0
  93. package/dist/vendor/runar/runar-sdk/src/ordinals/types.d.ts +21 -0
  94. package/dist/vendor/runar/runar-sdk/src/ordinals/types.js +4 -0
  95. package/dist/vendor/runar/runar-sdk/src/providers/provider.d.ts +65 -0
  96. package/dist/vendor/runar/runar-sdk/src/providers/provider.js +59 -0
  97. package/dist/vendor/runar/runar-sdk/src/providers/wallet-provider.d.ts +94 -0
  98. package/dist/vendor/runar/runar-sdk/src/providers/wallet-provider.js +297 -0
  99. package/dist/vendor/runar/runar-sdk/src/script-utils.d.ts +54 -0
  100. package/dist/vendor/runar/runar-sdk/src/script-utils.js +252 -0
  101. package/dist/vendor/runar/runar-sdk/src/signers/signer.d.ts +17 -0
  102. package/dist/vendor/runar/runar-sdk/src/signers/signer.js +4 -0
  103. package/dist/vendor/runar/runar-sdk/src/spend-safety.d.ts +31 -0
  104. package/dist/vendor/runar/runar-sdk/src/spend-safety.js +35 -0
  105. package/dist/vendor/runar/runar-sdk/src/state.d.ts +54 -0
  106. package/dist/vendor/runar/runar-sdk/src/state.js +584 -0
  107. package/dist/vendor/runar/runar-sdk/src/types.d.ts +227 -0
  108. package/dist/vendor/runar/runar-sdk/src/types.js +4 -0
  109. package/dist/vendor/runar/runar-sdk/src/unsound-primitives.d.ts +30 -0
  110. package/dist/vendor/runar/runar-sdk/src/unsound-primitives.js +42 -0
  111. package/dist/vendor/runar/runar-sdk.d.ts +9 -0
  112. package/dist/vendor/runar/runar-sdk.js +8 -0
  113. package/dist/wallet/action.d.ts +28 -0
  114. package/dist/wallet/action.js +42 -0
  115. package/dist/wallet/brc29.d.ts +83 -0
  116. package/dist/wallet/brc29.js +80 -0
  117. package/dist/wallet/color.d.ts +110 -0
  118. package/dist/wallet/color.js +134 -0
  119. package/dist/wallet/swapPayouts.d.ts +35 -0
  120. package/dist/wallet/swapPayouts.js +146 -0
  121. package/package.json +39 -4
@@ -0,0 +1,1149 @@
1
+ /**
2
+ * Lightweight ANF interpreter for auto-computing state transitions.
3
+ *
4
+ * Given a compiled artifact's ANF IR, the current contract state, and
5
+ * method arguments, this interpreter walks the ANF bindings and computes
6
+ * the new state. It handles `update_prop` nodes to track state mutations,
7
+ * while skipping on-chain-only operations like `check_preimage`,
8
+ * `deserialize_state`, `get_state_script`, `add_output`, and `add_raw_output`.
9
+ *
10
+ * Three execution modes:
11
+ *
12
+ * 1. **Lenient** (`computeNewState`, `computeNewStateAndDataOutputs`) —
13
+ * skips `assert` predicates so the SDK can pre-compute the post-state
14
+ * even when arguments wouldn't actually pass the on-chain script. This
15
+ * is the canonical use: auto-deriving `newState` for the next call.
16
+ * 2. **Strict** (`executeStrict`) — evaluates every `assert` predicate and
17
+ * throws `AssertionFailureError` (carrying the contract method + ANF
18
+ * binding name of the failing predicate) on the first false assert. Use
19
+ * for "will this call go through?" / "would the asserts hold" smoke
20
+ * tests off-chain before paying broadcast fees. Crypto built-ins
21
+ * (`checkSig`, `checkMultiSig`, `checkPreimage`) still mock-return
22
+ * `true` — only explicit `assert(...)` predicates are enforced.
23
+ * 3. **On-chain authoritative** (`executeOnChainAuthoritative`) — strict
24
+ * assert enforcement PLUS real ECDSA / SHA-256 preimage verification
25
+ * against a caller-supplied `sighash`. The signature shape requires the
26
+ * caller to provide the sighash up front, so it is impossible to invoke
27
+ * this mode accidentally without the cryptographic inputs. Use this to
28
+ * validate the exact transaction the caller intends to broadcast: a
29
+ * `checkSig(sig, pk)` only passes if the supplied DER signature
30
+ * verifies against the supplied compressed public key over the
31
+ * supplied sighash, and `checkPreimage(preimage)` only passes if
32
+ * `SHA256(SHA256(preimage)) === sighash` (the canonical BIP-143
33
+ * `OP_PUSH_TX` semantic).
34
+ */
35
+ import { Hash, Utils, PublicKey, Signature, BigNumber } from '@bsv/sdk';
36
+ // `verify` from @bsv/sdk's ECDSA module performs raw ECDSA verification
37
+ // against an already-hashed digest. We use this rather than
38
+ // `pubKey.verify(...)` (which internally sha256s its first arg) so the
39
+ // `sighash` passed by the caller is treated as the actual ECDSA digest,
40
+ // matching the on-chain CHECKSIG semantic where ECDSA verifies against
41
+ // the BIP-143 sighash directly with no extra hashing.
42
+ import { ECDSA } from '@bsv/sdk';
43
+ const ecdsaVerifyRaw = ECDSA.verify;
44
+ import { decodeScriptNumber } from './script-utils.js';
45
+ export function computeNewState(anf, methodName, currentState, args, constructorArgs = []) {
46
+ return computeNewStateAndDataOutputs(anf, methodName, currentState, args, constructorArgs).state;
47
+ }
48
+ /**
49
+ * Like {@link computeNewState} but also returns data outputs resolved
50
+ * from `this.addDataOutput(...)` and raw outputs resolved from
51
+ * `this.addRawOutput(...)` in the method body. Entries appear in
52
+ * declaration order and are what `buildCallTransaction` should emit
53
+ * between state outputs and the change output so the on-chain
54
+ * continuation-hash check passes.
55
+ */
56
+ export function computeNewStateAndDataOutputs(anf, methodName, currentState, args, constructorArgs = []) {
57
+ return runMethod(anf, methodName, currentState, args, constructorArgs, null);
58
+ }
59
+ /**
60
+ * Thrown by {@link executeStrict} on the first failing `assert` predicate.
61
+ * Carries enough context to point a developer at the exact ANF binding
62
+ * that aborted: the method being executed and the binding name (e.g.
63
+ * `assertPositive` from the source `assert(amount > 0)`).
64
+ */
65
+ export class AssertionFailureError extends Error {
66
+ methodName;
67
+ bindingName;
68
+ constructor(methodName, bindingName) {
69
+ super(`assert failed in ${methodName}: binding '${bindingName}' evaluated to false`);
70
+ this.name = 'AssertionFailureError';
71
+ this.methodName = methodName;
72
+ this.bindingName = bindingName;
73
+ }
74
+ }
75
+ /**
76
+ * Strict-mode counterpart to {@link computeNewStateAndDataOutputs}: walks
77
+ * the same ANF body but throws {@link AssertionFailureError} on the first
78
+ * `assert(predicate)` whose predicate evaluates to a falsy value. Use this
79
+ * before broadcasting a transaction to surface guard failures off-chain
80
+ * instead of relying on a node rejection. Crypto built-ins (`checkSig`,
81
+ * `checkMultiSig`, `checkPreimage`) still mock-return `true` — strict mode
82
+ * only enforces explicit `assert(...)` predicates.
83
+ */
84
+ export function executeStrict(anf, methodName, currentState, args, constructorArgs = []) {
85
+ return runMethod(anf, methodName, currentState, args, constructorArgs, {
86
+ methodName,
87
+ });
88
+ }
89
+ /**
90
+ * Like {@link executeStrict} but also performs real cryptographic
91
+ * verification of `checkSig`, `checkMultiSig`, and `checkPreimage` against
92
+ * the supplied `sighash`. Throws {@link AssertionFailureError} when any
93
+ * `assert(...)` (including the implicit one wrapping a failed crypto
94
+ * built-in) fires.
95
+ *
96
+ * The `ctx` parameter is mandatory and carries the sighash, so it is
97
+ * impossible to call this entry point accidentally without supplying the
98
+ * cryptographic inputs the verification needs.
99
+ */
100
+ export function executeOnChainAuthoritative(anf, methodName, currentState, args, constructorArgs, ctx) {
101
+ const sighash = normalizeSighash(ctx.sighash);
102
+ return runMethod(anf, methodName, currentState, args, constructorArgs, {
103
+ methodName,
104
+ realCrypto: { sighash },
105
+ });
106
+ }
107
+ function normalizeSighash(sighash) {
108
+ let bytes;
109
+ if (typeof sighash === 'string') {
110
+ bytes = Utils.toArray(sighash, 'hex');
111
+ }
112
+ else {
113
+ bytes = Array.from(sighash);
114
+ }
115
+ if (bytes.length !== 32) {
116
+ throw new Error(`executeOnChainAuthoritative: sighash must be exactly 32 bytes, got ${bytes.length}`);
117
+ }
118
+ return bytes;
119
+ }
120
+ function runMethod(anf, methodName, currentState, args, constructorArgs, strict) {
121
+ // Find the method in ANF
122
+ const method = anf.methods.find((m) => m.name === methodName && m.isPublic);
123
+ if (!method) {
124
+ throw new Error(`computeNewState: method '${methodName}' not found in ANF IR`);
125
+ }
126
+ // Initialize the environment with property values and method params
127
+ const env = {};
128
+ // Load properties: mutable fields from currentState, readonly fields
129
+ // from constructorArgs (matched by constructor param index, which excludes
130
+ // initialized properties).
131
+ // Build the constructor param index: position among non-initialized properties.
132
+ const ctorParamNames = anf.properties
133
+ .filter((p) => p.initialValue === undefined)
134
+ .map((p) => p.name);
135
+ for (const prop of anf.properties) {
136
+ if (prop.name in currentState) {
137
+ env[prop.name] = currentState[prop.name];
138
+ }
139
+ else if (prop.initialValue !== undefined) {
140
+ env[prop.name] = prop.initialValue;
141
+ }
142
+ else {
143
+ const ctorIdx = ctorParamNames.indexOf(prop.name);
144
+ if (ctorIdx >= 0 && ctorIdx < constructorArgs.length) {
145
+ env[prop.name] = constructorArgs[ctorIdx];
146
+ }
147
+ }
148
+ }
149
+ // Load method params (skip implicit ones injected by the compiler)
150
+ const implicitParams = new Set([
151
+ '_changePKH', '_changeAmount', '_newAmount', 'txPreimage',
152
+ ]);
153
+ for (const param of method.params) {
154
+ if (implicitParams.has(param.name))
155
+ continue;
156
+ if (param.name in args) {
157
+ env[param.name] = args[param.name];
158
+ }
159
+ }
160
+ // Track state mutations, data outputs, and raw outputs.
161
+ // `rawOutputs` holds entries from `add_raw_output` ANF kinds, which the
162
+ // simulator does NOT introspect (the script is caller-supplied). They
163
+ // are surfaced in the result envelope so an off-chain transaction
164
+ // builder can splice them in at the correct index.
165
+ const stateDelta = {};
166
+ const dataOutputs = [];
167
+ const rawOutputs = [];
168
+ // Ordered state-class outputs (state continuation + raw) in source order.
169
+ const outputs = [];
170
+ // Walk bindings
171
+ evalBindings(method.body, env, stateDelta, dataOutputs, rawOutputs, outputs, anf, strict);
172
+ return { state: { ...currentState, ...stateDelta }, dataOutputs, rawOutputs, outputs };
173
+ }
174
+ // ---------------------------------------------------------------------------
175
+ // Binding evaluation
176
+ // ---------------------------------------------------------------------------
177
+ function evalBindings(bindings, env, stateDelta, dataOutputs, rawOutputs, outputs, anf, strict = null,
178
+ // Per-binding raw stack bytes for byte-array-op results (& | ^ << >> ~). Keyed
179
+ // by binding name; lets a chained op read the real (possibly non-minimal)
180
+ // length of a prior op's result instead of re-minimizing its numeric value.
181
+ // `env` stays pure (decoded values) so state serialization is unaffected.
182
+ scriptBytes = {}) {
183
+ for (const binding of bindings) {
184
+ const val = evalValue(binding.value, env, stateDelta, dataOutputs, rawOutputs, outputs, anf, strict, binding.name, scriptBytes);
185
+ env[binding.name] = val;
186
+ }
187
+ }
188
+ function evalValue(value, env, stateDelta, dataOutputs, rawOutputs, outputs, anf, strict = null, bindingName = '<anonymous>', scriptBytes = {}) {
189
+ switch (value.kind) {
190
+ case 'load_param':
191
+ return env[value.name];
192
+ case 'load_prop':
193
+ return env[value.name];
194
+ case 'load_const': {
195
+ const v = value.value;
196
+ // Handle @ref: aliases (load_const with "@ref:targetName")
197
+ if (typeof v === 'string' && v.startsWith('@ref:')) {
198
+ const target = v.slice(5);
199
+ aliasScriptBytes(scriptBytes, target, bindingName);
200
+ return env[target];
201
+ }
202
+ // On-disk ANF spells every bigint as a `"<decimal>n"` STRING (see
203
+ // `jsonWithBigInt` in runar-cli's compile command) — that is the artifact
204
+ // every SDK loads with a bare `JSON.parse`. Decode it here so a const
205
+ // operand is a `bigint`, not a `string`: the byte-op paths below gate on
206
+ // `typeof !== 'string'`, so leaving it a string silently routes `<< >> &
207
+ // | ^ ~` down the ByteString branch and the SDK builds a continuation the
208
+ // deployed script disagrees with (NEW-008). Go / Rust / Zig already
209
+ // decode this shape; this makes all seven agree with the script.
210
+ //
211
+ // Unambiguous: ANF ByteString literals are hex and `n` is not a hex
212
+ // digit, so `^-?\d+n$` cannot be a bytestring.
213
+ if (typeof v === 'string' && /^-?\d+n$/.test(v)) {
214
+ return BigInt(v.slice(0, -1));
215
+ }
216
+ return v;
217
+ }
218
+ case 'bin_op': {
219
+ // Numeric byte-array ops (& | ^ << >>) thread the operands' real stack
220
+ // bytes so chained expressions match the deployed script (a shift/bitwise
221
+ // result can be non-minimal; the next length-sensitive op must see that).
222
+ // ByteString ops (result_type 'bytes' / string operands) fall through to
223
+ // evalBinOp, which keeps the minimal-operand path for everything else.
224
+ const isNumericByteOp = (value.op === '&' || value.op === '|' || value.op === '^' ||
225
+ value.op === '<<' || value.op === '>>') &&
226
+ value.result_type !== 'bytes' &&
227
+ typeof env[value.left] !== 'string' &&
228
+ typeof env[value.right] !== 'string';
229
+ if (isNumericByteOp) {
230
+ const ab = scriptBytes[value.left] ?? minimalScriptNumberBytes(toBigInt(env[value.left]));
231
+ let rb;
232
+ if (value.op === '<<' || value.op === '>>') {
233
+ // Shift count is read as a number on-chain — only `ab`'s length matters.
234
+ rb = scriptNumberShiftBytes(value.op, ab, toBigInt(env[value.right]));
235
+ }
236
+ else {
237
+ const bb = scriptBytes[value.right] ?? minimalScriptNumberBytes(toBigInt(env[value.right]));
238
+ rb = scriptNumberBitwiseBytes(value.op, ab, bb);
239
+ }
240
+ scriptBytes[bindingName] = rb;
241
+ return decodeScriptNumber(Utils.toHex(rb));
242
+ }
243
+ // NUMERIC consumption: a node decodes with fRequireMinimal=true and
244
+ // ABORTS on a non-minimal operand. Only the byte-array ops above may
245
+ // take one. `&& !isNumericByteOp` is implicit — we only reach here when
246
+ // the op is not one of them.
247
+ assertMinimalNumericOperand(scriptBytes[value.left], env[value.left], value.op);
248
+ assertMinimalNumericOperand(scriptBytes[value.right], env[value.right], value.op);
249
+ return evalBinOp(value.op, env[value.left], env[value.right], value.result_type);
250
+ }
251
+ case 'unary_op': {
252
+ if (value.op === '~' && value.result_type !== 'bytes' && typeof env[value.operand] !== 'string') {
253
+ const ab = scriptBytes[value.operand] ?? minimalScriptNumberBytes(toBigInt(env[value.operand]));
254
+ const rb = scriptNumberInvertBytes(ab);
255
+ scriptBytes[bindingName] = rb;
256
+ return decodeScriptNumber(Utils.toHex(rb));
257
+ }
258
+ assertMinimalNumericOperand(scriptBytes[value.operand], env[value.operand], value.op);
259
+ return evalUnaryOp(value.op, env[value.operand], value.result_type);
260
+ }
261
+ case 'call': {
262
+ // Strict mode: a `call(assert, x)` lowering path must enforce the
263
+ // predicate the same way the dedicated `assert` ANF node does.
264
+ if (strict && value.func === 'assert') {
265
+ const arg = env[value.args[0] ?? ''];
266
+ if (!isTruthy(arg)) {
267
+ throw new AssertionFailureError(strict.methodName, bindingName);
268
+ }
269
+ return undefined;
270
+ }
271
+ return evalCall(value.func, value.args.map((a) => env[a]), strict?.realCrypto);
272
+ }
273
+ case 'method_call':
274
+ return evalMethodCall(env, value.method, value.args.map((a) => env[a]), stateDelta, dataOutputs, rawOutputs, outputs, anf);
275
+ case 'if': {
276
+ const cond = env[value.cond];
277
+ const branch = isTruthy(cond) ? value.then : value.else;
278
+ // Create a child env for the branch
279
+ const childEnv = { ...env };
280
+ evalBindings(branch, childEnv, stateDelta, dataOutputs, rawOutputs, outputs, anf, strict, scriptBytes);
281
+ // Copy any new bindings back (the last binding is typically the branch result)
282
+ Object.assign(env, childEnv);
283
+ // Return the last binding's value from the branch
284
+ if (branch.length > 0) {
285
+ const lastName = branch[branch.length - 1].name;
286
+ aliasScriptBytes(scriptBytes, lastName, bindingName);
287
+ return childEnv[lastName];
288
+ }
289
+ return undefined;
290
+ }
291
+ case 'loop': {
292
+ const { count, body, iterVar } = value;
293
+ // Iteration `i` binds `iterVar = start + i*step` (issue #121). Older ANF
294
+ // payloads without start/step describe zero-start counting-up loops.
295
+ const start = value.start ?? 0n;
296
+ const step = BigInt(value.step ?? 1);
297
+ let lastVal;
298
+ for (let i = 0; i < count; i++) {
299
+ env[iterVar] = start + BigInt(i) * step;
300
+ const loopEnv = { ...env };
301
+ evalBindings(body, loopEnv, stateDelta, dataOutputs, rawOutputs, outputs, anf, strict, scriptBytes);
302
+ // Copy loop bindings back
303
+ Object.assign(env, loopEnv);
304
+ if (body.length > 0) {
305
+ const lastName = body[body.length - 1].name;
306
+ aliasScriptBytes(scriptBytes, lastName, bindingName);
307
+ lastVal = loopEnv[lastName];
308
+ }
309
+ }
310
+ return lastVal;
311
+ }
312
+ case 'assert': {
313
+ if (strict) {
314
+ // Marker-based skip: the auto-injected stateful-continuation
315
+ // `assert(hash256(_) === extractOutputHash(_))` carries
316
+ // `isAutoInjectedStateCheck: true` (set in
317
+ // `packages/runar-compiler/src/passes/04-anf-lower.ts`). The
318
+ // on-chain VM is authoritative for that check; off-chain we
319
+ // have no realistic continuation hash. Developer-written
320
+ // covenant asserts with the identical IR shape carry no
321
+ // marker and ARE enforced (see BUG-002).
322
+ if (value.isAutoInjectedStateCheck === true) {
323
+ return undefined;
324
+ }
325
+ const predicate = env[value.value];
326
+ if (!isTruthy(predicate)) {
327
+ throw new AssertionFailureError(strict.methodName, bindingName);
328
+ }
329
+ }
330
+ // Lenient: skip asserts (the on-chain script handles enforcement)
331
+ return undefined;
332
+ }
333
+ case 'update_prop': {
334
+ const newVal = env[value.value];
335
+ env[value.name] = newVal;
336
+ stateDelta[value.name] = newVal;
337
+ return undefined;
338
+ }
339
+ case 'add_output': {
340
+ // Extract implicit state changes from stateValues array.
341
+ // stateValues[i] maps to the i-th mutable property (declaration order).
342
+ if (anf && value.stateValues && value.stateValues.length > 0) {
343
+ const mutableProps = anf.properties.filter((p) => !p.readonly);
344
+ for (let i = 0; i < value.stateValues.length && i < mutableProps.length; i++) {
345
+ const propName = mutableProps[i].name;
346
+ const ref = value.stateValues[i];
347
+ const newVal = env[ref];
348
+ env[propName] = newVal;
349
+ stateDelta[propName] = newVal;
350
+ }
351
+ }
352
+ // Record the state continuation output in source order (finding G1): a
353
+ // method may interleave raw outputs around it, and the on-chain covenant
354
+ // folds them into hashOutputs in exactly this order.
355
+ outputs.push({ kind: 'state', satoshis: toBigInt(env[value.satoshis]) });
356
+ return undefined;
357
+ }
358
+ case 'add_data_output': {
359
+ // Resolve the two arg refs from env and record the data output.
360
+ const sats = toBigInt(env[value.satoshis]);
361
+ const script = env[value.scriptBytes];
362
+ dataOutputs.push({
363
+ satoshis: sats,
364
+ script: typeof script === 'string' ? script : '',
365
+ });
366
+ return undefined;
367
+ }
368
+ case 'add_raw_output': {
369
+ // `addRawOutput(satoshis, scriptBytes)`. The simulator does not
370
+ // introspect the script bytes (they're caller-supplied raw locking
371
+ // script); it simply forwards them in the result envelope so an
372
+ // off-chain transaction builder can emit the output at the correct
373
+ // index. Crypto built-ins remain mocked even in strict mode.
374
+ const sats = toBigInt(env[value.satoshis]);
375
+ const script = env[value.scriptBytes];
376
+ rawOutputs.push({
377
+ satoshis: sats,
378
+ script: typeof script === 'string' ? script : '',
379
+ });
380
+ // Also record in the ordered state-class output list so a transaction
381
+ // builder can emit it at the correct source-order index (finding G1).
382
+ outputs.push({
383
+ kind: 'raw',
384
+ satoshis: sats,
385
+ script: typeof script === 'string' ? script : '',
386
+ });
387
+ return undefined;
388
+ }
389
+ // On-chain-only operations — skip in simulation. These ANF kinds are
390
+ // markers consumed by the codegen, not by the off-chain interpreter.
391
+ case 'check_preimage':
392
+ case 'deserialize_state':
393
+ case 'get_state_script':
394
+ return undefined;
395
+ default:
396
+ return undefined;
397
+ }
398
+ }
399
+ // ---------------------------------------------------------------------------
400
+ // Script-number bitwise / shift semantics (byte-array ops, NOT numeric)
401
+ // ---------------------------------------------------------------------------
402
+ //
403
+ // OP_AND/OP_OR/OP_XOR/OP_INVERT/OP_LSHIFT/OP_RSHIFT operate on the RAW BYTES
404
+ // of the operands' minimal script-number encoding, not on their numeric
405
+ // value (spec/opcodes.md). AND/OR/XOR require equal-length operands and
406
+ // fail otherwise; shifts treat the byte array as a big-endian bit string
407
+ // and preserve its length. This reproduces EXACTLY what
408
+ // packages/runar-testing/src/vm/utils.ts's
409
+ // scriptNumberBitwise/scriptNumberInvert/scriptNumberShift do, so the SDK's
410
+ // off-chain state derivation agrees with the deployed script byte-for-byte.
411
+ // runar-sdk does not depend on runar-testing, so this is a local port using
412
+ // this package's own script-number decoder (`decodeScriptNumber` from
413
+ // ./script-utils.js). Note: `encodeScriptNumber` in ./contract.js is NOT
414
+ // reusable here — it returns the full *script push* encoding (with
415
+ // OP_0/OP_1..OP_16/OP_1NEGATE shortcuts), not the raw minimal bytes the
416
+ // bitwise/shift opcodes operate on, so `minimalScriptNumberBytes` below
417
+ // re-derives just the raw-byte core of that algorithm.
418
+ /** Minimal sign-magnitude bytes of a script-number-valued bigint
419
+ * (little-endian, no push-opcode wrapping). */
420
+ /**
421
+ * Mirror the on-chain minimal-encoding rule for a NUMERIC operand.
422
+ *
423
+ * Only values produced by the byte-array ops carry threaded `scriptBytes`, and
424
+ * only those can be non-minimal (`1 >> 1` is `[0x00]`, whose minimal encoding
425
+ * is EMPTY). Everything else is minimal by construction, so this is a no-op.
426
+ *
427
+ * Without it this interpreter re-minimises such a value and reports a spend
428
+ * valid that the deployed covenant aborts on — and since
429
+ * `RunarContract.prepareCall` uses this interpreter to compute the
430
+ * continuation state, it would build a broadcast against a state no node
431
+ * accepts.
432
+ */
433
+ function assertMinimalNumericOperand(bytes, value, op) {
434
+ if (bytes === undefined || typeof value !== 'bigint')
435
+ return;
436
+ const minimal = minimalScriptNumberBytes(value);
437
+ if (bytes.length === minimal.length && bytes.every((b, i) => b === minimal[i]))
438
+ return;
439
+ throw new Error(`non-minimally encoded script number consumed by '${op}': stack bytes [` +
440
+ bytes.map((b) => b.toString(16).padStart(2, '0')).join(' ') +
441
+ `] decode to ${value}, whose minimal encoding is [` +
442
+ minimal.map((b) => b.toString(16).padStart(2, '0')).join(' ') +
443
+ `]. A node aborts here.`);
444
+ }
445
+ function minimalScriptNumberBytes(n) {
446
+ if (n === 0n)
447
+ return [];
448
+ const negative = n < 0n;
449
+ let abs = negative ? -n : n;
450
+ const bytes = [];
451
+ while (abs > 0n) {
452
+ bytes.push(Number(abs & 0xffn));
453
+ abs >>= 8n;
454
+ }
455
+ const last = bytes[bytes.length - 1];
456
+ if (last & 0x80) {
457
+ bytes.push(negative ? 0x80 : 0x00);
458
+ }
459
+ else if (negative) {
460
+ bytes[bytes.length - 1] = last | 0x80;
461
+ }
462
+ return bytes;
463
+ }
464
+ // The *Bytes helpers operate on RAW stack bytes (the exact byte array a value
465
+ // would occupy on the deployed script's stack), NOT a value's minimal encoding.
466
+ // This matters for CHAINED expressions: a shift/bitwise RESULT can be a
467
+ // non-minimal byte array (e.g. `2 << 8` leaves a 1-byte 0x00), and feeding it to
468
+ // a length-sensitive `& | ^`/shift must see that real length to agree with the
469
+ // deployed script. The interpreter threads these bytes via a per-binding side
470
+ // map (see `evalBindings`); values from other sources are minimal on-chain.
471
+ /**
472
+ * Carry a binding's raw stack bytes across an ALIAS — a binding whose value IS
473
+ * another binding's slot: the `load_const "@ref:<name>"` every local rebind
474
+ * lowers to, an `if` adopting its taken arm's last value, a `loop` adopting its
475
+ * body's. Without this, a chained length-sensitive op re-minimises the aliased
476
+ * value and disagrees with the deployed script (NEW-006: `(4n ^ 4n)` is a
477
+ * 1-byte `0x00` on the stack but `[]` when re-minimised from `0n`).
478
+ *
479
+ * Mirrors `05-stack-lower.ts`, which carries its `rawSlots` marker across the
480
+ * same two constructs.
481
+ *
482
+ * CLEARS when the source has no entry: the alias target is a freshly pushed,
483
+ * minimal value, so a stale entry left by an earlier binding of the SAME name
484
+ * (`let m0 = 4n ^ 4n; m0 = 300n;`) would otherwise be read as this slot's width.
485
+ */
486
+ function aliasScriptBytes(scriptBytes, from, to) {
487
+ const bytes = scriptBytes[from];
488
+ if (bytes !== undefined) {
489
+ scriptBytes[to] = bytes;
490
+ }
491
+ else {
492
+ delete scriptBytes[to];
493
+ }
494
+ }
495
+ /** OP_AND/OP_OR/OP_XOR on raw stack bytes. Throws on length mismatch. */
496
+ function scriptNumberBitwiseBytes(op, av, bv) {
497
+ if (av.length !== bv.length) {
498
+ const name = op === '&' ? 'OP_AND' : op === '|' ? 'OP_OR' : 'OP_XOR';
499
+ throw new Error(`${name}: operands must be same length`);
500
+ }
501
+ const result = new Array(av.length);
502
+ for (let i = 0; i < av.length; i++) {
503
+ const x = av[i];
504
+ const y = bv[i];
505
+ result[i] = op === '&' ? x & y : op === '|' ? x | y : x ^ y;
506
+ }
507
+ return result;
508
+ }
509
+ /** OP_INVERT: flip every bit of the operand's raw stack bytes (length-preserving). */
510
+ function scriptNumberInvertBytes(av) {
511
+ return av.map((b) => ~b & 0xff);
512
+ }
513
+ /** OP_LSHIFT/OP_RSHIFT on raw stack bytes as a big-endian bit string, preserving
514
+ * byte length. `shift` is the numeric shift count (read as a number on-chain,
515
+ * so only `val`'s bytes are length-significant). Negative shifts fail. */
516
+ function scriptNumberShiftBytes(op, val, shift) {
517
+ if (shift < 0n) {
518
+ throw new Error(op === '<<' ? 'OP_LSHIFT: negative shift' : 'OP_RSHIFT: negative shift');
519
+ }
520
+ const n = Number(shift);
521
+ if (val.length === 0 || n === 0)
522
+ return val.slice();
523
+ let num = 0n;
524
+ for (let i = 0; i < val.length; i++)
525
+ num = (num << 8n) | BigInt(val[i]);
526
+ if (op === '<<') {
527
+ const bitLen = BigInt(val.length * 8);
528
+ num = (num << BigInt(n)) & ((1n << bitLen) - 1n);
529
+ }
530
+ else {
531
+ num >>= BigInt(n);
532
+ }
533
+ const result = new Array(val.length);
534
+ for (let i = val.length - 1; i >= 0; i--) {
535
+ result[i] = Number(num & 0xffn);
536
+ num >>= 8n;
537
+ }
538
+ return result;
539
+ }
540
+ // bigint -> bigint wrappers (minimal operands) — used by the single-op
541
+ // truth-table tests. The interpreter uses the *Bytes helpers directly so it can
542
+ // thread non-minimal chained intermediates via the side map.
543
+ function scriptNumberBitwise(op, a, b) {
544
+ return decodeScriptNumber(Utils.toHex(scriptNumberBitwiseBytes(op, minimalScriptNumberBytes(a), minimalScriptNumberBytes(b))));
545
+ }
546
+ function scriptNumberInvert(a) {
547
+ return decodeScriptNumber(Utils.toHex(scriptNumberInvertBytes(minimalScriptNumberBytes(a))));
548
+ }
549
+ function scriptNumberShift(op, a, shift) {
550
+ return decodeScriptNumber(Utils.toHex(scriptNumberShiftBytes(op, minimalScriptNumberBytes(a), shift)));
551
+ }
552
+ // ---------------------------------------------------------------------------
553
+ // Binary operations
554
+ // ---------------------------------------------------------------------------
555
+ function evalBinOp(op, left, right, resultType) {
556
+ if (resultType === 'bytes' || (typeof left === 'string' && typeof right === 'string')) {
557
+ return evalBytesBinOp(op, String(left ?? ''), String(right ?? ''));
558
+ }
559
+ const l = toBigInt(left);
560
+ const r = toBigInt(right);
561
+ switch (op) {
562
+ case '+': return l + r;
563
+ case '-': return l - r;
564
+ case '*': return l * r;
565
+ case '/': return r === 0n ? 0n : l / r;
566
+ case '%': return r === 0n ? 0n : l % r;
567
+ case '==':
568
+ case '===': return l === r;
569
+ case '!=':
570
+ case '!==': return l !== r;
571
+ case '<': return l < r;
572
+ case '<=': return l <= r;
573
+ case '>': return l > r;
574
+ case '>=': return l >= r;
575
+ case '&&': return isTruthy(left) && isTruthy(right);
576
+ case '||': return isTruthy(left) || isTruthy(right);
577
+ case '&': return scriptNumberBitwise('&', l, r);
578
+ case '|': return scriptNumberBitwise('|', l, r);
579
+ case '^': return scriptNumberBitwise('^', l, r);
580
+ case '<<': return scriptNumberShift('<<', l, r);
581
+ case '>>': return scriptNumberShift('>>', l, r);
582
+ default: return 0n;
583
+ }
584
+ }
585
+ function evalBytesBinOp(op, left, right) {
586
+ switch (op) {
587
+ case '+': // cat
588
+ return left + right;
589
+ case '==':
590
+ case '===':
591
+ return left === right;
592
+ case '!=':
593
+ case '!==':
594
+ return left !== right;
595
+ default:
596
+ return '';
597
+ }
598
+ }
599
+ // ---------------------------------------------------------------------------
600
+ // Unary operations
601
+ // ---------------------------------------------------------------------------
602
+ function evalUnaryOp(op, operand, resultType) {
603
+ if (resultType === 'bytes') {
604
+ // Bitwise NOT on bytes
605
+ if (op === '~') {
606
+ const hex = String(operand ?? '');
607
+ const bytes = Utils.toArray(hex, 'hex');
608
+ for (let i = 0; i < bytes.length; i++)
609
+ bytes[i] = ~bytes[i] & 0xff;
610
+ return Utils.toHex(bytes);
611
+ }
612
+ return operand;
613
+ }
614
+ const val = toBigInt(operand);
615
+ switch (op) {
616
+ case '-': return -val;
617
+ case '!': return !isTruthy(operand);
618
+ case '~': return scriptNumberInvert(val);
619
+ default: return val;
620
+ }
621
+ }
622
+ // ---------------------------------------------------------------------------
623
+ // Built-in function calls
624
+ // ---------------------------------------------------------------------------
625
+ function evalCall(func, args, realCrypto) {
626
+ switch (func) {
627
+ // Crypto — mocked unless real-crypto context is present.
628
+ case 'checkSig': {
629
+ if (!realCrypto)
630
+ return true;
631
+ return verifyEcdsa(args[0], args[1], realCrypto.sighash);
632
+ }
633
+ case 'checkMultiSig': {
634
+ if (!realCrypto)
635
+ return true;
636
+ return verifyMultiSig(args[0], args[1], realCrypto.sighash);
637
+ }
638
+ case 'checkPreimage': {
639
+ if (!realCrypto)
640
+ return true;
641
+ return verifyPreimage(args[0], realCrypto.sighash);
642
+ }
643
+ // Crypto — real hashes
644
+ case 'sha256': return hashFn('sha256', args[0]);
645
+ case 'hash256': return hashFn('hash256', args[0]);
646
+ case 'hash160': return hashFn('hash160', args[0]);
647
+ case 'ripemd160': return hashFn('ripemd160', args[0]);
648
+ // Assert — skip (on-chain handles it)
649
+ case 'assert': return undefined;
650
+ // Byte operations
651
+ case 'num2bin': {
652
+ const n = toBigInt(args[0]);
653
+ const len = Number(toBigInt(args[1]));
654
+ return num2binHex(n, len);
655
+ }
656
+ case 'bin2num': {
657
+ return bin2numBigInt(String(args[0] ?? ''));
658
+ }
659
+ case 'cat': {
660
+ return String(args[0] ?? '') + String(args[1] ?? '');
661
+ }
662
+ case 'substr': {
663
+ const hex = String(args[0] ?? '');
664
+ const start = Number(toBigInt(args[1]));
665
+ const len = Number(toBigInt(args[2]));
666
+ return hex.slice(start * 2, (start + len) * 2);
667
+ }
668
+ case 'reverseBytes': {
669
+ const hex = String(args[0] ?? '');
670
+ const pairs = [];
671
+ for (let i = 0; i < hex.length; i += 2)
672
+ pairs.push(hex.slice(i, i + 2));
673
+ return pairs.reverse().join('');
674
+ }
675
+ case 'len': {
676
+ const hex = String(args[0] ?? '');
677
+ return BigInt(hex.length / 2);
678
+ }
679
+ // Math builtins
680
+ case 'abs': return toBigInt(args[0]) < 0n ? -toBigInt(args[0]) : toBigInt(args[0]);
681
+ case 'min': return toBigInt(args[0]) < toBigInt(args[1]) ? toBigInt(args[0]) : toBigInt(args[1]);
682
+ case 'max': return toBigInt(args[0]) > toBigInt(args[1]) ? toBigInt(args[0]) : toBigInt(args[1]);
683
+ case 'within': {
684
+ const x = toBigInt(args[0]);
685
+ return x >= toBigInt(args[1]) && x < toBigInt(args[2]);
686
+ }
687
+ case 'safediv': {
688
+ const d = toBigInt(args[1]);
689
+ return d === 0n ? 0n : toBigInt(args[0]) / d;
690
+ }
691
+ case 'safemod': {
692
+ const d = toBigInt(args[1]);
693
+ return d === 0n ? 0n : toBigInt(args[0]) % d;
694
+ }
695
+ case 'clamp': {
696
+ const v = toBigInt(args[0]);
697
+ const lo = toBigInt(args[1]);
698
+ const hi = toBigInt(args[2]);
699
+ return v < lo ? lo : v > hi ? hi : v;
700
+ }
701
+ case 'sign': {
702
+ const v = toBigInt(args[0]);
703
+ return v > 0n ? 1n : v < 0n ? -1n : 0n;
704
+ }
705
+ case 'pow': {
706
+ const base = toBigInt(args[0]);
707
+ const exp = toBigInt(args[1]);
708
+ if (exp < 0n)
709
+ return 0n;
710
+ let result = 1n;
711
+ for (let i = 0n; i < exp; i++)
712
+ result *= base;
713
+ return result;
714
+ }
715
+ case 'sqrt': {
716
+ const v = toBigInt(args[0]);
717
+ if (v <= 0n)
718
+ return 0n;
719
+ let x = v;
720
+ let y = (x + 1n) / 2n;
721
+ while (y < x) {
722
+ x = y;
723
+ y = (x + v / x) / 2n;
724
+ }
725
+ return x;
726
+ }
727
+ case 'gcd': {
728
+ let a = toBigInt(args[0]);
729
+ let b = toBigInt(args[1]);
730
+ if (a < 0n)
731
+ a = -a;
732
+ if (b < 0n)
733
+ b = -b;
734
+ while (b !== 0n) {
735
+ const t = b;
736
+ b = a % b;
737
+ a = t;
738
+ }
739
+ return a;
740
+ }
741
+ case 'divmod': {
742
+ const a = toBigInt(args[0]);
743
+ const b = toBigInt(args[1]);
744
+ if (b === 0n)
745
+ return 0n;
746
+ // Returns quotient; in ANF the second result is in a separate binding
747
+ return a / b;
748
+ }
749
+ case 'log2': {
750
+ const v = toBigInt(args[0]);
751
+ if (v <= 0n)
752
+ return 0n;
753
+ let bits = 0n;
754
+ let x = v;
755
+ while (x > 1n) {
756
+ x >>= 1n;
757
+ bits++;
758
+ }
759
+ return bits;
760
+ }
761
+ case 'bool': return isTruthy(args[0]) ? 1n : 0n;
762
+ case 'mulDiv': {
763
+ return (toBigInt(args[0]) * toBigInt(args[1])) / toBigInt(args[2]);
764
+ }
765
+ case 'percentOf': {
766
+ return (toBigInt(args[0]) * toBigInt(args[1])) / 10000n;
767
+ }
768
+ // Baby Bear (p = 2^31 - 2^27 + 1). Needed so StateCovenant.advanceState
769
+ // can auto-derive continuation state instead of failing closed.
770
+ case 'bbFieldMul': {
771
+ const p = 2013265921n;
772
+ return (toBigInt(args[0]) * toBigInt(args[1])) % p;
773
+ }
774
+ case 'merkleRootSha256': {
775
+ let current = String(args[0] ?? '');
776
+ const proof = String(args[1] ?? '');
777
+ const index = toBigInt(args[2]);
778
+ const depth = Number(toBigInt(args[3]));
779
+ for (let i = 0; i < depth; i++) {
780
+ const sibling = proof.slice(i * 64, (i + 1) * 64);
781
+ const bit = (index >> BigInt(i)) & 1n;
782
+ const preimage = bit === 1n ? sibling + current : current + sibling;
783
+ current = hashFn('sha256', preimage);
784
+ }
785
+ return current;
786
+ }
787
+ // Preimage intrinsics — return dummy values in simulation
788
+ case 'extractOutputHash':
789
+ case 'extractAmount':
790
+ return '00'.repeat(32);
791
+ case 'extractLocktime':
792
+ return 0n;
793
+ // Dummy nVersion. Peers (runar-py/rs/zig/rb/java) already return 1;
794
+ // runar-lang runtime does too. Without this arm `prepareCall` fails
795
+ // closed on PreimageExtractors.tick (NEW-006).
796
+ case 'extractVersion':
797
+ return 1n;
798
+ // W7: this arm was MISSING while all six peer SDK interpreters
799
+ // (runar-rs `anf_interpreter.rs`, runar-py, runar-zig — whose comment
800
+ // claims it is mirroring this file — and runar-rb) already returned
801
+ // `0xfffffffe` for it. The omission made every stateful call to a method
802
+ // that reads nSequence fail closed in the TypeScript SDK alone, which is
803
+ // exactly the finality guard #131 tells authors to write. `0xfffffffe` is
804
+ // the SDK's own non-final default (`resolveInputSequence`), so it is the
805
+ // honest off-chain answer for the same reason `extractLocktime` answers 0:
806
+ // the real value is a property of a transaction that does not exist yet.
807
+ case 'extractSequence':
808
+ return 0xfffffffen;
809
+ // Dummy 32-byte prevouts hash. The real hash is a property of a
810
+ // transaction that does not exist yet during off-chain state derivation;
811
+ // lenient mode skips the on-chain assert that binds it. Without this arm
812
+ // `prepareCall` fails closed on every `merge` (NEW-006), so no SDK caller
813
+ // can build the call at all.
814
+ case 'extractHashPrevouts':
815
+ case 'extractHashSequence':
816
+ return '00'.repeat(32);
817
+ case 'extractOutpoint':
818
+ return '00'.repeat(36);
819
+ // Dummy empty scriptCode. The real bytes are a property of a transaction
820
+ // that does not exist yet during off-chain state derivation; lenient mode
821
+ // skips the on-chain asserts that bind them. Without this arm `prepareCall`
822
+ // fails closed on every `merge` that reads extractScriptCode (NEW-006).
823
+ case 'extractScriptCode':
824
+ return '';
825
+ // A-3, KNOWN REMAINING GAP: `extractSigHashType` IS a real runar-lang
826
+ // builtin (`preimage.ts`), unlike its three siblings above it has no
827
+ // dummy value here, and `04-anf-lower.ts` emits it on every stateful
828
+ // method. Measured across this package's suite plus `examples/` and
829
+ // `packages/runar-testing`: 317 evaluations, every one of them consumed
830
+ // by an `assert` that lenient mode skips, so the `undefined` never
831
+ // reaches a state field. Refusing it would break every stateful call for
832
+ // a value nothing reads; giving it a dummy would change strict-mode
833
+ // assert outcomes. Left as-is and pinned by name in
834
+ // `a3-unmodelled-builtin-fails-closed.test.ts` so it is visible rather
835
+ // than silent.
836
+ case 'extractSigHashType':
837
+ return undefined;
838
+ // A-3: compiler-synthesized pseudo-functions, NOT runar-lang builtins —
839
+ // `04-anf-lower.ts` emits these `kind:'call'` nodes itself and none of
840
+ // them is exported by `runar-lang`. They are covenant / constructor
841
+ // scaffolding whose result the SDK reconstructs on its own (the state
842
+ // continuation, the change output, the `super(...)` call), so
843
+ // `undefined` is the correct off-chain answer and must stay
844
+ // non-throwing. `computeStateOutput` runs on every stateful call in the
845
+ // measured corpus; a blanket throw would have broken all of them.
846
+ //
847
+ // `__array_access` (ByteString `data[i]`) is deliberately NOT here: it is
848
+ // a value-producing operation this interpreter genuinely does not model,
849
+ // which is the same defect as an unmodelled builtin, so it falls through
850
+ // to the refusal below.
851
+ //
852
+ // Cross-tier check: every `"func"` appearing in `conformance/
853
+ // anf-interpreter/programs` is either modelled above or one of these four
854
+ // (`buildChangeOutput` and `super` occur there and nowhere else in the
855
+ // measured corpus), so the cross-interpreter parity fixtures still agree
856
+ // with the six peer interpreters after this change.
857
+ case 'computeStateOutput':
858
+ case 'buildChangeOutput':
859
+ case 'super':
860
+ return undefined;
861
+ default:
862
+ // A-3 / NEW-006: FAIL CLOSED. This used to `return undefined`, which
863
+ // made the `try/catch` guard in `contract.ts` structurally unable to
864
+ // fire for the 76 of runar-lang's 105 builtins this interpreter does
865
+ // not model — every EC op, every NIST P-256/P-384 op, all six SLH-DSA
866
+ // verifies, Rabin, `left`/`right`/`split`, `sha256Compress`,
867
+ // `merkleRootSha256`. `contract.ts` then spreads the result over the
868
+ // real state and the `undefined` wins, so a `boolean` state field fed
869
+ // by a signature verifier silently became false and the eventual error
870
+ // pointed at the covenant instead of at this gap. Naming the builtin
871
+ // here is the difference between a five-minute diagnosis and a
872
+ // multi-hour one.
873
+ throw new Error(`ANF interpreter does not model the builtin '${func}'. Its result cannot be ` +
874
+ 'computed off-chain, so any state field or data output derived from it would ' +
875
+ 'be silently wrong. Pass an explicit `newState` to `call()` if you know the ' +
876
+ "post-state, or add an arm for this builtin to `evalCall` in " +
877
+ 'packages/runar-sdk/src/anf-interpreter.ts.');
878
+ }
879
+ }
880
+ function evalMethodCall(callerEnv, methodName, args, stateDelta, dataOutputs, rawOutputs, outputs, anf) {
881
+ // Private method calls appear in the ANF with their bodies available
882
+ // in anf.methods. Execute the method body to compute its return value.
883
+ if (anf) {
884
+ const method = anf.methods.find((m) => m.name === methodName && !m.isPublic);
885
+ if (method) {
886
+ // Build env for the private method: copy property values for load_prop
887
+ const methodEnv = {};
888
+ for (const prop of anf.properties) {
889
+ if (prop.name in callerEnv) {
890
+ methodEnv[prop.name] = callerEnv[prop.name];
891
+ }
892
+ }
893
+ // Map method params to passed args
894
+ for (let i = 0; i < method.params.length && i < args.length; i++) {
895
+ methodEnv[method.params[i].name] = args[i];
896
+ }
897
+ // Execute the method body — pass real stateDelta so update_prop
898
+ // mutations in private methods are captured
899
+ evalBindings(method.body, methodEnv, stateDelta, dataOutputs, rawOutputs, outputs, anf);
900
+ // Propagate property changes back to the caller's env
901
+ for (const prop of anf.properties) {
902
+ if (prop.name in methodEnv) {
903
+ callerEnv[prop.name] = methodEnv[prop.name];
904
+ }
905
+ }
906
+ // Return the last binding's value (the method's return value)
907
+ if (method.body.length > 0) {
908
+ return methodEnv[method.body[method.body.length - 1].name];
909
+ }
910
+ return undefined;
911
+ }
912
+ }
913
+ return undefined;
914
+ }
915
+ // ---------------------------------------------------------------------------
916
+ // Hash helpers
917
+ // ---------------------------------------------------------------------------
918
+ function hashFn(name, input) {
919
+ const hex = String(input ?? '');
920
+ const bytes = Utils.toArray(hex, 'hex');
921
+ let result;
922
+ switch (name) {
923
+ case 'sha256':
924
+ result = Hash.sha256(bytes);
925
+ break;
926
+ case 'hash256':
927
+ result = Hash.hash256(bytes);
928
+ break;
929
+ case 'hash160':
930
+ result = Hash.hash160(bytes);
931
+ break;
932
+ case 'ripemd160':
933
+ result = Hash.ripemd160(bytes);
934
+ break;
935
+ }
936
+ return Utils.toHex(result);
937
+ }
938
+ // ---------------------------------------------------------------------------
939
+ // Numeric helpers
940
+ // ---------------------------------------------------------------------------
941
+ function toBigInt(v) {
942
+ if (typeof v === 'bigint')
943
+ return v;
944
+ if (typeof v === 'number')
945
+ return BigInt(v);
946
+ if (typeof v === 'boolean')
947
+ return v ? 1n : 0n;
948
+ if (typeof v === 'string') {
949
+ // Handle "42n" format from JSON
950
+ if (/^-?\d+n$/.test(v))
951
+ return BigInt(v.slice(0, -1));
952
+ // Handle plain numeric strings
953
+ if (/^-?\d+$/.test(v))
954
+ return BigInt(v);
955
+ return 0n;
956
+ }
957
+ return 0n;
958
+ }
959
+ function isTruthy(v) {
960
+ if (typeof v === 'boolean')
961
+ return v;
962
+ if (typeof v === 'bigint')
963
+ return v !== 0n;
964
+ if (typeof v === 'number')
965
+ return v !== 0;
966
+ if (typeof v === 'string')
967
+ return v !== '' && v !== '0' && v !== 'false';
968
+ return false;
969
+ }
970
+ // ---------------------------------------------------------------------------
971
+ // Byte encoding helpers
972
+ // ---------------------------------------------------------------------------
973
+ /**
974
+ * `num2bin(n, byteLen)` — exactly what OP_NUM2BIN computes (NEW-013).
975
+ *
976
+ * The order of the two steps below is load-bearing. This function used to set
977
+ * the sign bit on the last MAGNITUDE byte and pad zeros AFTER it, so
978
+ * `num2bin(-1n, 2n)` produced `8100` while the script produces `0180`. The
979
+ * result is the bytes the SDK puts in the call transaction, so the wrong order
980
+ * built continuations the deployed script rejects — and six of the seven SDKs
981
+ * shared the mistake, which is why tier-vs-tier parity never caught it.
982
+ *
983
+ * The engine (`@bsv/sdk` `Spend`, and BSV consensus) pads FIRST and then puts
984
+ * the sign bit on the new most-significant byte.
985
+ */
986
+ function num2binHex(n, byteLen) {
987
+ // 1. Minimal BSV script-number encoding: little-endian magnitude with the
988
+ // sign in bit 7 of the top byte, growing one byte when magnitude data
989
+ // already occupies that bit.
990
+ const negative = n < 0n;
991
+ let abs = negative ? -n : n;
992
+ const bytes = [];
993
+ while (abs > 0n) {
994
+ bytes.push(Number(abs & 0xffn));
995
+ abs >>= 8n;
996
+ }
997
+ if (bytes.length > 0) {
998
+ if ((bytes[bytes.length - 1] & 0x80) !== 0) {
999
+ bytes.push(negative ? 0x80 : 0x00);
1000
+ }
1001
+ else if (negative) {
1002
+ bytes[bytes.length - 1] |= 0x80;
1003
+ }
1004
+ }
1005
+ // 2a. Field too narrow for the value: OP_NUM2BIN rejects this outright
1006
+ // ("impossible encoding"). The interpreter keeps its historical
1007
+ // truncation rather than growing a new failure mode here; an equal-length
1008
+ // encoding is already final and needs no sign-bit move.
1009
+ if (bytes.length >= byteLen) {
1010
+ return bytes
1011
+ .slice(0, byteLen)
1012
+ .map((b) => b.toString(16).padStart(2, '0'))
1013
+ .join('');
1014
+ }
1015
+ // 2b. Padded: lift the sign bit off the magnitude, zero-extend, and re-apply
1016
+ // it to the byte that is now most significant.
1017
+ let signBit = 0;
1018
+ if (bytes.length > 0) {
1019
+ signBit = bytes[bytes.length - 1] & 0x80;
1020
+ bytes[bytes.length - 1] &= 0x7f;
1021
+ }
1022
+ while (bytes.length < byteLen)
1023
+ bytes.push(0x00);
1024
+ if (signBit !== 0)
1025
+ bytes[byteLen - 1] |= 0x80;
1026
+ return bytes.map((b) => b.toString(16).padStart(2, '0')).join('');
1027
+ }
1028
+ // ---------------------------------------------------------------------------
1029
+ // Real ECDSA / preimage verification (used by executeOnChainAuthoritative)
1030
+ // ---------------------------------------------------------------------------
1031
+ function toByteArray(v) {
1032
+ if (typeof v === 'string') {
1033
+ if (v.length % 2 !== 0)
1034
+ return null;
1035
+ if (!/^[0-9a-fA-F]*$/.test(v))
1036
+ return null;
1037
+ return Utils.toArray(v, 'hex');
1038
+ }
1039
+ if (v instanceof Uint8Array)
1040
+ return Array.from(v);
1041
+ if (Array.isArray(v))
1042
+ return v;
1043
+ return null;
1044
+ }
1045
+ function parseDerSignatureMaybeWithSighashByte(bytes) {
1046
+ if (bytes.length < 8 || bytes[0] !== 0x30)
1047
+ return null;
1048
+ const declared = bytes[1];
1049
+ const expected = declared + 2;
1050
+ let pure;
1051
+ if (bytes.length === expected) {
1052
+ pure = bytes;
1053
+ }
1054
+ else if (bytes.length === expected + 1) {
1055
+ // Drop trailing sighash type byte (e.g. 0x41 SIGHASH_ALL|FORKID).
1056
+ pure = bytes.slice(0, expected);
1057
+ }
1058
+ else {
1059
+ pure = bytes;
1060
+ }
1061
+ try {
1062
+ return Signature.fromDER(pure);
1063
+ }
1064
+ catch {
1065
+ return null;
1066
+ }
1067
+ }
1068
+ function verifyEcdsa(sigVal, pkVal, sighash) {
1069
+ const sigBytes = toByteArray(sigVal);
1070
+ const pkBytes = toByteArray(pkVal);
1071
+ if (!sigBytes || !pkBytes)
1072
+ return false;
1073
+ try {
1074
+ const pubKey = PublicKey.fromDER(pkBytes);
1075
+ const sig = parseDerSignatureMaybeWithSighashByte(sigBytes);
1076
+ if (!sig)
1077
+ return false;
1078
+ // Raw ECDSA verify: treat `sighash` as the message digest directly,
1079
+ // matching the on-chain CHECKSIG semantic (and the cross-tier real-
1080
+ // crypto fixture convention). `pubKey.verify(msg, sig)` would re-hash
1081
+ // `msg` with sha256 internally, which makes the TS verification
1082
+ // disagree with every other SDK that simply ECDSA-verifies the
1083
+ // supplied 32-byte digest.
1084
+ const msgBN = new BigNumber(sighash);
1085
+ return ecdsaVerifyRaw(msgBN, sig, pubKey);
1086
+ }
1087
+ catch {
1088
+ return false;
1089
+ }
1090
+ }
1091
+ function verifyMultiSig(sigsVal, pksVal, sighash) {
1092
+ if (!Array.isArray(sigsVal) || !Array.isArray(pksVal))
1093
+ return false;
1094
+ if (sigsVal.length > pksVal.length)
1095
+ return false;
1096
+ let pkIdx = 0;
1097
+ for (const sig of sigsVal) {
1098
+ let matched = false;
1099
+ while (pkIdx < pksVal.length) {
1100
+ const ok = verifyEcdsa(sig, pksVal[pkIdx], sighash);
1101
+ pkIdx++;
1102
+ if (ok) {
1103
+ matched = true;
1104
+ break;
1105
+ }
1106
+ }
1107
+ if (!matched)
1108
+ return false;
1109
+ }
1110
+ return true;
1111
+ }
1112
+ /**
1113
+ * BIP-143 / OP_PUSH_TX semantic: the on-chain check is
1114
+ * `hash256(preimage) === sighash`. We replicate that: the supplied preimage
1115
+ * is the serialised BIP-143 message; its double-SHA-256 must equal the
1116
+ * sighash the caller provided to {@link executeOnChainAuthoritative}.
1117
+ */
1118
+ function verifyPreimage(preimageVal, sighash) {
1119
+ const preBytes = toByteArray(preimageVal);
1120
+ if (!preBytes)
1121
+ return false;
1122
+ const computed = Hash.hash256(preBytes);
1123
+ if (computed.length !== sighash.length)
1124
+ return false;
1125
+ for (let i = 0; i < sighash.length; i++) {
1126
+ if (computed[i] !== sighash[i])
1127
+ return false;
1128
+ }
1129
+ return true;
1130
+ }
1131
+ function bin2numBigInt(hex) {
1132
+ if (!hex || hex.length === 0)
1133
+ return 0n;
1134
+ const bytes = [];
1135
+ for (let i = 0; i < hex.length; i += 2) {
1136
+ bytes.push(parseInt(hex.slice(i, i + 2), 16));
1137
+ }
1138
+ if (bytes.length === 0)
1139
+ return 0n;
1140
+ const negative = (bytes[bytes.length - 1] & 0x80) !== 0;
1141
+ if (negative) {
1142
+ bytes[bytes.length - 1] &= 0x7f;
1143
+ }
1144
+ let result = 0n;
1145
+ for (let i = bytes.length - 1; i >= 0; i--) {
1146
+ result = (result << 8n) | BigInt(bytes[i]);
1147
+ }
1148
+ return negative ? -result : result;
1149
+ }