@kaspacom/kcc20-tx-builder 0.7.0 → 0.8.0-fee.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.
- package/CHANGELOG.md +11 -1
- package/RELEASING.md +9 -5
- package/artifacts/KCC20FeeEscrowV1.placeholder.json +75648 -0
- package/artifacts/KCC20FeePolicyV1.manifest.json +86 -0
- package/artifacts/KCC20FeeRecipientV1.placeholder.json +3493 -0
- package/artifacts/KCC20OrderbookFeeAskV1.placeholder.json +199068 -0
- package/artifacts/KCC20OrderbookFeeBidV1.placeholder.json +13815 -0
- package/artifacts/KCC20OrderbookFeeHolderV1.placeholder.json +171404 -0
- package/artifacts/KCC20OrderbookFeeRootV1.placeholder.json +11488 -0
- package/artifacts/KCC20WrapperFeeArgentV1.placeholder.json +28365 -0
- package/artifacts/KCC20WrapperFeeV1.placeholder.json +27233 -0
- package/dist/artifacts.d.ts +20 -0
- package/dist/artifacts.d.ts.map +1 -1
- package/dist/artifacts.js +20 -0
- package/dist/artifacts.js.map +1 -1
- package/dist/cjs/artifacts.d.ts +20 -0
- package/dist/cjs/artifacts.js +21 -1
- package/dist/cjs/artifacts.js.map +1 -1
- package/dist/cjs/escrow-fee.d.ts +79 -0
- package/dist/cjs/escrow-fee.js +155 -0
- package/dist/cjs/escrow-fee.js.map +1 -0
- package/dist/cjs/escrow-group.d.ts +22 -0
- package/dist/cjs/escrow-group.js +80 -0
- package/dist/cjs/escrow-group.js.map +1 -0
- package/dist/cjs/escrow-policy.d.ts +72 -0
- package/dist/cjs/escrow-policy.js +118 -0
- package/dist/cjs/escrow-policy.js.map +1 -0
- package/dist/cjs/index.d.ts +3 -0
- package/dist/cjs/index.js +3 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/escrow-fee.d.ts +80 -0
- package/dist/escrow-fee.d.ts.map +1 -0
- package/dist/escrow-fee.js +148 -0
- package/dist/escrow-fee.js.map +1 -0
- package/dist/escrow-group.d.ts +23 -0
- package/dist/escrow-group.d.ts.map +1 -0
- package/dist/escrow-group.js +75 -0
- package/dist/escrow-group.js.map +1 -0
- package/dist/escrow-policy.d.ts +73 -0
- package/dist/escrow-policy.d.ts.map +1 -0
- package/dist/escrow-policy.js +111 -0
- package/dist/escrow-policy.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/docs/COMPATIBILITY.json +1 -1
- package/docs/escrow-fees.md +56 -0
- package/package.json +3 -2
- package/src/artifacts.ts +31 -0
- package/src/escrow-fee.ts +181 -0
- package/src/escrow-group.ts +105 -0
- package/src/escrow-policy.ts +130 -0
- package/src/index.ts +3 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { blake3 } from "@noble/hashes/blake3";
|
|
2
|
+
import { blake2b } from "@noble/hashes/blake2b";
|
|
3
|
+
import { calculateKcc20FeeClaim, type Kcc20FeeReceiptAmounts } from "./escrow-fee.js";
|
|
4
|
+
|
|
5
|
+
/** Fixed 361-byte policy for the versioned fee roles. Decoding is not artifact authentication. */
|
|
6
|
+
export interface Kcc20EscrowPolicy {
|
|
7
|
+
version: 1;
|
|
8
|
+
creatorFeeBps: number;
|
|
9
|
+
protocolAccountId: string;
|
|
10
|
+
creatorAccountId: string;
|
|
11
|
+
receiptTemplate: { hash: string; prefixLength: number; suffixLength: number };
|
|
12
|
+
rootTemplate: { hash: string; prefixLength: number; suffixLength: number };
|
|
13
|
+
holderTemplate: { hash: string; prefixLength: number; suffixLength: number };
|
|
14
|
+
askTemplate: { hash: string; prefixLength: number; suffixLength: number };
|
|
15
|
+
bidTemplate: { hash: string; prefixLength: number; suffixLength: number };
|
|
16
|
+
ticketTemplate: { hash: string; prefixLength: number; suffixLength: number };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const ZERO = "0".repeat(64);
|
|
20
|
+
function hex32(value: string, allowZero = false): Uint8Array {
|
|
21
|
+
if (typeof value !== "string" || !/^[0-9a-fA-F]{64}$/.test(value) || (!allowZero && value === ZERO)) {
|
|
22
|
+
throw new Error("expected nonzero 32-byte identity");
|
|
23
|
+
}
|
|
24
|
+
return Uint8Array.from(value.match(/../g)!.map(byte => Number.parseInt(byte, 16)));
|
|
25
|
+
}
|
|
26
|
+
function hex(value: Uint8Array): string {
|
|
27
|
+
return Array.from(value, byte => byte.toString(16).padStart(2, "0")).join("");
|
|
28
|
+
}
|
|
29
|
+
function int64(value: bigint): Uint8Array {
|
|
30
|
+
if (typeof value !== "bigint" || value < 0n || value > 0x7fff_ffff_ffff_ffffn) throw new Error("expected nonnegative int64");
|
|
31
|
+
const result = new Uint8Array(8);
|
|
32
|
+
new DataView(result.buffer).setBigUint64(0, value, true);
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
function concat(...parts: Uint8Array[]): Uint8Array {
|
|
36
|
+
const result = new Uint8Array(parts.reduce((length, part) => length + part.length, 0));
|
|
37
|
+
let offset = 0;
|
|
38
|
+
for (const part of parts) { result.set(part, offset); offset += part.length; }
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
function template(value: Kcc20EscrowPolicy["receiptTemplate"]): Uint8Array {
|
|
42
|
+
for (const length of [value.prefixLength, value.suffixLength]) {
|
|
43
|
+
if (!Number.isSafeInteger(length) || length <= 0) throw new Error("template lengths must be positive safe integers");
|
|
44
|
+
}
|
|
45
|
+
return concat(hex32(value.hash), int64(BigInt(value.prefixLength)), int64(BigInt(value.suffixLength)));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function encodeKcc20EscrowPolicy(policy: Kcc20EscrowPolicy): Uint8Array {
|
|
49
|
+
if (policy.version !== 1) throw new Error("unsupported fee policy version");
|
|
50
|
+
if (!Number.isInteger(policy.creatorFeeBps) || policy.creatorFeeBps < 0 || policy.creatorFeeBps > 500) throw new Error("creator rate must be 0 to 500 bps");
|
|
51
|
+
const protocol = hex32(policy.protocolAccountId);
|
|
52
|
+
const creator = hex32(policy.creatorAccountId, policy.creatorFeeBps === 0);
|
|
53
|
+
if (hex(protocol) === hex(creator)) throw new Error("fee account identities must differ");
|
|
54
|
+
if ((policy.creatorFeeBps === 0) !== (hex(creator) === ZERO)) throw new Error("creator rate and account do not match");
|
|
55
|
+
return concat(Uint8Array.of(1), int64(BigInt(policy.creatorFeeBps)), protocol, creator,
|
|
56
|
+
template(policy.receiptTemplate), template(policy.rootTemplate), template(policy.holderTemplate),
|
|
57
|
+
template(policy.askTemplate), template(policy.bidTemplate), template(policy.ticketTemplate));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function decodeKcc20EscrowPolicy(bytes: Uint8Array): Kcc20EscrowPolicy {
|
|
61
|
+
if (!(bytes instanceof Uint8Array) || bytes.length !== 361 || bytes[0] !== 1) throw new Error("invalid fee policy encoding");
|
|
62
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
63
|
+
const integer = (offset: number): number => {
|
|
64
|
+
const value = view.getBigUint64(offset, true);
|
|
65
|
+
if (value > BigInt(Number.MAX_SAFE_INTEGER)) throw new Error("noncanonical or oversized policy integer");
|
|
66
|
+
return Number(value);
|
|
67
|
+
};
|
|
68
|
+
const readTemplate = (offset: number) => ({ hash: hex(bytes.slice(offset, offset + 32)),
|
|
69
|
+
prefixLength: integer(offset + 32), suffixLength: integer(offset + 40) });
|
|
70
|
+
const policy: Kcc20EscrowPolicy = {
|
|
71
|
+
version: 1, creatorFeeBps: integer(1),
|
|
72
|
+
protocolAccountId: hex(bytes.slice(9, 41)), creatorAccountId: hex(bytes.slice(41, 73)),
|
|
73
|
+
receiptTemplate: readTemplate(73), rootTemplate: readTemplate(121),
|
|
74
|
+
holderTemplate: readTemplate(169), askTemplate: readTemplate(217),
|
|
75
|
+
bidTemplate: readTemplate(265), ticketTemplate: readTemplate(313),
|
|
76
|
+
};
|
|
77
|
+
encodeKcc20EscrowPolicy(policy);
|
|
78
|
+
return policy;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function kcc20EscrowPolicyId(input: {
|
|
82
|
+
tokenId: string; feeTicketId: string; priceScale: bigint; policy: Kcc20EscrowPolicy;
|
|
83
|
+
}): string {
|
|
84
|
+
if (typeof input.priceScale !== "bigint" || input.priceScale <= 0n) throw new Error("priceScale must be positive");
|
|
85
|
+
return hex(blake3(concat(hex32(input.tokenId), hex32(input.feeTicketId, true),
|
|
86
|
+
int64(input.priceScale), encodeKcc20EscrowPolicy(input.policy))));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Public consent can be reused only for this TN10 market and fixed fee policy.
|
|
90
|
+
* Send `message` to the wallet's standard personal-message signing API. A wallet
|
|
91
|
+
* signature is still required; creating this payload does not establish authority.
|
|
92
|
+
*/
|
|
93
|
+
export function kcc20CreatorFeeConsent(input: {
|
|
94
|
+
tokenId: string; marketId: string; feeTicketId: string; priceScale: bigint; policy: Kcc20EscrowPolicy;
|
|
95
|
+
}): { commitment: string; message: string; personalMessageHash: string } {
|
|
96
|
+
if (typeof input.priceScale !== "bigint" || input.priceScale <= 0n) throw new Error("priceScale must be positive");
|
|
97
|
+
const policy = encodeKcc20EscrowPolicy(input.policy);
|
|
98
|
+
if (input.policy.creatorFeeBps === 0) throw new Error("creator consent is only used when creator fees are enabled");
|
|
99
|
+
const commitment = hex(blake3(concat(Uint8Array.of(0xc1), hex32(input.tokenId), hex32(input.marketId),
|
|
100
|
+
hex32(input.feeTicketId, true), int64(input.priceScale), policy)));
|
|
101
|
+
const message = `KCC20 creator fee consent v1 (TN10)\n${commitment}`;
|
|
102
|
+
const encoder = new TextEncoder();
|
|
103
|
+
const personalMessageHash = hex(blake2b(encoder.encode(message), {
|
|
104
|
+
dkLen: 32, key: encoder.encode("PersonalMessageSigningHash"),
|
|
105
|
+
}));
|
|
106
|
+
return { commitment, message, personalMessageHash };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function outpoint(value: { transactionId: string; index: number }): Uint8Array {
|
|
110
|
+
if (!Number.isInteger(value.index) || value.index < 0 || value.index > 0xffffffff) throw new Error("outpoint index must be uint32");
|
|
111
|
+
return concat(hex32(value.transactionId, true), int64(BigInt(value.index)));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Input order must match the covenant family's consensus input order. */
|
|
115
|
+
export function kcc20FeeClaimCommitment(input: {
|
|
116
|
+
receipts: readonly Kcc20FeeReceiptAmounts[];
|
|
117
|
+
party: "protocol" | "creator";
|
|
118
|
+
accountOutpoint: { transactionId: string; index: number };
|
|
119
|
+
/** Serialized ScriptPublicKey, including its two-byte little-endian version. */
|
|
120
|
+
payoutScriptPublicKey: Uint8Array;
|
|
121
|
+
}): string {
|
|
122
|
+
const amounts = calculateKcc20FeeClaim(input.receipts, input.party);
|
|
123
|
+
if (!(input.payoutScriptPublicKey instanceof Uint8Array) || input.payoutScriptPublicKey.length < 3) throw new Error("serialized payout ScriptPublicKey is required");
|
|
124
|
+
let receiptHash: Uint8Array = new Uint8Array(32);
|
|
125
|
+
for (const receipt of input.receipts) receiptHash = blake3(concat(receiptHash, outpoint(receipt.outpoint)));
|
|
126
|
+
const first = input.receipts[0];
|
|
127
|
+
return hex(blake3(concat(Uint8Array.of(0xf1), hex32(first.tokenId), hex32(first.marketId), hex32(first.policyId),
|
|
128
|
+
receiptHash, outpoint(input.accountOutpoint), int64(input.party === "creator" ? 1n : 0n),
|
|
129
|
+
int64(amounts.claimSompi), int64(amounts.remainderSompi), blake3(input.payoutScriptPublicKey))));
|
|
130
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -18,6 +18,9 @@ export * from "./paid-mint-amount.js";
|
|
|
18
18
|
export * from "./token-amount.js";
|
|
19
19
|
export * from "./token-operation.js";
|
|
20
20
|
export * from "./trade-fee.js";
|
|
21
|
+
export * from "./escrow-fee.js";
|
|
22
|
+
export * from "./escrow-policy.js";
|
|
23
|
+
export * from "./escrow-group.js";
|
|
21
24
|
export * from "./trading-operation.js";
|
|
22
25
|
export * from "./protocol.js";
|
|
23
26
|
export * from "./runtime.js";
|