@kaspacom/kcc20-tx-builder 0.8.0-fee.10 → 0.8.0-fee.11

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 (50) hide show
  1. package/CHANGELOG.md +7 -1
  2. package/dist/cjs/engine.js +125 -23
  3. package/dist/cjs/engine.js.map +1 -1
  4. package/dist/cjs/escrow-account-setup.d.ts +2 -0
  5. package/dist/cjs/escrow-account-setup.js +1 -0
  6. package/dist/cjs/escrow-account-setup.js.map +1 -1
  7. package/dist/cjs/escrow-receipt.d.ts +1 -0
  8. package/dist/cjs/escrow-receipt.js +5 -4
  9. package/dist/cjs/escrow-receipt.js.map +1 -1
  10. package/dist/cjs/escrow-recipient.d.ts +63 -0
  11. package/dist/cjs/escrow-recipient.js +119 -0
  12. package/dist/cjs/escrow-recipient.js.map +1 -0
  13. package/dist/cjs/index.d.ts +1 -0
  14. package/dist/cjs/index.js +1 -0
  15. package/dist/cjs/index.js.map +1 -1
  16. package/dist/cjs/operations.d.ts +1 -1
  17. package/dist/cjs/operations.js +1 -0
  18. package/dist/cjs/operations.js.map +1 -1
  19. package/dist/engine.d.ts.map +1 -1
  20. package/dist/engine.js +125 -23
  21. package/dist/engine.js.map +1 -1
  22. package/dist/escrow-account-setup.d.ts +2 -0
  23. package/dist/escrow-account-setup.d.ts.map +1 -1
  24. package/dist/escrow-account-setup.js +1 -1
  25. package/dist/escrow-account-setup.js.map +1 -1
  26. package/dist/escrow-receipt.d.ts +1 -0
  27. package/dist/escrow-receipt.d.ts.map +1 -1
  28. package/dist/escrow-receipt.js +4 -4
  29. package/dist/escrow-receipt.js.map +1 -1
  30. package/dist/escrow-recipient.d.ts +64 -0
  31. package/dist/escrow-recipient.d.ts.map +1 -0
  32. package/dist/escrow-recipient.js +115 -0
  33. package/dist/escrow-recipient.js.map +1 -0
  34. package/dist/index.d.ts +1 -0
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +1 -0
  37. package/dist/index.js.map +1 -1
  38. package/dist/operations.d.ts +1 -1
  39. package/dist/operations.d.ts.map +1 -1
  40. package/dist/operations.js +1 -0
  41. package/dist/operations.js.map +1 -1
  42. package/docs/COMPATIBILITY.json +1 -1
  43. package/docs/fee-recipient-wallet.md +13 -0
  44. package/package.json +3 -3
  45. package/src/engine.ts +119 -23
  46. package/src/escrow-account-setup.ts +1 -1
  47. package/src/escrow-receipt.ts +4 -4
  48. package/src/escrow-recipient.ts +117 -0
  49. package/src/index.ts +1 -0
  50. package/src/operations.ts +1 -0
package/CHANGELOG.md CHANGED
@@ -1,6 +1,12 @@
1
1
  # Changelog
2
2
 
3
- ## 0.8.0-fee.10 (unreleased)
3
+ ## 0.8.0-fee.11 (unreleased)
4
+
5
+ - Build the pinned recipient covenant, delegated fee claims and rotation back to wallet control through the shared PSKT flow.
6
+ - Revalidate recipient authority and live UTXOs; preserve account and recipient principals and the other party's fees.
7
+ - Execute signed recipient claims and rotation in pinned consensus, including re-signed invalid payout rejection.
8
+
9
+ ## 0.8.0-fee.10
4
10
 
5
11
  - Add fee-market crossed settlement to the backend-only matcher, with exact payouts, preserved order deposits and one receipt.
6
12
  - Require a positive total matcher spending limit and reject mismatched or stale sources before submission.
@@ -5,6 +5,7 @@ exports.createKcc20PsktBuilderEngine = createKcc20PsktBuilderEngine;
5
5
  const token_authorization_js_1 = require("./token-authorization.js");
6
6
  const sha256_1 = require("@noble/hashes/sha256");
7
7
  const blake3_1 = require("@noble/hashes/blake3");
8
+ const blake2b_1 = require("@noble/hashes/blake2b");
8
9
  const token_profiles_js_1 = require("./token-profiles.js");
9
10
  const transfer_payload_js_1 = require("./transfer-payload.js");
10
11
  const token_profiles_generated_js_1 = require("./token-profiles.generated.js");
@@ -17,6 +18,7 @@ const escrow_market_artifacts_js_1 = require("./escrow-market-artifacts.js");
17
18
  const escrow_fee_js_1 = require("./escrow-fee.js");
18
19
  const escrow_policy_js_1 = require("./escrow-policy.js");
19
20
  const escrow_group_js_1 = require("./escrow-group.js");
21
+ const escrow_recipient_js_1 = require("./escrow-recipient.js");
20
22
  const escrow_receipt_js_1 = require("./escrow-receipt.js");
21
23
  const escrow_receipt_js_2 = require("./escrow-receipt.js");
22
24
  const escrow_claim_plan_js_1 = require("./escrow-claim-plan.js");
@@ -355,6 +357,7 @@ function createKcc20PsktBuilderEngine(options) {
355
357
  "fee-ticket.burn": buildFeeTicketBurnPskt,
356
358
  "fee-ticket.transfer": buildFeeTicketTransferPskt,
357
359
  "kcc20fee.create-account": buildFeeAccountCreatePskt,
360
+ "kcc20fee.create-recipient": buildFeeAccountCreatePskt,
358
361
  "kcc20fee.bind-account": buildFeeAccountBindPskt,
359
362
  "kcc20fee.deploy-market-genesis": buildFeeMarketGenesisPskt,
360
363
  "kcc20fee.bind-market": buildFeeMarketBindPskt,
@@ -410,6 +413,7 @@ function createKcc20PsktBuilderEngine(options) {
410
413
  async function buildFeeAccountCreatePskt(input) {
411
414
  const request = input.request || {};
412
415
  const params = request.params || {};
416
+ const recipient = request.builderKey === "kcc20fee.create-recipient";
413
417
  const tokenId = (0, sale_common_js_1.requireHex32)(params.tokenId, "fee account token id");
414
418
  const owner = (0, sale_common_js_1.requireHex32)(request.owner?.kcc20Owner, "fee account owner");
415
419
  const principalSompi = parseU64(params.principalSompi, "fee account principal");
@@ -417,12 +421,16 @@ function createKcc20PsktBuilderEngine(options) {
417
421
  const walletAddress = requireKaspaAddress(request.owner?.walletAddress);
418
422
  assertFeeAccountSchnorrWalletOwner(walletAddress, owner);
419
423
  const priorityFee = parseU64(env.KCC20_FEE_ACCOUNT_PRIORITY_FEE_SOMPI || DEFAULT_PRIORITY_FEE.toString(), "priorityFee");
420
- const artifactKey = "KCC20FeeEscrowV1.placeholder.json";
424
+ const artifactKey = recipient ? "KCC20FeeRecipientV1.placeholder.json" : "KCC20FeeEscrowV1.placeholder.json";
421
425
  const supplied = options.artifacts?.[artifactKey] ?? await options.artifactProvider?.(artifactKey);
422
426
  const artifact = supplied?.artifact ?? supplied;
423
427
  if (!artifact)
424
428
  throw new Error("missing pinned fee escrow artifact");
425
- const initial = (0, escrow_account_setup_js_1.buildKcc20InitialFeeAccount)({
429
+ const initial = recipient ? (0, escrow_recipient_js_1.buildKcc20InitialFeeRecipient)({
430
+ artifact, tokenId, ownerSchnorrPublicKey: owner, principalSompi,
431
+ marketId: (0, sale_common_js_1.requireHex32)(params.marketId, "recipient market"),
432
+ policyId: (0, sale_common_js_1.requireHex32)(params.policyId, "recipient policy"),
433
+ }) : (0, escrow_account_setup_js_1.buildKcc20InitialFeeAccount)({
426
434
  artifact, tokenId, ownerSchnorrPublicKey: owner, principalSompi,
427
435
  });
428
436
  const kw = kaspaWasm;
@@ -490,21 +498,21 @@ function createKcc20PsktBuilderEngine(options) {
490
498
  scripts: [],
491
499
  submitTransactionSupported: true,
492
500
  metadata: {
493
- builderKey: "kcc20fee.create-account",
494
- operation: "create-fee-account",
501
+ builderKey: request.builderKey,
502
+ operation: recipient ? "create-fee-recipient" : "create-fee-account",
495
503
  action: "genesis",
496
- contract: "KCC20FeeEscrowV1",
504
+ contract: recipient ? "KCC20FeeRecipientV1" : "KCC20FeeEscrowV1",
497
505
  network, walletAddress, tokenId, owner, accountId,
498
506
  accountPrincipalSompi: principalSompi.toString(),
499
507
  // Attach the broadcast txid to this hint before binding. The first
500
508
  // spend reveals the script; until then the indexer sees only P2SH.
501
- accountGenesisHint: {
509
+ [recipient ? "recipientGenesisHint" : "accountGenesisHint"]: {
502
510
  vout: 0,
503
511
  address: kw.addressFromScriptPublicKey(payToScriptHashScript(initial.redeemScript), network).toString(),
504
512
  covenantIdHex: accountId,
505
513
  scriptHashHex: initial.scriptHash,
506
514
  amountSompiExact: principalSompi.toString(),
507
- state: { ...initial.state, roleName: "account" },
515
+ state: { ...initial.state, roleName: recipient ? "recipient" : "account" },
508
516
  },
509
517
  },
510
518
  };
@@ -4407,6 +4415,7 @@ function createKcc20PsktBuilderEngine(options) {
4407
4415
  throw new Error("creator claim is disabled by this market policy");
4408
4416
  const indexedReceipts = params.receipts;
4409
4417
  const indexedAccount = params.account;
4418
+ const indexedRecipient = params.recipient;
4410
4419
  if (!Array.isArray(indexedReceipts) || indexedReceipts.length < 1 ||
4411
4420
  indexedReceipts.length > 5 || !indexedAccount) {
4412
4421
  throw new Error("fee-market claim requires one to five receipts and an account");
@@ -4417,12 +4426,17 @@ function createKcc20PsktBuilderEngine(options) {
4417
4426
  const accountOwnerScheme = Number(indexedAccount.state?.owner_scheme);
4418
4427
  const accountOwner = accountOwnerScheme === 1
4419
4428
  ? (0, sale_common_js_1.bytesToHex)((0, blake3_1.blake3)((0, sale_common_js_1.hexToBytes)(owner))) : owner;
4420
- if ((accountOwnerScheme !== 0 && accountOwnerScheme !== 1) ||
4421
- String(indexedAccount.state?.owner).toLowerCase() !== accountOwner) {
4429
+ const delegated = accountOwnerScheme === 4;
4430
+ if ((!delegated && ((accountOwnerScheme !== 0 && accountOwnerScheme !== 1) ||
4431
+ String(indexedAccount.state?.owner).toLowerCase() !== accountOwner)) ||
4432
+ (delegated && !indexedRecipient)) {
4422
4433
  throw new Error("fee account is not controlled by the signing wallet");
4423
4434
  }
4424
4435
  const network = request.network || env.KASPA_NETWORK || "testnet-10";
4425
4436
  const artifact = (await readFeeMarketArtifacts()).receipt;
4437
+ const recipientKey = "KCC20FeeRecipientV1.placeholder.json";
4438
+ const recipientSupplied = delegated ? (options.artifacts?.[recipientKey] ?? await options.artifactProvider?.(recipientKey)) : null;
4439
+ const recipientArtifact = recipientSupplied?.artifact ?? recipientSupplied;
4426
4440
  const kw = kaspaWasm;
4427
4441
  const { CovenantBinding, Encoding, Hash, RpcClient, Transaction, TransactionOutput, payToAddressScript, payToScriptHashScript } = kw;
4428
4442
  const payoutSpk = payToAddressScript(walletAddress);
@@ -4431,7 +4445,7 @@ function createKcc20PsktBuilderEngine(options) {
4431
4445
  new DataView(payoutBytes.buffer).setUint16(0, Number(payoutSpk.version), true);
4432
4446
  payoutBytes.set(payoutScript, 2);
4433
4447
  const priorityFee = parseU64(env.KCC20_FEE_MARKET_CLAIM_PRIORITY_FEE_SOMPI || DEFAULT_PRIORITY_FEE.toString(), "priorityFee");
4434
- const sourceAddresses = [...indexedReceipts, indexedAccount]
4448
+ const sourceAddresses = [...indexedReceipts, indexedAccount, ...(delegated ? [indexedRecipient] : [])]
4435
4449
  .map(item => requireKaspaAddress(item.address));
4436
4450
  const rpc = new RpcClient({ url: env.KASPA_WRPC_URL || DEFAULT_WRPC_URL,
4437
4451
  encoding: Encoding.Borsh, networkId: network });
@@ -4472,17 +4486,29 @@ function createKcc20PsktBuilderEngine(options) {
4472
4486
  escrowTemplate: checkedAccount.escrowTemplate,
4473
4487
  payoutScriptPublicKey: payoutBytes,
4474
4488
  });
4489
+ const recipientLive = delegated ? getLive(indexedRecipient, "fee recipient") : null;
4490
+ const approval = delegated ? (0, escrow_recipient_js_1.planKcc20FeeRecipientApproval)({
4491
+ artifact: recipientArtifact, indexed: indexedRecipient, live: recipientLive.live,
4492
+ account: checkedAccount.account, nextAccount: transition.successorAccountState,
4493
+ signingOwner: owner, action: 1,
4494
+ }) : null;
4495
+ if (approval)
4496
+ assertScriptAddress(kw, approval.currentRedeemScript, indexedRecipient.address, network, "fee recipient address");
4475
4497
  const successor = (0, escrow_receipt_js_1.buildKcc20FeeEscrowScript)(artifact, transition.successorAccountState);
4476
4498
  const remainder = transition.remainderReceiptState
4477
4499
  ? (0, escrow_receipt_js_1.buildKcc20FeeEscrowScript)(artifact, transition.remainderReceiptState)
4478
4500
  : null;
4479
4501
  const accountInputIndex = checkedReceipts.length;
4480
- const fundingInputIndex = accountInputIndex + 1;
4502
+ const recipientInputIndex = delegated ? accountInputIndex + 1 : -1;
4503
+ const fundingInputIndex = accountInputIndex + 1 + Number(delegated);
4481
4504
  const outputs = [new TransactionOutput(transition.claimSompi, payoutSpk),
4482
4505
  new TransactionOutput(transition.accountPrincipalSompi, payToScriptHashScript(successor.redeemScript), new CovenantBinding(accountInputIndex, new Hash(accountId)))];
4483
4506
  if (remainder)
4484
4507
  outputs.push(new TransactionOutput(transition.remainderSompi, payToScriptHashScript(remainder.redeemScript), new CovenantBinding(0, new Hash(marketId))));
4485
- const fundingEntry = (0, sale_common_js_1.selectFundingEntry)(walletUtxos.entries, priorityFee + 10000n, [...checkedReceipts.map(item => item.entry.outpoint), accountEntry.outpoint], params.fundingOutpoint);
4508
+ const recipientOutputIndex = delegated ? outputs.length : -1;
4509
+ if (approval)
4510
+ outputs.push(new TransactionOutput(approval.principalSompi, payToScriptHashScript(approval.successorRedeemScript), new CovenantBinding(recipientInputIndex, new Hash(approval.covenantId))));
4511
+ const fundingEntry = (0, sale_common_js_1.selectFundingEntry)(walletUtxos.entries, priorityFee + 10000n, [...checkedReceipts.map(item => item.entry.outpoint), accountEntry.outpoint, ...(recipientLive ? [recipientLive.entry.outpoint] : [])], params.fundingOutpoint);
4486
4512
  const fundingChangeIndex = outputs.length;
4487
4513
  outputs.push(new TransactionOutput((0, sale_common_js_1.utxoAmountSompi)(fundingEntry) - priorityFee, payoutSpk));
4488
4514
  const inputs = [
@@ -4492,6 +4518,8 @@ function createKcc20PsktBuilderEngine(options) {
4492
4518
  })),
4493
4519
  { previousOutpoint: accountEntry.outpoint, utxo: accountEntry,
4494
4520
  sequence: 0n, sigOpCount: 0, computeBudget: 100 },
4521
+ ...(recipientLive ? [{ previousOutpoint: recipientLive.entry.outpoint, utxo: recipientLive.entry,
4522
+ sequence: 0n, sigOpCount: 0, computeBudget: 100 }] : []),
4495
4523
  { previousOutpoint: fundingEntry.outpoint, utxo: fundingEntry,
4496
4524
  sequence: 0n, sigOpCount: 0, computeBudget: 30 },
4497
4525
  ];
@@ -4504,6 +4532,8 @@ function createKcc20PsktBuilderEngine(options) {
4504
4532
  });
4505
4533
  transaction.inputs[accountInputIndex].computeBudget = 100;
4506
4534
  transaction.inputs[fundingInputIndex].computeBudget = 30;
4535
+ if (delegated)
4536
+ transaction.inputs[recipientInputIndex].computeBudget = 100;
4507
4537
  assertUniqueTransactionInputOutpoints(transaction, "fee-market claim");
4508
4538
  for (const [index, checked] of checkedReceipts.entries()) {
4509
4539
  const prefix = covenantScriptBuilder(kw);
@@ -4515,7 +4545,13 @@ function createKcc20PsktBuilderEngine(options) {
4515
4545
  prefix.addData((0, abi_js_1.dispatchTagFor)(artifact, "claim"));
4516
4546
  transaction.inputs[index].signatureScript = encodeCovenantP2shSignatureScript(prefix.drain(), checked.redeemScript);
4517
4547
  }
4518
- const scripts = [{ inputIndex: accountInputIndex,
4548
+ const scripts = delegated ? [{ inputIndex: recipientInputIndex,
4549
+ scriptHex: (0, sale_common_js_1.bytesToHex)(approval.currentRedeemScript), signType: 1,
4550
+ signatureScript: { mode: "ordered-args", args: [
4551
+ { type: "signature" }, ...feeAccountStateTemplateArgs(approval.nextState),
4552
+ i64TemplateArg(recipientOutputIndex),
4553
+ dataTemplateArg((0, abi_js_1.dispatchTagFor)(recipientArtifact, "approveFeeAccountAction")),
4554
+ ] } }] : [{ inputIndex: accountInputIndex,
4519
4555
  scriptHex: (0, sale_common_js_1.bytesToHex)(checkedAccount.redeemScript), signType: 1,
4520
4556
  signatureScript: { mode: "ordered-args", args: [
4521
4557
  i64TemplateArg(1),
@@ -4528,6 +4564,24 @@ function createKcc20PsktBuilderEngine(options) {
4528
4564
  dataTemplateArg(new Uint8Array(0)),
4529
4565
  dataTemplateArg((0, abi_js_1.dispatchTagFor)(artifact, "authorize")),
4530
4566
  ] } }];
4567
+ if (approval) {
4568
+ const accountWitness = covenantScriptBuilder(kw);
4569
+ const args = [i64TemplateArg(1), ...feeAccountStateTemplateArgs(transition.successorAccountState),
4570
+ i64TemplateArg(1), dataTemplateArg(new Uint8Array(0)),
4571
+ i64TemplateArg(recipientInputIndex), i64TemplateArg(recipientOutputIndex),
4572
+ ...feeAccountStateTemplateArgs(approval.nextState),
4573
+ dataTemplateArg(approval.templatePrefix), dataTemplateArg(approval.templateSuffix),
4574
+ dataTemplateArg((0, abi_js_1.dispatchTagFor)(artifact, "authorize"))];
4575
+ for (const arg of args) {
4576
+ if (arg.type === "i64")
4577
+ accountWitness.addI64(BigInt(arg.value));
4578
+ else if (arg.type === "byte")
4579
+ addExplicitByte(accountWitness, arg.value);
4580
+ else
4581
+ accountWitness.addData((0, sale_common_js_1.hexToBytes)(arg.hex));
4582
+ }
4583
+ transaction.inputs[accountInputIndex].signatureScript = encodeCovenantP2shSignatureScript(accountWitness.drain(), checkedAccount.redeemScript);
4584
+ }
4531
4585
  const feeResult = transactionWithPredictedSignedFeeChange(kw, transaction, fundingChangeIndex, network, "fee-market claim", candidate => {
4532
4586
  const serialized = candidate.serializeToSafeJSON();
4533
4587
  if (typeof serialized !== "string")
@@ -4554,6 +4608,13 @@ function createKcc20PsktBuilderEngine(options) {
4554
4608
  metadata: { builderKey: "kcc20fee.claim", operation: "fee-market-claim",
4555
4609
  network, walletAddress, tokenId, marketId, policyId, party, accountId,
4556
4610
  receiptCount: checkedReceipts.length,
4611
+ recipientInputIndex, recipientOutputIndex,
4612
+ ...(approval ? { recipientContinuation: {
4613
+ covenantIdHex: approval.covenantId, vout: recipientOutputIndex,
4614
+ address: kw.addressFromScriptPublicKey(payToScriptHashScript(approval.successorRedeemScript), network).toString(),
4615
+ scriptHashHex: (0, sale_common_js_1.bytesToHex)((0, blake2b_1.blake2b)(approval.successorRedeemScript, { dkLen: 32 })),
4616
+ amountSompiExact: approval.principalSompi.toString(), state: { ...approval.nextState, roleName: "recipient" },
4617
+ } } : {}),
4557
4618
  claimSompi: transition.claimSompi.toString(),
4558
4619
  remainderSompi: transition.remainderSompi.toString(),
4559
4620
  payoutOutputIndex: 0, accountOutputIndex: 1,
@@ -4575,6 +4636,7 @@ function createKcc20PsktBuilderEngine(options) {
4575
4636
  const policyId = (0, sale_common_js_1.requireHex32)(params.policyId, "fee-market policy ID");
4576
4637
  const accountId = (0, sale_common_js_1.requireHex32)(params.accountId, "fee account ID");
4577
4638
  const indexedAccount = params.account;
4639
+ const indexedRecipient = params.recipient;
4578
4640
  if (!indexedAccount)
4579
4641
  throw new Error("current fee account UTXO is required");
4580
4642
  const walletAddress = requireKaspaAddress(request.owner?.walletAddress);
@@ -4583,8 +4645,10 @@ function createKcc20PsktBuilderEngine(options) {
4583
4645
  const accountOwnerScheme = Number(indexedAccount.state?.owner_scheme);
4584
4646
  const accountOwner = accountOwnerScheme === 1
4585
4647
  ? (0, sale_common_js_1.bytesToHex)((0, blake3_1.blake3)((0, sale_common_js_1.hexToBytes)(currentOwner))) : currentOwner;
4586
- if ((accountOwnerScheme !== 0 && accountOwnerScheme !== 1) ||
4587
- String(indexedAccount.state?.owner).toLowerCase() !== accountOwner) {
4648
+ const delegated = accountOwnerScheme === 4;
4649
+ if ((!delegated && ((accountOwnerScheme !== 0 && accountOwnerScheme !== 1) ||
4650
+ String(indexedAccount.state?.owner).toLowerCase() !== accountOwner)) ||
4651
+ (delegated && !indexedRecipient)) {
4588
4652
  throw new Error("fee account is not controlled by the signing wallet");
4589
4653
  }
4590
4654
  const newOwner = (0, sale_common_js_1.requireHex32)(params.newOwner, "new fee account owner");
@@ -4609,7 +4673,7 @@ function createKcc20PsktBuilderEngine(options) {
4609
4673
  (newScheme === 4 && (guardTemplate === zero || guardPrefix === 0n || guardSuffix === 0n))) {
4610
4674
  throw new Error("new fee account authority guard is invalid");
4611
4675
  }
4612
- if (newOwner === accountOwner && newScheme === accountOwnerScheme &&
4676
+ if (newOwner === String(indexedAccount.state.owner).toLowerCase() && newScheme === accountOwnerScheme &&
4613
4677
  guardTemplate === String(indexedAccount.state.guard_template).toLowerCase() &&
4614
4678
  guardPrefix === BigInt(indexedAccount.state.guard_prefix) &&
4615
4679
  guardSuffix === BigInt(indexedAccount.state.guard_suffix)) {
@@ -4617,6 +4681,9 @@ function createKcc20PsktBuilderEngine(options) {
4617
4681
  }
4618
4682
  const network = request.network || env.KASPA_NETWORK || "testnet-10";
4619
4683
  const artifact = (await readFeeMarketArtifacts()).receipt;
4684
+ const recipientKey = "KCC20FeeRecipientV1.placeholder.json";
4685
+ const recipientSupplied = delegated ? (options.artifacts?.[recipientKey] ?? await options.artifactProvider?.(recipientKey)) : null;
4686
+ const recipientArtifact = recipientSupplied?.artifact ?? recipientSupplied;
4620
4687
  const kw = kaspaWasm;
4621
4688
  if (newScheme === 3) {
4622
4689
  const redeemHex = String(params.newOwnerP2shRedeemScriptHex || "");
@@ -4636,7 +4703,7 @@ function createKcc20PsktBuilderEngine(options) {
4636
4703
  await rpc.connect();
4637
4704
  try {
4638
4705
  const [accountUtxos, walletUtxos] = await Promise.all([
4639
- getUtxosByAddresses(rpc, [accountAddress]),
4706
+ getUtxosByAddresses(rpc, [accountAddress, ...(delegated ? [requireKaspaAddress(indexedRecipient.address)] : [])]),
4640
4707
  getUtxosByAddresses(rpc, [walletAddress]),
4641
4708
  ]);
4642
4709
  const accountEntry = findUtxoEntry(accountUtxos.entries, (0, sale_common_js_1.requireHex32)(indexedAccount.txidHex, "account txid"), parseVout(indexedAccount.vout, "account vout"));
@@ -4657,25 +4724,43 @@ function createKcc20PsktBuilderEngine(options) {
4657
4724
  owner_scheme: newScheme, guard_template: guardTemplate,
4658
4725
  guard_prefix: guardPrefix.toString(), guard_suffix: guardSuffix.toString(),
4659
4726
  action_hash: zero };
4727
+ const recipientEntry = delegated ? findUtxoEntry(accountUtxos.entries, (0, sale_common_js_1.requireHex32)(indexedRecipient.txidHex, "recipient txid"), parseVout(indexedRecipient.vout, "recipient vout")) : null;
4728
+ if (delegated && !recipientEntry)
4729
+ throw new Error("fee recipient is spent or unavailable");
4730
+ const recipientSpk = recipientEntry?.scriptPublicKey ?? recipientEntry?.entry?.scriptPublicKey ?? recipientEntry?.utxoEntry?.scriptPublicKey;
4731
+ const approval = delegated ? (0, escrow_recipient_js_1.planKcc20FeeRecipientApproval)({ artifact: recipientArtifact,
4732
+ indexed: indexedRecipient, live: { outpoint: recipientEntry.outpoint, amountSompi: (0, sale_common_js_1.utxoAmountSompi)(recipientEntry),
4733
+ covenantIdHex: requireMatchingCovenantId(recipientEntry, indexedRecipient.covenantIdHex, "recipient live"),
4734
+ scriptPublicKey: { version: Number(recipientSpk?.version), scriptHex: String(recipientSpk?.script).toLowerCase() } },
4735
+ account: checked.account, nextAccount: nextState, signingOwner: currentOwner, action: 2 }) : null;
4736
+ if (approval)
4737
+ assertScriptAddress(kw, approval.currentRedeemScript, indexedRecipient.address, network, "fee recipient address");
4738
+ const fundingInputIndex = delegated ? 2 : 1;
4739
+ const fundingChangeIndex = delegated ? 2 : 1;
4660
4740
  const successor = (0, escrow_receipt_js_1.buildKcc20FeeEscrowScript)(artifact, nextState);
4661
- const fundingEntry = (0, sale_common_js_1.selectFundingEntry)(walletUtxos.entries, priorityFee + 10000n, [accountEntry.outpoint], params.fundingOutpoint);
4741
+ const fundingEntry = (0, sale_common_js_1.selectFundingEntry)(walletUtxos.entries, priorityFee + 10000n, [accountEntry.outpoint, ...(recipientEntry ? [recipientEntry.outpoint] : [])], params.fundingOutpoint);
4662
4742
  const fundingChange = (0, sale_common_js_1.utxoAmountSompi)(fundingEntry) - priorityFee;
4663
4743
  if (fundingChange <= 10000n)
4664
4744
  throw new Error("fee account rotation funding is too small");
4665
4745
  const outputs = [new TransactionOutput(checked.account.principalSompi, payToScriptHashScript(successor.redeemScript), new CovenantBinding(0, new Hash(accountId))),
4746
+ ...(approval ? [new TransactionOutput(approval.principalSompi, payToScriptHashScript(approval.successorRedeemScript), new CovenantBinding(1, new Hash(approval.covenantId)))] : []),
4666
4747
  new TransactionOutput(fundingChange, payToAddressScript(walletAddress))];
4667
4748
  const transaction = new Transaction({ version: 1, lockTime: 0n,
4668
4749
  inputs: [
4669
4750
  { previousOutpoint: accountEntry.outpoint, utxo: accountEntry,
4670
4751
  sequence: 0n, sigOpCount: 0, computeBudget: 100 },
4752
+ ...(recipientEntry ? [{ previousOutpoint: recipientEntry.outpoint, utxo: recipientEntry,
4753
+ sequence: 0n, sigOpCount: 0, computeBudget: 100 }] : []),
4671
4754
  { previousOutpoint: fundingEntry.outpoint, utxo: fundingEntry,
4672
4755
  sequence: 0n, sigOpCount: 0, computeBudget: 30 },
4673
4756
  ], outputs, subnetworkId: sale_common_js_1.SUBNETWORK_ID_NATIVE, gas: 0n, payload: "",
4674
4757
  });
4675
4758
  (0, sale_common_js_1.setVersionOneInputMassFields)(transaction, 100);
4676
- transaction.inputs[1].computeBudget = 30;
4759
+ transaction.inputs[fundingInputIndex].computeBudget = 30;
4677
4760
  assertUniqueTransactionInputOutpoints(transaction, "fee account rotation");
4678
- const scripts = [{ inputIndex: 0, scriptHex: (0, sale_common_js_1.bytesToHex)(checked.redeemScript),
4761
+ const scripts = approval ? [{ inputIndex: 1, scriptHex: (0, sale_common_js_1.bytesToHex)(approval.currentRedeemScript), signType: 1,
4762
+ signatureScript: { mode: "ordered-args", args: [{ type: "signature" }, ...feeAccountStateTemplateArgs(approval.nextState),
4763
+ i64TemplateArg(1), dataTemplateArg((0, abi_js_1.dispatchTagFor)(recipientArtifact, "approveFeeAccountAction"))] } }] : [{ inputIndex: 0, scriptHex: (0, sale_common_js_1.bytesToHex)(checked.redeemScript),
4679
4764
  signType: 1, signatureScript: { mode: "ordered-args", args: [
4680
4765
  i64TemplateArg(2), ...feeAccountStateTemplateArgs(nextState),
4681
4766
  i64TemplateArg(0), { type: "signature",
@@ -4686,12 +4771,28 @@ function createKcc20PsktBuilderEngine(options) {
4686
4771
  dataTemplateArg(new Uint8Array(0)),
4687
4772
  dataTemplateArg((0, abi_js_1.dispatchTagFor)(artifact, "authorize")),
4688
4773
  ] } }];
4689
- const feeResult = transactionWithPredictedSignedFeeChange(kw, transaction, 1, network, "fee account rotation", candidate => {
4774
+ if (approval) {
4775
+ const witness = covenantScriptBuilder(kw);
4776
+ const args = [i64TemplateArg(2), ...feeAccountStateTemplateArgs(nextState), i64TemplateArg(0),
4777
+ dataTemplateArg(new Uint8Array(0)), i64TemplateArg(1), i64TemplateArg(1),
4778
+ ...feeAccountStateTemplateArgs(approval.nextState), dataTemplateArg(approval.templatePrefix),
4779
+ dataTemplateArg(approval.templateSuffix), dataTemplateArg((0, abi_js_1.dispatchTagFor)(artifact, "authorize"))];
4780
+ for (const arg of args) {
4781
+ if (arg.type === "i64")
4782
+ witness.addI64(BigInt(arg.value));
4783
+ else if (arg.type === "byte")
4784
+ addExplicitByte(witness, arg.value);
4785
+ else
4786
+ witness.addData((0, sale_common_js_1.hexToBytes)(arg.hex));
4787
+ }
4788
+ transaction.inputs[0].signatureScript = encodeCovenantP2shSignatureScript(witness.drain(), checked.redeemScript);
4789
+ }
4790
+ const feeResult = transactionWithPredictedSignedFeeChange(kw, transaction, fundingChangeIndex, network, "fee account rotation", candidate => {
4690
4791
  const serialized = candidate.serializeToSafeJSON();
4691
4792
  if (typeof serialized !== "string")
4692
4793
  throw new Error("rotation preflight serialization failed");
4693
4794
  const predictedJson = transactionJsonWithScriptHints(kw, JSON.parse(serialized), scripts);
4694
- predictedJson.inputs[1].signatureScript = encodeP2pkSignatureScript((0, sale_common_js_1.bytesToHex)(dummySignatureBytes()));
4795
+ predictedJson.inputs[fundingInputIndex].signatureScript = encodeP2pkSignatureScript((0, sale_common_js_1.bytesToHex)(dummySignatureBytes()));
4695
4796
  return typeof kw.Transaction.deserializeFromSafeJSON === "function"
4696
4797
  ? kw.Transaction.deserializeFromSafeJSON(JSON.stringify(predictedJson))
4697
4798
  : kw.Transaction.deserializeFromJSON(JSON.stringify(predictedJson));
@@ -4706,13 +4807,14 @@ function createKcc20PsktBuilderEngine(options) {
4706
4807
  }
4707
4808
  return { schema: OUTPUT_SCHEMA,
4708
4809
  psktTransactionJson: feeResult.transaction.serializeToSafeJSON(),
4709
- signInputs: [{ index: 1, sighashType: 1 }], scripts,
4810
+ signInputs: [{ index: fundingInputIndex, sighashType: 1 }], scripts,
4710
4811
  submitTransactionSupported: true,
4711
4812
  metadata: { builderKey: "kcc20fee.rotate-account",
4712
4813
  operation: "fee-market-rotate-account", network, walletAddress,
4713
4814
  tokenId, marketId, policyId, accountId, newOwner,
4714
4815
  newOwnerScheme: newScheme, accountOutputIndex: 0,
4715
4816
  transfersAllUnclaimedEntitlements: true,
4817
+ recipientInputIndex: delegated ? 1 : -1, recipientOutputIndex: delegated ? 1 : -1,
4716
4818
  signedMass: { compute: signedMass.computeMass.toString(),
4717
4819
  transient: signedMass.transientMass.toString(),
4718
4820
  storage: storageMass.toString() } },