@kronsdk/kron-sdk 0.4.0 → 0.5.0

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/README.md CHANGED
@@ -4,10 +4,16 @@
4
4
  — a bonding-curve launchpad + AMM DEX — from any JS/TS environment.** Browser or Node. No custody, ever:
5
5
  this package only *builds* transactions; a wallet (yours, or your user's) signs them.
6
6
 
7
- > **Status: v0.2.1, testnet (TN10).** Read paths and the covenant builders are proven byte-identical to
7
+ > **Status: v0.5.0, testnet (TN10).** Read paths and the covenant builders are proven byte-identical to
8
8
  > KRON's own production code (see "Verification" below). Wallet signing is a documented interface plus a
9
9
  > generic reference implementation — see [`docs/WALLETS.md`](docs/WALLETS.md) for the contract and how to
10
10
  > adapt it to a specific wallet's injected provider.
11
+ >
12
+ > **⚠️ Upgrade from < 0.5.0.** Every earlier release built **version-0** transactions, which cannot carry
13
+ > the covenant bindings Kaspa's covenant layer (KIP-20) requires on output — every assembled spend was
14
+ > rejected on-chain with `script ran, but verification failed`. Fixed in 0.5.0: `assembleNativeTx` now
15
+ > builds v1 transactions with bindings + compute budgets attached. See the [CHANGELOG](CHANGELOG.md) for
16
+ > the migration note if you assembled transactions by hand instead of via `spend.assembleNativeTx`.
11
17
 
12
18
  ## Why this exists
13
19
 
@@ -34,11 +40,12 @@ ESM only (`"type": "module"`) in v1 — see [Design notes](#design-notes) for wh
34
40
 
35
41
  ```bash
36
42
  npm install @kronsdk/kron-sdk@latest # newest
37
- npm install @kronsdk/kron-sdk@0.2.0 # or pin an exact version for reproducible builds
43
+ npm install @kronsdk/kron-sdk@0.5.0 # or pin an exact version for reproducible builds
38
44
  ```
39
45
 
40
- The package follows semver. The token-list client (`client.RegistryClient.tokenlist()`) and on-chain
41
- verifier (`verify.verifyTokenListEntry`) landed in **0.2.0** see [Discover & verify tokens](#discover--verify-tokens-token-list).
46
+ The package follows semver. **0.5.0 is a required upgrade for anyone using `spend.assembleNativeTx`** see
47
+ the warning above. The token-list client (`client.RegistryClient.tokenlist()`) and on-chain verifier
48
+ (`verify.verifyTokenListEntry`) landed in **0.2.0** — see [Discover & verify tokens](#discover--verify-tokens-token-list).
42
49
 
43
50
  ## Quickstart — quote a curve buy (Node)
44
51
 
package/dist/index.d.ts CHANGED
@@ -140,6 +140,21 @@ declare namespace genesis {
140
140
 
141
141
  type K$5 = Kaspa;
142
142
  type Spk$4 = any;
143
+ /** Covenant txs are KIP-20 v1 transactions (covenant outputs require tx.version >= 1). */
144
+ declare const TX_VERSION = 1;
145
+ /** Per-input compute budget (v1): a P2PK funding input ≈ one sig op. */
146
+ declare const FUNDING_COMPUTE = 10;
147
+ /** Per-input compute budget (v1): a kcc20 `transfer` input (token balance / inventory / seller piece). */
148
+ declare const TOKEN_COMPUTE = 500;
149
+ /** Per-input compute budget (v1): a curve_cp / amm_pool_cp input (the large redeem scripts). */
150
+ declare const COVENANT_COMPUTE = 2000;
151
+ /** Covenant output min value (KIP-9 storage mass) — the conventional token-UTXO dust, 0.5 KAS. */
152
+ declare const COVENANT_DUST = 50000000n;
153
+ /** A covenant output's KIP-20 lineage: which covenant id it continues and which input authorizes it. */
154
+ type CovBinding = {
155
+ covid: string;
156
+ authorizingInput: number;
157
+ };
143
158
  /** A covenant UTXO being spent, already carrying its signature script (no wallet signature needed). */
144
159
  type CovInput = {
145
160
  transactionId: string;
@@ -152,16 +167,19 @@ type CovInput = {
152
167
  redeem: Uint8Array;
153
168
  /** what this input is, for assembly/debugging: 'curve' | 'minterBranch' | 'burn' | 'pool' | 'poolToken'. */
154
169
  role: string;
170
+ /** v1 compute budget override; defaults by role (curve/pool → COVENANT_COMPUTE, else TOKEN_COMPUTE). */
171
+ computeBudget?: number;
155
172
  };
156
- /** A covenant-required output (value + scriptPublicKey). */
173
+ /** A covenant-required output (value + scriptPublicKey [+ covenant binding]). */
157
174
  type CovOutput = {
158
175
  value: bigint;
159
176
  scriptPublicKey: Spk$4;
160
177
  role: string;
178
+ binding?: CovBinding;
161
179
  };
162
180
  /** A complete covenant action: the inputs it spends + the outputs it must create + computed economics. */
163
181
  type CovenantSpend = {
164
- kind: 'init' | 'initVested' | 'buy' | 'sell' | 'graduate' | 'swapKasForToken' | 'swapTokenForKas' | 'addLiquidity' | 'removeLiquidity' | 'bindLp' | 'claim' | 'claimFinal';
182
+ kind: 'init' | 'initVested' | 'buy' | 'sell' | 'transfer' | 'graduate' | 'swapKasForToken' | 'swapTokenForKas' | 'addLiquidity' | 'removeLiquidity' | 'bindLp' | 'claim' | 'claimFinal';
165
183
  inputs: CovInput[];
166
184
  outputs: CovOutput[];
167
185
  economics: Record<string, bigint>;
@@ -183,10 +201,12 @@ type AssembledNativeTx = {
183
201
  change: bigint;
184
202
  };
185
203
  /**
186
- * Assemble a complete tx: the spend's covenant inputs (pre-scripted) + the trader's funding inputs + a
187
- * change output. `networkFee` is caller-provided (derive from the node; KIP-9 storage mass depends on
188
- * output values, so the node confirms the exact fee/change at broadcast). Covenant inputs carry sigOpCount
189
- * 0 (no checkSig on the accept path used here); funding inputs are signed via signFundingInputs.
204
+ * Assemble a complete v1 covenant tx: the spend's covenant inputs (pre-scripted) + the trader's funding
205
+ * inputs + a change output. Covenant outputs whose `binding` is set carry the KIP-20 `CovenantBinding`
206
+ * (REQUIRED for any output the covenant validates see the module header). `networkFee` is
207
+ * caller-provided; size it with `estimateNativeFee` (v1 fees must cover the per-input compute budget, so a
208
+ * flat legacy fee is usually too low). Covenant inputs default to a role-based compute budget; funding
209
+ * inputs are signed via signFundingInputs (or the signPskt bridge).
190
210
  */
191
211
  declare function assembleNativeTx(k: K$5, opts: {
192
212
  spend: CovenantSpend;
@@ -194,6 +214,14 @@ declare function assembleNativeTx(k: K$5, opts: {
194
214
  changeAddress: string;
195
215
  networkFee: bigint;
196
216
  }): AssembledNativeTx;
217
+ /**
218
+ * Size `networkFee` for an assembled v1 tx: byte/storage mass (via the WASM mass calculator, with
219
+ * placeholder signatures on the funding inputs so byte mass is realistic) + the per-input compute budget
220
+ * the calculator omits (grams = budget × 100), at `feeRateSompiPerGram` (use the node's feerate estimate;
221
+ * min-relay on TN10 has been ~100 sompi/gram), with a 1.5× over-cover and a 10_000-sompi floor. Assemble
222
+ * with a guess (e.g. 10_000n), call this, then re-assemble with the returned fee.
223
+ */
224
+ declare function estimateNativeFee(k: K$5, networkId: string, asm: AssembledNativeTx, feeRateSompiPerGram: number): bigint;
197
225
  /** Sign the trader's funding inputs (P2PK) in place; covenant inputs are left untouched (pre-scripted). */
198
226
  declare function signFundingInputs(k: K$5, tx: any, privKey: any, fundingInputIndexes: number[]): any;
199
227
  /**
@@ -221,16 +249,23 @@ declare function signPsktWithKey(k: K$5, txJsonString: string, signInputs: {
221
249
  }[], privKey: any): string;
222
250
 
223
251
  type spend_AssembledNativeTx = AssembledNativeTx;
252
+ declare const spend_COVENANT_COMPUTE: typeof COVENANT_COMPUTE;
253
+ declare const spend_COVENANT_DUST: typeof COVENANT_DUST;
254
+ type spend_CovBinding = CovBinding;
224
255
  type spend_CovInput = CovInput;
225
256
  type spend_CovOutput = CovOutput;
226
257
  type spend_CovenantSpend = CovenantSpend;
258
+ declare const spend_FUNDING_COMPUTE: typeof FUNDING_COMPUTE;
227
259
  type spend_FundingEntry = FundingEntry;
260
+ declare const spend_TOKEN_COMPUTE: typeof TOKEN_COMPUTE;
261
+ declare const spend_TX_VERSION: typeof TX_VERSION;
228
262
  declare const spend_assembleNativeTx: typeof assembleNativeTx;
263
+ declare const spend_estimateNativeFee: typeof estimateNativeFee;
229
264
  declare const spend_signFundingInputs: typeof signFundingInputs;
230
265
  declare const spend_signPsktWithKey: typeof signPsktWithKey;
231
266
  declare const spend_toPsktJson: typeof toPsktJson;
232
267
  declare namespace spend {
233
- export { type spend_AssembledNativeTx as AssembledNativeTx, type spend_CovInput as CovInput, type spend_CovOutput as CovOutput, type spend_CovenantSpend as CovenantSpend, type spend_FundingEntry as FundingEntry, spend_assembleNativeTx as assembleNativeTx, spend_signFundingInputs as signFundingInputs, spend_signPsktWithKey as signPsktWithKey, spend_toPsktJson as toPsktJson };
268
+ export { type spend_AssembledNativeTx as AssembledNativeTx, spend_COVENANT_COMPUTE as COVENANT_COMPUTE, spend_COVENANT_DUST as COVENANT_DUST, type spend_CovBinding as CovBinding, type spend_CovInput as CovInput, type spend_CovOutput as CovOutput, type spend_CovenantSpend as CovenantSpend, spend_FUNDING_COMPUTE as FUNDING_COMPUTE, type spend_FundingEntry as FundingEntry, spend_TOKEN_COMPUTE as TOKEN_COMPUTE, spend_TX_VERSION as TX_VERSION, spend_assembleNativeTx as assembleNativeTx, spend_estimateNativeFee as estimateNativeFee, spend_signFundingInputs as signFundingInputs, spend_signPsktWithKey as signPsktWithKey, spend_toPsktJson as toPsktJson };
234
269
  }
235
270
 
236
271
  type K$4 = Kaspa;
@@ -259,6 +294,23 @@ type Kcc20Template = {
259
294
  };
260
295
  /** Produce the kcc20 redeem script for `state` by splicing the 46-byte region. Byte-identical to silverc. */
261
296
  declare function materializeKcc20Script(tpl: Kcc20Template, state: Kcc20State): Uint8Array;
297
+ /**
298
+ * Recover the `{ script, stateStart }` template AND the current balance state from a live token UTXO's
299
+ * redeem script (your indexer's `redeemScriptHex`). Scans for the 46-byte state region's push layout
300
+ * (`0x20 <owner:32> 0x01 <type:1> 0x08 <amount:8> 0x01 <isMinter:1>` with type ≤ 0x03, isMinter ≤ 1) and
301
+ * requires it to match at exactly ONE offset. This is the supported way to build the template for
302
+ * `materializeKcc20Script` — splice the SAME script the chain holds; never re-compile.
303
+ * `maxIns`/`maxOuts` are compiled constants not recoverable from the bytes — pass the token's deploy
304
+ * params if you know them (KRON deploys use 4/4, the default) — they are informational only (the splice
305
+ * uses just `script` + `stateStart`).
306
+ */
307
+ declare function decodeKcc20Redeem(redeem: Uint8Array, opts?: {
308
+ maxIns?: number;
309
+ maxOuts?: number;
310
+ }): {
311
+ template: Kcc20Template;
312
+ state: Kcc20State;
313
+ };
262
314
  /** Token P2SH scriptPublicKey for a redeem script. */
263
315
  declare const kcc20Spk: (k: K$4, redeem: Uint8Array) => Spk$3;
264
316
  /** Token P2SH scriptPublicKey for a balance state (materialize → P2SH). */
@@ -288,13 +340,33 @@ declare function pushKcc20States(b: SigScriptBuilder, states: Kcc20State[]): voi
288
340
  * carrying that covenant id). `sigs` are 65-byte Schnorr sigs (empty for covenant-id-only ownership).
289
341
  */
290
342
  declare function transferSigScript(k: K$4, redeem: Uint8Array, newStates: Kcc20State[], witnesses: number[], sigs?: Uint8Array[]): string;
343
+ /**
344
+ * Send `sendAmount` from one or more presence-owned token UTXOs (same owner) to `recipientPubkey32`, with
345
+ * the remainder returned to the sender — the wallet "Send" button. A plain conserving `transfer`: inputs
346
+ * are authorized by ONE co-present P2PK input at tx index `presenceWitnessIdx` (assemble the covenant
347
+ * inputs first, then the sender's funding inputs: with N token inputs, `presenceWitnessIdx = N`).
348
+ *
349
+ * `tokenCovid` (the token's covenant id, hex — `covenantId` from the indexer's token info) is REQUIRED:
350
+ * every covenant output must carry the KIP-20 `CovenantBinding` or the chain rejects the spend with
351
+ * "script ran, but verification failed". Outputs: [recipient, change] (change omitted when exact).
352
+ */
353
+ declare function buildKcc20Send(k: K$4, tpl: Kcc20Template, senderTokens: {
354
+ transactionId: string;
355
+ index: number;
356
+ value: bigint;
357
+ state: Kcc20State;
358
+ }[], recipientPubkey32: Uint8Array, sendAmount: bigint, presenceWitnessIdx: number, tokenCovid: string, opts?: {
359
+ tokenDust?: bigint;
360
+ }): CovenantSpend;
291
361
 
292
362
  declare const kcc20Tx_IDENTIFIER: typeof IDENTIFIER;
293
363
  type kcc20Tx_IdentifierType = IdentifierType;
294
364
  type kcc20Tx_Kcc20State = Kcc20State;
295
365
  type kcc20Tx_Kcc20Template = Kcc20Template;
296
366
  declare const kcc20Tx_addressPresenceOwned: typeof addressPresenceOwned;
367
+ declare const kcc20Tx_buildKcc20Send: typeof buildKcc20Send;
297
368
  declare const kcc20Tx_covenantIdOwned: typeof covenantIdOwned;
369
+ declare const kcc20Tx_decodeKcc20Redeem: typeof decodeKcc20Redeem;
298
370
  declare const kcc20Tx_kcc20Address: typeof kcc20Address;
299
371
  declare const kcc20Tx_kcc20Spk: typeof kcc20Spk;
300
372
  declare const kcc20Tx_kcc20SpkForState: typeof kcc20SpkForState;
@@ -305,7 +377,7 @@ declare const kcc20Tx_pushKcc20States: typeof pushKcc20States;
305
377
  declare const kcc20Tx_scriptHashOwned: typeof scriptHashOwned;
306
378
  declare const kcc20Tx_transferSigScript: typeof transferSigScript;
307
379
  declare namespace kcc20Tx {
308
- export { kcc20Tx_IDENTIFIER as IDENTIFIER, type kcc20Tx_IdentifierType as IdentifierType, type kcc20Tx_Kcc20State as Kcc20State, type kcc20Tx_Kcc20Template as Kcc20Template, kcc20Tx_addressPresenceOwned as addressPresenceOwned, kcc20Tx_covenantIdOwned as covenantIdOwned, kcc20Tx_kcc20Address as kcc20Address, kcc20Tx_kcc20Spk as kcc20Spk, kcc20Tx_kcc20SpkForState as kcc20SpkForState, kcc20Tx_materializeKcc20Script as materializeKcc20Script, kcc20Tx_pubkeyOwned as pubkeyOwned, kcc20Tx_pushKcc20StateScalar as pushKcc20StateScalar, kcc20Tx_pushKcc20States as pushKcc20States, kcc20Tx_scriptHashOwned as scriptHashOwned, kcc20Tx_transferSigScript as transferSigScript };
380
+ export { kcc20Tx_IDENTIFIER as IDENTIFIER, type kcc20Tx_IdentifierType as IdentifierType, type kcc20Tx_Kcc20State as Kcc20State, type kcc20Tx_Kcc20Template as Kcc20Template, kcc20Tx_addressPresenceOwned as addressPresenceOwned, kcc20Tx_buildKcc20Send as buildKcc20Send, kcc20Tx_covenantIdOwned as covenantIdOwned, kcc20Tx_decodeKcc20Redeem as decodeKcc20Redeem, kcc20Tx_kcc20Address as kcc20Address, kcc20Tx_kcc20Spk as kcc20Spk, kcc20Tx_kcc20SpkForState as kcc20SpkForState, kcc20Tx_materializeKcc20Script as materializeKcc20Script, kcc20Tx_pubkeyOwned as pubkeyOwned, kcc20Tx_pushKcc20StateScalar as pushKcc20StateScalar, kcc20Tx_pushKcc20States as pushKcc20States, kcc20Tx_scriptHashOwned as scriptHashOwned, kcc20Tx_transferSigScript as transferSigScript };
309
381
  }
310
382
 
311
383
  type K$3 = Kaspa;
@@ -598,6 +670,8 @@ declare function buildCpGraduate(k: K$2, tpl: CpTemplate, tokenTpl: Kcc20Templat
598
670
  * a plain conserving kcc20 transfer authorized by a co-present P2PK input at `presenceWitnessIdx`. Lets a
599
671
  * holder sell an ARBITRARY amount on covenants that require full-UTXO sells (curve/pool): split, then sell the
600
672
  * `sellAmount` piece. No curve/pool involved — just the token covenant.
673
+ * Pass `opts.tokenCovid` (the token's covenant id, hex — `covenantId` from the indexer) so both outputs carry
674
+ * the KIP-20 covenant binding the chain requires; without it the assembled tx fails on-chain.
601
675
  */
602
676
  declare function buildSplitToken(k: K$2, tokenTpl: Kcc20Template, sellerToken: {
603
677
  transactionId: string;
@@ -606,11 +680,14 @@ declare function buildSplitToken(k: K$2, tokenTpl: Kcc20Template, sellerToken: {
606
680
  state: Kcc20State;
607
681
  }, sellAmount: bigint, presenceWitnessIdx: number, opts?: {
608
682
  tokenDust?: bigint;
683
+ tokenCovid?: string;
609
684
  }): CovenantSpend;
610
685
  /**
611
686
  * Consolidate several presence-owned token UTXOs (same owner) into ONE — a conserving kcc20 transfer (N covid-A
612
687
  * inputs → 1 output) authorized by a single co-present P2PK input at `presenceWitnessIdx`. Lets a holder merge
613
688
  * many small buys into one piece so a later sell needs just one (or two) inputs. No curve/pool involved.
689
+ * Pass `opts.tokenCovid` (the token's covenant id, hex) so the merged output carries the KIP-20 covenant
690
+ * binding the chain requires; without it the assembled tx fails on-chain.
614
691
  */
615
692
  declare function buildConsolidate(k: K$2, tokenTpl: Kcc20Template, tokens: {
616
693
  transactionId: string;
@@ -619,6 +696,7 @@ declare function buildConsolidate(k: K$2, tokenTpl: Kcc20Template, tokens: {
619
696
  state: Kcc20State;
620
697
  }[], presenceWitnessIdx: number, opts?: {
621
698
  tokenDust?: bigint;
699
+ tokenCovid?: string;
622
700
  }): CovenantSpend;
623
701
 
624
702
  type curveCpTx_CpCurveState = CpCurveState;
package/dist/index.js CHANGED
@@ -163,12 +163,24 @@ var ZERO_COVID = "00".repeat(32);
163
163
  // src/native/spend.ts
164
164
  var spend_exports = {};
165
165
  __export(spend_exports, {
166
+ COVENANT_COMPUTE: () => COVENANT_COMPUTE,
167
+ COVENANT_DUST: () => COVENANT_DUST,
168
+ FUNDING_COMPUTE: () => FUNDING_COMPUTE,
169
+ TOKEN_COMPUTE: () => TOKEN_COMPUTE,
170
+ TX_VERSION: () => TX_VERSION,
166
171
  assembleNativeTx: () => assembleNativeTx,
172
+ estimateNativeFee: () => estimateNativeFee,
167
173
  signFundingInputs: () => signFundingInputs,
168
174
  signPsktWithKey: () => signPsktWithKey,
169
175
  toPsktJson: () => toPsktJson
170
176
  });
177
+ var TX_VERSION = 1;
178
+ var FUNDING_COMPUTE = 10;
179
+ var TOKEN_COMPUTE = 500;
180
+ var COVENANT_COMPUTE = 2e3;
181
+ var COVENANT_DUST = 50000000n;
171
182
  var SUBNET_ZERO = "0000000000000000000000000000000000000000";
183
+ var budgetForRole = (role) => role === "curve" || role === "pool" ? COVENANT_COMPUTE : TOKEN_COMPUTE;
172
184
  function assembleNativeTx(k, opts) {
173
185
  const { spend, fundingEntries, changeAddress, networkFee } = opts;
174
186
  const kk = k;
@@ -178,6 +190,7 @@ function assembleNativeTx(k, opts) {
178
190
  signatureScript: ci.signatureScript,
179
191
  sequence: 0n,
180
192
  sigOpCount: 0,
193
+ computeBudget: ci.computeBudget ?? budgetForRole(ci.role),
181
194
  utxo: {
182
195
  outpoint: { transactionId: ci.transactionId, index: ci.index },
183
196
  amount: ci.value,
@@ -188,7 +201,7 @@ function assembleNativeTx(k, opts) {
188
201
  })
189
202
  );
190
203
  const fundingInputs = fundingEntries.map(
191
- (e) => new kk.TransactionInput({ previousOutpoint: e.outpoint, signatureScript: "", sequence: 0n, sigOpCount: 1, utxo: e })
204
+ (e) => new kk.TransactionInput({ previousOutpoint: e.outpoint, signatureScript: "", sequence: 0n, sigOpCount: 0, computeBudget: FUNDING_COMPUTE, utxo: e })
192
205
  );
193
206
  const covInValue = spend.inputs.reduce((s, ci) => s + ci.value, 0n);
194
207
  const fundingTotal = fundingEntries.reduce((s, e) => s + BigInt(e.amount), 0n);
@@ -196,10 +209,12 @@ function assembleNativeTx(k, opts) {
196
209
  const covenantOut = spend.outputs.reduce((s, o) => s + o.value, 0n);
197
210
  const change = totalIn - covenantOut - networkFee;
198
211
  if (change < 0n) throw new Error(`insufficient funding: need ${covenantOut + networkFee} sompi, have ${totalIn}`);
199
- const outputs = spend.outputs.map((o) => new kk.TransactionOutput(o.value, o.scriptPublicKey));
212
+ const outputs = spend.outputs.map(
213
+ (o) => o.binding ? new kk.TransactionOutput(o.value, o.scriptPublicKey, new kk.CovenantBinding(o.binding.authorizingInput, new kk.Hash(o.binding.covid))) : new kk.TransactionOutput(o.value, o.scriptPublicKey)
214
+ );
200
215
  outputs.push(new kk.TransactionOutput(change, kk.payToAddressScript(changeAddress)));
201
216
  const transaction = new kk.Transaction({
202
- version: 0,
217
+ version: TX_VERSION,
203
218
  inputs: [...covInputs, ...fundingInputs],
204
219
  outputs,
205
220
  lockTime: 0n,
@@ -215,6 +230,27 @@ function assembleNativeTx(k, opts) {
215
230
  change
216
231
  };
217
232
  }
233
+ function estimateNativeFee(k, networkId, asm, feeRateSompiPerGram) {
234
+ const kk = k;
235
+ const tx = asm.transaction;
236
+ const ins = tx.inputs;
237
+ const saved = asm.fundingInputIndexes.map((i) => ins[i].signatureScript);
238
+ for (const i of asm.fundingInputIndexes) ins[i].signatureScript = "00".repeat(66);
239
+ tx.inputs = ins;
240
+ let byteMass = 2000n;
241
+ try {
242
+ byteMass = BigInt(kk.calculateTransactionMass(networkId, tx));
243
+ } catch {
244
+ }
245
+ const ins2 = tx.inputs;
246
+ asm.fundingInputIndexes.forEach((i, j) => ins2[i].signatureScript = saved[j]);
247
+ tx.inputs = ins2;
248
+ let computeGrams = 0n;
249
+ for (const inp of tx.inputs) computeGrams += BigInt(inp.computeBudget || 0) * 100n;
250
+ const rate = BigInt(Math.max(Math.ceil(feeRateSompiPerGram), 1));
251
+ const fee = (byteMass + computeGrams) * rate * 3n / 2n;
252
+ return fee > 10000n ? fee : 10000n;
253
+ }
218
254
  function signFundingInputs(k, tx, privKey, fundingInputIndexes) {
219
255
  const inputs = tx.inputs;
220
256
  for (const idx of fundingInputIndexes) {
@@ -247,7 +283,9 @@ var kcc20Tx_exports = {};
247
283
  __export(kcc20Tx_exports, {
248
284
  IDENTIFIER: () => IDENTIFIER,
249
285
  addressPresenceOwned: () => addressPresenceOwned,
286
+ buildKcc20Send: () => buildKcc20Send,
250
287
  covenantIdOwned: () => covenantIdOwned,
288
+ decodeKcc20Redeem: () => decodeKcc20Redeem,
251
289
  kcc20Address: () => kcc20Address,
252
290
  kcc20Spk: () => kcc20Spk,
253
291
  kcc20SpkForState: () => kcc20SpkForState,
@@ -259,6 +297,7 @@ __export(kcc20Tx_exports, {
259
297
  transferSigScript: () => transferSigScript
260
298
  });
261
299
  var IDENTIFIER = { PUBKEY: 0, SCRIPT_HASH: 1, COVENANT_ID: 2, ADDRESS: 3 };
300
+ var STATE_LEN = 46;
262
301
  function materializeKcc20Script(tpl, state) {
263
302
  const s = tpl.stateStart;
264
303
  const t = tpl.script;
@@ -278,6 +317,25 @@ function materializeKcc20Script(tpl, state) {
278
317
  out[s + 45] = state.isMinter ? 1 : 0;
279
318
  return out;
280
319
  }
320
+ function decodeKcc20Redeem(redeem, opts = {}) {
321
+ const hits = [];
322
+ for (let s2 = 0; s2 + STATE_LEN <= redeem.length; s2++) {
323
+ if (redeem[s2] === 32 && redeem[s2 + 33] === 1 && redeem[s2 + 34] <= 3 && redeem[s2 + 35] === 8 && redeem[s2 + 44] === 1 && redeem[s2 + 45] <= 1) hits.push(s2);
324
+ }
325
+ if (hits.length !== 1) throw new Error(`could not locate the kcc20 state region in the redeem script (${hits.length} candidate offsets) \u2014 is this a kcc20 token UTXO?`);
326
+ const s = hits[0];
327
+ let amount = 0n;
328
+ for (let i = 7; i >= 0; i--) amount = amount << 8n | BigInt(redeem[s + 36 + i]);
329
+ return {
330
+ template: { script: redeem.slice(), stateStart: s, maxIns: opts.maxIns ?? 4, maxOuts: opts.maxOuts ?? 4 },
331
+ state: {
332
+ ownerIdentifier: redeem.slice(s + 1, s + 33),
333
+ identifierType: redeem[s + 34],
334
+ amount,
335
+ isMinter: redeem[s + 45] === 1
336
+ }
337
+ };
338
+ }
281
339
  var kcc20Spk = (k, redeem) => k.payToScriptHashScript(redeem);
282
340
  var kcc20SpkForState = (k, tpl, state) => kcc20Spk(k, materializeKcc20Script(tpl, state));
283
341
  function kcc20Address(k, tpl, state, network) {
@@ -327,6 +385,30 @@ function transferSigScript(k, redeem, newStates, witnesses, sigs = []) {
327
385
  b.redeem(redeem);
328
386
  return b.drain();
329
387
  }
388
+ function buildKcc20Send(k, tpl, senderTokens, recipientPubkey32, sendAmount, presenceWitnessIdx, tokenCovid, opts = {}) {
389
+ if (senderTokens.length < 1) throw new Error("send requires at least one token UTXO");
390
+ if (!tokenCovid) throw new Error("send requires the token covenant id (indexer token info `covenantId`) for the output bindings");
391
+ const total = senderTokens.reduce((s, t) => s + t.state.amount, 0n);
392
+ const change = total - sendAmount;
393
+ if (sendAmount < 1n || change < 0n) throw new Error(`send requires 1 <= sendAmount <= ${total} (the selected UTXOs' total)`);
394
+ const dust = opts.tokenDust ?? 50000000n;
395
+ const owner = senderTokens[0].state.ownerIdentifier;
396
+ const recipientOut = addressPresenceOwned(recipientPubkey32, sendAmount);
397
+ const newStates = change >= 1n ? [recipientOut, addressPresenceOwned(owner, change)] : [recipientOut];
398
+ const witnesses = senderTokens.map(() => presenceWitnessIdx);
399
+ const inputs = senderTokens.map((t) => {
400
+ const r = materializeKcc20Script(tpl, t.state);
401
+ return { transactionId: t.transactionId, index: t.index, value: t.value, scriptPublicKey: kcc20Spk(k, r), signatureScript: transferSigScript(k, r, newStates, witnesses), redeem: r, role: "token" };
402
+ });
403
+ const binding = { covid: tokenCovid, authorizingInput: 0 };
404
+ const outputs = newStates.map((st, i) => ({
405
+ value: dust,
406
+ scriptPublicKey: kcc20Spk(k, materializeKcc20Script(tpl, st)),
407
+ role: i === 0 ? "send" : "change",
408
+ binding
409
+ }));
410
+ return { kind: "transfer", inputs, outputs, economics: { sendAmount, change }, covids: { tokenCovid } };
411
+ }
330
412
 
331
413
  // src/native/curveCpTx.ts
332
414
  var curveCpTx_exports = {};
@@ -756,14 +838,15 @@ function buildSplitToken(k, tokenTpl, sellerToken, sellAmount, presenceWitnessId
756
838
  const out1 = addressPresenceOwned(owner, sellAmount);
757
839
  const out2 = addressPresenceOwned(owner, change);
758
840
  const redeem = materializeKcc20Script(tokenTpl, sellerToken.state);
841
+ const binding = opts.tokenCovid ? { covid: opts.tokenCovid, authorizingInput: 0 } : void 0;
759
842
  const inputs = [
760
843
  { transactionId: sellerToken.transactionId, index: sellerToken.index, value: sellerToken.value, scriptPublicKey: kcc20Spk(k, redeem), signatureScript: transferSigScript(k, redeem, [out1, out2], [presenceWitnessIdx]), redeem, role: "sellerToken" }
761
844
  ];
762
845
  const outputs = [
763
- { value: dust, scriptPublicKey: kcc20Spk(k, materializeKcc20Script(tokenTpl, out1)), role: "split" },
764
- { value: dust, scriptPublicKey: kcc20Spk(k, materializeKcc20Script(tokenTpl, out2)), role: "change" }
846
+ { value: dust, scriptPublicKey: kcc20Spk(k, materializeKcc20Script(tokenTpl, out1)), role: "split", binding },
847
+ { value: dust, scriptPublicKey: kcc20Spk(k, materializeKcc20Script(tokenTpl, out2)), role: "change", binding }
765
848
  ];
766
- return { kind: "sell", inputs, outputs, economics: { sellAmount, change }, covids: { tokenCovid: hexOf2(owner) } };
849
+ return { kind: "sell", inputs, outputs, economics: { sellAmount, change }, covids: opts.tokenCovid ? { tokenCovid: opts.tokenCovid } : {} };
767
850
  }
768
851
  function buildConsolidate(k, tokenTpl, tokens, presenceWitnessIdx, opts = {}) {
769
852
  if (tokens.length < 2) throw new Error("consolidate needs at least 2 UTXOs");
@@ -777,10 +860,11 @@ function buildConsolidate(k, tokenTpl, tokens, presenceWitnessIdx, opts = {}) {
777
860
  const r = materializeKcc20Script(tokenTpl, t.state);
778
861
  return { transactionId: t.transactionId, index: t.index, value: t.value, scriptPublicKey: kcc20Spk(k, r), signatureScript: transferSigScript(k, r, newStates, witnesses), redeem: r, role: "token" };
779
862
  });
863
+ const binding = opts.tokenCovid ? { covid: opts.tokenCovid, authorizingInput: 0 } : void 0;
780
864
  const outputs = [
781
- { value: dust, scriptPublicKey: kcc20Spk(k, materializeKcc20Script(tokenTpl, merged)), role: "merged" }
865
+ { value: dust, scriptPublicKey: kcc20Spk(k, materializeKcc20Script(tokenTpl, merged)), role: "merged", binding }
782
866
  ];
783
- return { kind: "sell", inputs, outputs, economics: { total }, covids: { tokenCovid: hexOf2(owner) } };
867
+ return { kind: "sell", inputs, outputs, economics: { total }, covids: opts.tokenCovid ? { tokenCovid: opts.tokenCovid } : {} };
784
868
  }
785
869
  var hexOf2 = (u8) => Array.from(u8, (b) => b.toString(16).padStart(2, "0")).join("");
786
870