@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.
- package/CHANGELOG.md +7 -1
- package/dist/cjs/engine.js +125 -23
- package/dist/cjs/engine.js.map +1 -1
- package/dist/cjs/escrow-account-setup.d.ts +2 -0
- package/dist/cjs/escrow-account-setup.js +1 -0
- package/dist/cjs/escrow-account-setup.js.map +1 -1
- package/dist/cjs/escrow-receipt.d.ts +1 -0
- package/dist/cjs/escrow-receipt.js +5 -4
- package/dist/cjs/escrow-receipt.js.map +1 -1
- package/dist/cjs/escrow-recipient.d.ts +63 -0
- package/dist/cjs/escrow-recipient.js +119 -0
- package/dist/cjs/escrow-recipient.js.map +1 -0
- package/dist/cjs/index.d.ts +1 -0
- package/dist/cjs/index.js +1 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/operations.d.ts +1 -1
- package/dist/cjs/operations.js +1 -0
- package/dist/cjs/operations.js.map +1 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +125 -23
- package/dist/engine.js.map +1 -1
- package/dist/escrow-account-setup.d.ts +2 -0
- package/dist/escrow-account-setup.d.ts.map +1 -1
- package/dist/escrow-account-setup.js +1 -1
- package/dist/escrow-account-setup.js.map +1 -1
- package/dist/escrow-receipt.d.ts +1 -0
- package/dist/escrow-receipt.d.ts.map +1 -1
- package/dist/escrow-receipt.js +4 -4
- package/dist/escrow-receipt.js.map +1 -1
- package/dist/escrow-recipient.d.ts +64 -0
- package/dist/escrow-recipient.d.ts.map +1 -0
- package/dist/escrow-recipient.js +115 -0
- package/dist/escrow-recipient.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/operations.d.ts +1 -1
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +1 -0
- package/dist/operations.js.map +1 -1
- package/docs/COMPATIBILITY.json +1 -1
- package/docs/fee-recipient-wallet.md +13 -0
- package/package.json +3 -3
- package/src/engine.ts +119 -23
- package/src/escrow-account-setup.ts +1 -1
- package/src/escrow-receipt.ts +4 -4
- package/src/escrow-recipient.ts +117 -0
- package/src/index.ts +1 -0
- package/src/operations.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.8.0-fee.
|
|
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.
|
package/dist/cjs/engine.js
CHANGED
|
@@ -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,
|
|
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:
|
|
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
|
-
|
|
4421
|
-
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
|
|
4587
|
-
|
|
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 ===
|
|
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[
|
|
4759
|
+
transaction.inputs[fundingInputIndex].computeBudget = 30;
|
|
4677
4760
|
assertUniqueTransactionInputOutpoints(transaction, "fee account rotation");
|
|
4678
|
-
const scripts = [{ inputIndex:
|
|
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
|
-
|
|
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[
|
|
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:
|
|
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() } },
|