@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,98 @@
1
+ import { Transaction } from '@bsv/sdk';
2
+ import type { RunarArtifact } from '../../../../pool/runar-ir-schema.js';
3
+ import type { UTXO } from './types.js';
4
+ /**
5
+ * Build a transaction that spends a contract UTXO (method call).
6
+ *
7
+ * The transaction:
8
+ * - Input 0: the current contract UTXO with the given unlocking script.
9
+ * - Additional inputs: funding UTXOs if provided.
10
+ * - Output 0 (optional): new contract UTXO with updated locking script
11
+ * (for stateful contracts).
12
+ * - Last output (optional): change.
13
+ *
14
+ * Returns the Transaction object (with unlocking script for input 0
15
+ * already placed) and the total input count.
16
+ */
17
+ export declare function buildCallTransaction(currentUtxo: UTXO, unlockingScript: string, newLockingScript?: string, newSatoshis?: number, changeAddress?: string, changeScript?: string, additionalUtxos?: UTXO[], feeRate?: number, options?: {
18
+ /** Multiple contract outputs (replaces single newLockingScript). */
19
+ contractOutputs?: Array<{
20
+ script: string;
21
+ satoshis: number;
22
+ }>;
23
+ /** Additional contract inputs with their own unlocking scripts (for merge). */
24
+ additionalContractInputs?: Array<{
25
+ utxo: UTXO;
26
+ unlockingScript: string;
27
+ }>;
28
+ /** Data outputs declared via `this.addDataOutput(...)` in the method
29
+ * body. Emitted between contract (state) outputs and the change output,
30
+ * in declaration order — matching the compile-time continuation-hash
31
+ * layout. */
32
+ dataOutputs?: Array<{
33
+ script: string;
34
+ satoshis: number;
35
+ }>;
36
+ /** Override the call tx's nLockTime. Unset → defaults to 0 (legacy).
37
+ * Set to a non-zero value for contracts that assert
38
+ * `extractLocktime(preimage) >= deadline` (e.g. auction close/claim). */
39
+ locktime?: number;
40
+ /** Override the nSequence written onto every input (issue #131). Unset →
41
+ * 0xfffffffe when a non-zero locktime is set (so nLockTime is consensus-
42
+ * enforced), else 0xffffffff (final, legacy). */
43
+ sequence?: number;
44
+ }): {
45
+ tx: Transaction;
46
+ inputCount: number;
47
+ changeAmount: number;
48
+ };
49
+ /**
50
+ * Resolve the nSequence for a call tx's inputs (issue #131).
51
+ *
52
+ * An all-0xffffffff input set makes nLockTime a consensus no-op, so a
53
+ * locktime-gated method would be script-enforced (via extractLocktime) yet
54
+ * NOT consensus-enforced. When a non-zero locktime is set we therefore default
55
+ * every input to 0xfffffffe (non-final, enforceable). Explicit `sequence`
56
+ * always wins; with no/zero locktime we keep the legacy 0xffffffff.
57
+ *
58
+ * Shared by `buildCallTransaction` and the terminal-path tx builder so both
59
+ * sites stay byte-consistent.
60
+ */
61
+ export declare function resolveInputSequence(locktime: number | undefined, sequence: number | undefined): number;
62
+ /**
63
+ * Estimate the fee for a method call transaction.
64
+ *
65
+ * @param lockingScriptByteLen - Byte length of the (single) contract
66
+ * continuation output's locking script.
67
+ * @param unlockingScriptByteLen - Byte length of the primary contract input's
68
+ * unlocking script.
69
+ * @param numFundingInputs - Number of P2PKH funding inputs (~148 bytes
70
+ * each), NOT continuation outputs. Use `extraOutputBytes` to size
71
+ * additional continuation/data outputs.
72
+ * @param feeRate - Sat/KB (default 100).
73
+ * @param extraOutputBytes - Serialized byte size of any outputs BEYOND
74
+ * the single `lockingScriptByteLen` one already counted (additional
75
+ * continuation outputs, data outputs) — mirrors `estimateDeployFee`'s
76
+ * `extraOutputBytes` in deployment.ts (finding C15). Defaults to 0.
77
+ */
78
+ export declare function estimateCallFee(lockingScriptByteLen: number, unlockingScriptByteLen: number, numFundingInputs: number, feeRate?: number, extraOutputBytes?: number): number;
79
+ export interface EstimateFeeForArtifactOpts {
80
+ /** Sat/byte. Default 0.1. */
81
+ feeRate?: number;
82
+ /** Number of continuation outputs, each assumed the same byte size as the
83
+ * artifact's own locking script (homogeneous multi-output fan-out).
84
+ * Default 1. Fixed under finding C4: previously this value was fed
85
+ * directly into `estimateCallFee`'s `numFundingInputs` slot (each priced
86
+ * at ~148 bytes, a P2PKH input) instead of sizing extra continuation
87
+ * OUTPUTS — silently mispricing every caller of this helper. */
88
+ outputCount?: number;
89
+ /** Unlocking-script byte length. Default: ceil(artifact.script.length / 4). */
90
+ unlockingScriptLen?: number;
91
+ }
92
+ /**
93
+ * Estimate the fee for a single call against a deployed contract built
94
+ * from the given artifact. Wraps {@link estimateCallFee} with defaults
95
+ * derived from the artifact and accepts `feeRate` in sat/byte (vs the
96
+ * sat/kilobyte unit `estimateCallFee` itself takes).
97
+ */
98
+ export declare function estimateFeeForArtifact(artifact: RunarArtifact, opts?: EstimateFeeForArtifactOpts): number;
@@ -0,0 +1,209 @@
1
+ // ---------------------------------------------------------------------------
2
+ // runar-sdk/calling.ts — Transaction construction for method invocation
3
+ // ---------------------------------------------------------------------------
4
+ import { Transaction, LockingScript, UnlockingScript } from '@bsv/sdk';
5
+ import { buildP2PKHScript } from './script-utils.js';
6
+ /**
7
+ * Build a transaction that spends a contract UTXO (method call).
8
+ *
9
+ * The transaction:
10
+ * - Input 0: the current contract UTXO with the given unlocking script.
11
+ * - Additional inputs: funding UTXOs if provided.
12
+ * - Output 0 (optional): new contract UTXO with updated locking script
13
+ * (for stateful contracts).
14
+ * - Last output (optional): change.
15
+ *
16
+ * Returns the Transaction object (with unlocking script for input 0
17
+ * already placed) and the total input count.
18
+ */
19
+ export function buildCallTransaction(currentUtxo, unlockingScript, newLockingScript, newSatoshis, changeAddress, changeScript, additionalUtxos, feeRate = 100, options) {
20
+ const extraContractInputs = options?.additionalContractInputs ?? [];
21
+ const dataOutputs = options?.dataOutputs ?? [];
22
+ const allUtxos = [currentUtxo, ...extraContractInputs.map((i) => i.utxo), ...(additionalUtxos ?? [])];
23
+ const totalInput = allUtxos.reduce((sum, u) => sum + u.satoshis, 0);
24
+ // Determine contract outputs: multi-output takes priority over single
25
+ const contractOutputs = options?.contractOutputs ??
26
+ (newLockingScript
27
+ ? [{ script: newLockingScript, satoshis: newSatoshis ?? currentUtxo.satoshis }]
28
+ : []);
29
+ const contractOutputSats = contractOutputs.reduce((sum, o) => sum + o.satoshis, 0)
30
+ + dataOutputs.reduce((sum, o) => sum + o.satoshis, 0);
31
+ // Estimate fee using actual script sizes
32
+ const input0Size = 32 + 4 + varIntByteSize(unlockingScript.length / 2) +
33
+ unlockingScript.length / 2 + 4;
34
+ let extraContractInputsSize = 0;
35
+ for (const ci of extraContractInputs) {
36
+ extraContractInputsSize += 32 + 4 +
37
+ varIntByteSize(ci.unlockingScript.length / 2) +
38
+ ci.unlockingScript.length / 2 + 4;
39
+ }
40
+ const p2pkhInputsSize = (additionalUtxos?.length ?? 0) * 148;
41
+ const inputsSize = input0Size + extraContractInputsSize + p2pkhInputsSize;
42
+ let outputsSize = 0;
43
+ for (const co of contractOutputs) {
44
+ outputsSize += 8 + varIntByteSize(co.script.length / 2) + co.script.length / 2;
45
+ }
46
+ for (const do_ of dataOutputs) {
47
+ outputsSize += 8 + varIntByteSize(do_.script.length / 2) + do_.script.length / 2;
48
+ }
49
+ if (changeAddress || changeScript) {
50
+ outputsSize += 34; // P2PKH change
51
+ }
52
+ const estimatedSize = 10 + inputsSize + outputsSize;
53
+ const fee = Math.ceil(estimatedSize * feeRate / 1000);
54
+ const change = totalInput - contractOutputSats - fee;
55
+ // Fail closed on a genuinely underfunded call (finding C3): if the inputs
56
+ // cannot even cover the (non-change) contract + data outputs, the tx spends
57
+ // more than it takes in — it can never confirm and a covenant would strand
58
+ // the funds. Do NOT throw merely because `change < 0`: an exact-cover
59
+ // continuation (issue #116) keeps the full input value in the continuation
60
+ // output and adds no funding, so `change === -fee` (negative) even though
61
+ // `totalInput === contractOutputSats` and the resulting zero-fee tx is valid
62
+ // (the covenant accepts a no-change spend). Guard on the only value-invalid
63
+ // case — `totalInput < contractOutputSats` (contractOutputSats already
64
+ // includes data outputs) — and keep the existing clamp (change output
65
+ // omitted when `change <= 0`). Mirrors Java's fail-closed selection loop.
66
+ if (totalInput < contractOutputSats) {
67
+ throw new Error(`buildCallTransaction: insufficient funds. Need ${contractOutputSats + fee} sats, have ${totalInput}`);
68
+ }
69
+ // Build Transaction object
70
+ const tx = new Transaction();
71
+ // Locktime: default 0 (legacy); overridable via options.locktime for
72
+ // contracts asserting `extractLocktime(preimage) >= deadline`.
73
+ tx.lockTime = options?.locktime ?? 0;
74
+ // Sequence (issue #131): an all-0xffffffff input set makes nLockTime a
75
+ // consensus no-op. When a non-zero locktime is set, default every input to
76
+ // 0xfffffffe (non-final) so the locktime is actually enforced. Explicit
77
+ // options.sequence always wins.
78
+ const inputSequence = resolveInputSequence(options?.locktime, options?.sequence);
79
+ // Input 0: primary contract UTXO with unlocking script
80
+ tx.addInput({
81
+ sourceTXID: currentUtxo.txid,
82
+ sourceOutputIndex: currentUtxo.outputIndex,
83
+ unlockingScript: UnlockingScript.fromHex(unlockingScript),
84
+ sequence: inputSequence,
85
+ });
86
+ // Additional contract inputs (with their own unlocking scripts)
87
+ for (const ci of extraContractInputs) {
88
+ tx.addInput({
89
+ sourceTXID: ci.utxo.txid,
90
+ sourceOutputIndex: ci.utxo.outputIndex,
91
+ unlockingScript: UnlockingScript.fromHex(ci.unlockingScript),
92
+ sequence: inputSequence,
93
+ });
94
+ }
95
+ // P2PKH funding inputs (unsigned)
96
+ if (additionalUtxos) {
97
+ for (const utxo of additionalUtxos) {
98
+ tx.addInput({
99
+ sourceTXID: utxo.txid,
100
+ sourceOutputIndex: utxo.outputIndex,
101
+ unlockingScript: new UnlockingScript(),
102
+ sequence: inputSequence,
103
+ });
104
+ }
105
+ }
106
+ // Contract outputs
107
+ for (const co of contractOutputs) {
108
+ tx.addOutput({
109
+ satoshis: co.satoshis,
110
+ lockingScript: LockingScript.fromHex(co.script),
111
+ });
112
+ }
113
+ // Data outputs (from this.addDataOutput in method body). Emitted after
114
+ // state outputs and before change to match the continuation hash.
115
+ for (const do_ of dataOutputs) {
116
+ tx.addOutput({
117
+ satoshis: do_.satoshis,
118
+ lockingScript: LockingScript.fromHex(do_.script),
119
+ });
120
+ }
121
+ // Change output
122
+ if (change > 0 && (changeAddress || changeScript)) {
123
+ const actualChangeScript = changeScript || buildP2PKHScript(changeAddress);
124
+ tx.addOutput({
125
+ satoshis: change,
126
+ lockingScript: LockingScript.fromHex(actualChangeScript),
127
+ });
128
+ }
129
+ return { tx, inputCount: allUtxos.length, changeAmount: change > 0 ? change : 0 };
130
+ }
131
+ /**
132
+ * Resolve the nSequence for a call tx's inputs (issue #131).
133
+ *
134
+ * An all-0xffffffff input set makes nLockTime a consensus no-op, so a
135
+ * locktime-gated method would be script-enforced (via extractLocktime) yet
136
+ * NOT consensus-enforced. When a non-zero locktime is set we therefore default
137
+ * every input to 0xfffffffe (non-final, enforceable). Explicit `sequence`
138
+ * always wins; with no/zero locktime we keep the legacy 0xffffffff.
139
+ *
140
+ * Shared by `buildCallTransaction` and the terminal-path tx builder so both
141
+ * sites stay byte-consistent.
142
+ */
143
+ export function resolveInputSequence(locktime, sequence) {
144
+ if (sequence !== undefined)
145
+ return sequence;
146
+ if (locktime !== undefined && locktime !== 0)
147
+ return 0xfffffffe;
148
+ return 0xffffffff;
149
+ }
150
+ // ---------------------------------------------------------------------------
151
+ // Fee estimation
152
+ // ---------------------------------------------------------------------------
153
+ const P2PKH_INPUT_SIZE = 148;
154
+ const P2PKH_OUTPUT_SIZE = 34;
155
+ const TX_OVERHEAD = 10;
156
+ /**
157
+ * Estimate the fee for a method call transaction.
158
+ *
159
+ * @param lockingScriptByteLen - Byte length of the (single) contract
160
+ * continuation output's locking script.
161
+ * @param unlockingScriptByteLen - Byte length of the primary contract input's
162
+ * unlocking script.
163
+ * @param numFundingInputs - Number of P2PKH funding inputs (~148 bytes
164
+ * each), NOT continuation outputs. Use `extraOutputBytes` to size
165
+ * additional continuation/data outputs.
166
+ * @param feeRate - Sat/KB (default 100).
167
+ * @param extraOutputBytes - Serialized byte size of any outputs BEYOND
168
+ * the single `lockingScriptByteLen` one already counted (additional
169
+ * continuation outputs, data outputs) — mirrors `estimateDeployFee`'s
170
+ * `extraOutputBytes` in deployment.ts (finding C15). Defaults to 0.
171
+ */
172
+ export function estimateCallFee(lockingScriptByteLen, unlockingScriptByteLen, numFundingInputs, feeRate = 100, extraOutputBytes = 0) {
173
+ const contractInputSize = 32 + 4 + varIntByteSize(unlockingScriptByteLen) + unlockingScriptByteLen + 4;
174
+ const fundingInputsSize = numFundingInputs * P2PKH_INPUT_SIZE;
175
+ const contractOutputSize = 8 + varIntByteSize(lockingScriptByteLen) + lockingScriptByteLen;
176
+ const changeOutputSize = P2PKH_OUTPUT_SIZE;
177
+ const txSize = TX_OVERHEAD + contractInputSize + fundingInputsSize + contractOutputSize + extraOutputBytes + changeOutputSize;
178
+ return Math.ceil(txSize * feeRate / 1000);
179
+ }
180
+ /**
181
+ * Estimate the fee for a single call against a deployed contract built
182
+ * from the given artifact. Wraps {@link estimateCallFee} with defaults
183
+ * derived from the artifact and accepts `feeRate` in sat/byte (vs the
184
+ * sat/kilobyte unit `estimateCallFee` itself takes).
185
+ */
186
+ export function estimateFeeForArtifact(artifact, opts = {}) {
187
+ const satPerByte = opts.feeRate ?? 0.1;
188
+ const outputs = opts.outputCount ?? 1;
189
+ const lockingLen = artifact.script.length / 2;
190
+ const unlockingLen = opts.unlockingScriptLen ?? Math.ceil(artifact.script.length / 4);
191
+ // estimateCallFee already prices ONE continuation output of lockingLen
192
+ // bytes; extraOutputBytes covers any additional outputs beyond that one
193
+ // (finding C4 — outputCount used to be smuggled into numFundingInputs).
194
+ const perOutputSize = 8 + varIntByteSize(lockingLen) + lockingLen;
195
+ const extraOutputBytes = Math.max(0, outputs - 1) * perOutputSize;
196
+ return estimateCallFee(lockingLen, unlockingLen, 0, satPerByte * 1000, extraOutputBytes);
197
+ }
198
+ // ---------------------------------------------------------------------------
199
+ // Helpers
200
+ // ---------------------------------------------------------------------------
201
+ function varIntByteSize(n) {
202
+ if (n < 0xfd)
203
+ return 1;
204
+ if (n <= 0xffff)
205
+ return 3;
206
+ if (n <= 0xffffffff)
207
+ return 5;
208
+ return 9;
209
+ }