@kaspacom/kcc20-tx-builder 0.6.3 → 0.8.0-fee.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/CHANGELOG.md +12 -0
- package/RELEASING.md +9 -5
- package/artifacts/KCC20SnapshotBootstrap.placeholder.json +14577 -7985
- package/artifacts/KCC20SnapshotController.placeholder.json +978 -813
- package/dist/abi.d.ts.map +1 -1
- package/dist/abi.js +32 -1
- package/dist/abi.js.map +1 -1
- package/dist/artifacts.d.ts +7 -7
- package/dist/artifacts.js +7 -7
- package/dist/cjs/abi.js +32 -1
- package/dist/cjs/abi.js.map +1 -1
- package/dist/cjs/artifacts.d.ts +7 -7
- package/dist/cjs/artifacts.js +7 -7
- 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-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 +2 -0
- package/dist/cjs/index.js +2 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/snapshot-deployment.js +2 -1
- package/dist/cjs/snapshot-deployment.js.map +1 -1
- package/dist/cjs/snapshot-operation.js +17 -1
- package/dist/cjs/snapshot-operation.js.map +1 -1
- package/dist/cjs/snapshot-signerless.js +26 -6
- package/dist/cjs/snapshot-signerless.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-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 +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/snapshot-deployment.d.ts.map +1 -1
- package/dist/snapshot-deployment.js +2 -1
- package/dist/snapshot-deployment.js.map +1 -1
- package/dist/snapshot-operation.d.ts.map +1 -1
- package/dist/snapshot-operation.js +17 -1
- package/dist/snapshot-operation.js.map +1 -1
- package/dist/snapshot-signerless.d.ts.map +1 -1
- package/dist/snapshot-signerless.js +26 -6
- package/dist/snapshot-signerless.js.map +1 -1
- package/docs/COMPATIBILITY.json +26 -14
- package/docs/escrow-fees.md +56 -0
- package/package.json +5 -2
- package/src/abi.ts +46 -6
- package/src/artifacts.ts +7 -7
- package/src/escrow-fee.ts +181 -0
- package/src/escrow-policy.ts +130 -0
- package/src/index.ts +2 -0
- package/src/snapshot-deployment.ts +2 -1
- package/src/snapshot-operation.ts +17 -1
- package/src/snapshot-signerless.ts +30 -6
- package/artifacts/kcc20-snapshot-bootstrap/KCC20SnapshotBootstrap.placeholder.json +0 -101678
- package/artifacts/kcc20-snapshot-controller/KCC20SnapshotController.placeholder.json +0 -46548
package/docs/COMPATIBILITY.json
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": "kcc20-example-compatibility/v1",
|
|
3
|
-
"packageVersion": "0.
|
|
3
|
+
"packageVersion": "0.8.0-fee.0",
|
|
4
4
|
"contractsRepository": "https://github.com/KASPACOM/kaspa-covenants",
|
|
5
|
-
"contractsCommit":
|
|
5
|
+
"contractsCommit": "5deb1769e69142595576ef7c573af9f433a48a3c",
|
|
6
6
|
"compiler": {
|
|
7
7
|
"repository": "https://github.com/kaspanet/silverscript",
|
|
8
8
|
"tag": "v1.0.0",
|
|
9
9
|
"commit": "3ed973335b59269293564805cc2c58a14595ec03",
|
|
10
|
-
"tree": "2a30f522768d9b4cae6fe30014326c689a25bd41"
|
|
11
|
-
"build": "verified-source"
|
|
10
|
+
"tree": "2a30f522768d9b4cae6fe30014326c689a25bd41"
|
|
12
11
|
},
|
|
13
12
|
"wasm": {
|
|
14
13
|
"version": "1.1.1-toc.1",
|
|
@@ -31,45 +30,58 @@
|
|
|
31
30
|
"scriptSha256": "ef9fe81ff7aa2dc2df118d8d7cc126aca6f2f9c181b9c2d15727a538b23c6f71",
|
|
32
31
|
"sourceSha256": "b4216aa3db10f486b2032fa52eb93ec1768c65ea841415bd2fffb85bdc652bbb",
|
|
33
32
|
"constructorArgsSha256": "a3f32dc91fee760df98d0238d60f65a6d1324bce5ab0740e31f035144a90cc61",
|
|
34
|
-
"abiSha256": "bc5c24dc0e7ccfdd734981bfa81c806672ae90059fd075e93c9254671b25f155"
|
|
33
|
+
"abiSha256": "bc5c24dc0e7ccfdd734981bfa81c806672ae90059fd075e93c9254671b25f155",
|
|
34
|
+
"compilerBuild": "verified-source"
|
|
35
35
|
},
|
|
36
36
|
"KCC20Wrapper.placeholder.json": {
|
|
37
37
|
"fileSha256": "2461e39a285d1f135f4891659cb78b3b6e5d2b28b32e153b61e2e336eab02115",
|
|
38
38
|
"scriptSha256": "23cbd5473860cab4e63890d24ae1bd6732fce5beaa34f594dad01e845e3e9b9f",
|
|
39
39
|
"sourceSha256": "030708f5b9c9e5cf7d1c4c9b9d699a681d11fa197635c3225f321dadb92ae37a",
|
|
40
40
|
"constructorArgsSha256": "085f7457aa6456ef49ea3b3181739943d17b498d82fc2c8b6ab61a6f51f95573",
|
|
41
|
-
"abiSha256": "d4f2fdfd16cd0cd6b42e863de90a8bae90e5b44d9495b85a32f287f88750df2c"
|
|
41
|
+
"abiSha256": "d4f2fdfd16cd0cd6b42e863de90a8bae90e5b44d9495b85a32f287f88750df2c",
|
|
42
|
+
"compilerBuild": "verified-source"
|
|
42
43
|
},
|
|
43
44
|
"KCC20.placeholder.json": {
|
|
44
45
|
"fileSha256": "a45d06e8351421dac1ef73f3595c6f34068e4fa1e13d3f8fa1c88be518c6e56f",
|
|
45
46
|
"scriptSha256": "dd9cb334cd9e9220b15644782f271ef3ecdba34fd0762010e3d6bc18176837ae",
|
|
46
47
|
"sourceSha256": "9ae9b13ebc56cd85f072ea6cc78d3a5e5f050b66dda62677e223bc9c29ec231e",
|
|
47
48
|
"constructorArgsSha256": "c86edb17c8e7a338bf35b519caa3c4f955bcf5ab73476f9c5a3baa34bb73926a",
|
|
48
|
-
"abiSha256": "c2a169d67682b49ce3501f288e73f18ca96c815fd47db8e614d75b20f3678802"
|
|
49
|
+
"abiSha256": "c2a169d67682b49ce3501f288e73f18ca96c815fd47db8e614d75b20f3678802",
|
|
50
|
+
"compilerBuild": "verified-source"
|
|
49
51
|
},
|
|
50
52
|
"KCC20FeeTicket.placeholder.json": {
|
|
51
53
|
"fileSha256": "b104ff07adcfe58a1db134bb44782fa59453dadb7716e0a831447f910efb9526",
|
|
52
54
|
"scriptSha256": "c0e95cb8e5226462674647f86d5760626b5c38b7bd97a6e2ec9ba61fdef8941f",
|
|
53
55
|
"sourceSha256": "980294c10d9af5d79218a5d81100df30da7ca80e3bf5c284adac3c8803b51d49",
|
|
54
56
|
"constructorArgsSha256": "66b2ca443a59de6854f29bf2e8aa9c11a3074707070d653310432ac1c31ec81f",
|
|
55
|
-
"abiSha256": "8bb5f66f91bc296380eaa6b5bcc7d5045b59c2b4783e8b1bcf394a7e1395a420"
|
|
57
|
+
"abiSha256": "8bb5f66f91bc296380eaa6b5bcc7d5045b59c2b4783e8b1bcf394a7e1395a420",
|
|
58
|
+
"compilerBuild": "verified-source"
|
|
56
59
|
},
|
|
57
60
|
"KCC20Vesting.placeholder.json": {
|
|
58
61
|
"fileSha256": "b917c9dcd05237fdedf76ac6b0df5791d5953aca1d057909c5fac075fb17ca43",
|
|
59
62
|
"scriptSha256": "e32d9f5bde124526475a61075ba46eef41955d4662b1422713860cfbd7a59661",
|
|
60
63
|
"sourceSha256": "6f32a53c6212bd985c2fad48b3dc66ee0daffd5ac411e5676268d22eccbfa5b3",
|
|
61
64
|
"constructorArgsSha256": "b1adf7afaf5daa150e7bb54e8ddfdc63fae5a23a9b30ad528435ce6346205b40",
|
|
62
|
-
"abiSha256": "b58ee502376aaf59e0b2d663012b2964a3ae7b30025b2d830e46ea18993e7dc7"
|
|
65
|
+
"abiSha256": "b58ee502376aaf59e0b2d663012b2964a3ae7b30025b2d830e46ea18993e7dc7",
|
|
66
|
+
"compilerBuild": "verified-source"
|
|
63
67
|
},
|
|
64
68
|
"KCC20SnapshotBootstrap.placeholder.json": {
|
|
65
|
-
"fileSha256": "
|
|
66
|
-
"scriptSha256": "
|
|
69
|
+
"fileSha256": "daddabe50454e1c6c187c0b027cc8887f79f0f0086a14f26c6a2a56054991ebb",
|
|
70
|
+
"scriptSha256": "a22b93b0d8cd04096d83d25bbb6c38f0c07ac025b70226677db484fa93fe09ef",
|
|
71
|
+
"sourceSha256": "6e6e6a8a5d12b68d0bf5a67c4e9a727dbed4605c62896f869c73f1f3e66c0fa8",
|
|
72
|
+
"constructorArgsSha256": "ccfd363002a8efb2ad8d8c7a3e44be9ad54078c292d294098ec9262326d040ce",
|
|
73
|
+
"abiSha256": "7cf08a871d29a3fddc988000c6dd0bb01140bcacdf931254c341248e0baa8a3b",
|
|
74
|
+
"compilerBuild": "verified-release"
|
|
67
75
|
},
|
|
68
76
|
"KCC20SnapshotController.placeholder.json": {
|
|
69
|
-
"fileSha256": "
|
|
70
|
-
"scriptSha256": "
|
|
77
|
+
"fileSha256": "a73727cfe03108097e1226566cff9dbbbf42a5dc8526aac3196817ffc642212f",
|
|
78
|
+
"scriptSha256": "8a73c57e309860ff799a71ca9352afa84f71638061cfa427cc5e8d36d1b8b025",
|
|
79
|
+
"sourceSha256": "1d8697fd967af3278e9fceae5a98ef442eaf29037822c963baf4c9d252288edd",
|
|
80
|
+
"constructorArgsSha256": "1d6b2ac27fa5fea7e6c419543d4968f5b623b3139e880984d2fa5a116a453635",
|
|
81
|
+
"abiSha256": "f4e503b480fd8c63194fcbe987e3818998bf4c387c360cb0371bebf542c60050",
|
|
82
|
+
"compilerBuild": "verified-release"
|
|
71
83
|
}
|
|
72
84
|
},
|
|
73
|
-
"contractsSourceStatus": "
|
|
85
|
+
"contractsSourceStatus": "reviewed source and generated inputs pinned to contractsCommit",
|
|
74
86
|
"standardRevision": "3fbec524abfbc20e87652eb938db218f8c17db17"
|
|
75
87
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Experimental fee escrow primitives
|
|
2
|
+
|
|
3
|
+
These exports support the new DEV fee-market contracts. Existing transaction
|
|
4
|
+
construction still uses the legacy fee version. These functions do not build,
|
|
5
|
+
authorize or submit escrow spends, and this change does not publish a package.
|
|
6
|
+
|
|
7
|
+
`calculateKcc20EscrowFees` implements the fixed 1% protocol fee and optional
|
|
8
|
+
0–5% creator fee. Each nonzero share has a 0.5 KAS minimum, applied per fill.
|
|
9
|
+
`protocolWaived` is a quote assumption. A transaction builder must independently
|
|
10
|
+
authenticate and consume an eligible FeeTicket before using that assumption.
|
|
11
|
+
|
|
12
|
+
`quoteKcc20EscrowSettlement` returns the trading-price and fee components for
|
|
13
|
+
take-ask, fill-bid and crossed settlements. Ask buyers pay fees above the price;
|
|
14
|
+
bid sellers receive price minus fees; a crossed matcher funds the fee once.
|
|
15
|
+
The result excludes network fees and storage deposits and is not a complete
|
|
16
|
+
wallet debit/proceeds quote. Fills that cannot cover their fees are rejected.
|
|
17
|
+
|
|
18
|
+
`quoteKcc20EscrowTradeGroup` takes ordered fills from one atomic trade group.
|
|
19
|
+
It calculates each fill's fee independently, then returns one fee vector and
|
|
20
|
+
one summed receipt value. A ticket waiver applies to every fill in that group;
|
|
21
|
+
crossed matches reject a ticket. The function checks the contract's maximum
|
|
22
|
+
fill counts, but it does not prove a group fits the signed transaction mass
|
|
23
|
+
budget or that all fills share one authenticated market and taker. The builder
|
|
24
|
+
must perform those checks before signing. In the current offline proof, a
|
|
25
|
+
two-ask buy passes the conservative mass target and a three-ask buy does not.
|
|
26
|
+
|
|
27
|
+
`calculateKcc20FeeClaim` checks the accounting for one through five distinct
|
|
28
|
+
receipts from the same token, market, policy and fee accounts. It returns the
|
|
29
|
+
claim and retained amounts. This does not establish that the receipts exist,
|
|
30
|
+
are unspent or have authenticated scripts. Construction must revalidate all
|
|
31
|
+
inputs against current chain state and the approved artifacts.
|
|
32
|
+
|
|
33
|
+
`encodeKcc20EscrowPolicy` and `decodeKcc20EscrowPolicy` implement the fixed
|
|
34
|
+
361-byte role policy, with receipt, root, holder, ask, bid and FeeTicket
|
|
35
|
+
template tuples in that order. They reject unsupported versions, noncanonical
|
|
36
|
+
integers, invalid rates and invalid account identities. `kcc20EscrowPolicyId`
|
|
37
|
+
hashes token ID, FeeTicket ID, price scale and the complete policy encoding.
|
|
38
|
+
The protocol rate and fee floors are constants of this contract version.
|
|
39
|
+
|
|
40
|
+
`kcc20FeeClaimCommitment` binds the ordered receipt outpoints, account outpoint,
|
|
41
|
+
claiming party, amounts, market identity and payout ScriptPublicKey. Supply the
|
|
42
|
+
ScriptPublicKey's two-byte version prefix as well as its script bytes. Receipt
|
|
43
|
+
order must match the native covenant family's input order. The consensus proof
|
|
44
|
+
harness uses the same fixed commitment vector.
|
|
45
|
+
|
|
46
|
+
Release requires authenticated artifact resolution, settlement and claim
|
|
47
|
+
construction, complete signed-transaction mass checks, application integration,
|
|
48
|
+
funded TN10 lifecycle proof and independent review of the final artifacts.
|
|
49
|
+
# Creator consent
|
|
50
|
+
|
|
51
|
+
`kcc20CreatorFeeConsent` produces the public message and personal-message hash
|
|
52
|
+
for the new TN10 wrapper. The authenticated creator signs `message` using the
|
|
53
|
+
wallet's standard message-signing API. The commitment covers the native token,
|
|
54
|
+
market, FeeTicket, scale and exact immutable fee policy, including account IDs.
|
|
55
|
+
The helper only constructs the message; it does not authenticate metadata,
|
|
56
|
+
verify a signature, grant creator rights or enable a market.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kaspacom/kcc20-tx-builder",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0-fee.0",
|
|
4
4
|
"description": "Runtime-independent KCC20 covenant transaction and PSKT construction primitives.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "KaspaCom",
|
|
@@ -42,6 +42,8 @@
|
|
|
42
42
|
"default": "./dist/cjs/index.js"
|
|
43
43
|
}
|
|
44
44
|
},
|
|
45
|
+
"./artifacts/kcc20-snapshot-bootstrap/KCC20SnapshotBootstrap.placeholder.json": "./artifacts/KCC20SnapshotBootstrap.placeholder.json",
|
|
46
|
+
"./artifacts/kcc20-snapshot-controller/KCC20SnapshotController.placeholder.json": "./artifacts/KCC20SnapshotController.placeholder.json",
|
|
45
47
|
"./artifacts/*": "./artifacts/*",
|
|
46
48
|
"./package.json": "./package.json",
|
|
47
49
|
"./*": {
|
|
@@ -86,7 +88,8 @@
|
|
|
86
88
|
"scripts": {
|
|
87
89
|
"build": "tsc -p tsconfig.json && tsc -p tsconfig.cjs.json && node scripts/write-cjs-package.mjs",
|
|
88
90
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
89
|
-
"test": "npm run
|
|
91
|
+
"test:escrow-fee": "npm run build && node --test test/escrow-fee.mjs test/escrow-policy.mjs",
|
|
92
|
+
"test": "npm run typecheck && npm run build && node test/smoke.mjs && node test/cjs-smoke.cjs && node test/snapshot-plan.mjs && node test/token-profiles.mjs && node test/profile-transfer.mjs && node --test test/mint-availability-limit.mjs test/consolidation-profile.mjs test/consolidation-transaction.mjs test/escrow-fee.mjs test/escrow-policy.mjs && node test/pinned-funding.mjs",
|
|
90
93
|
"prepack": "npm test",
|
|
91
94
|
"test:examples": "npm run verify:compatibility && node scripts/test-examples.mjs --browser",
|
|
92
95
|
"verify:compatibility": "node scripts/verify-compatibility.mjs",
|
package/src/abi.ts
CHANGED
|
@@ -207,15 +207,41 @@ export function mintExtensionCommitment(extension) {
|
|
|
207
207
|
|
|
208
208
|
export function holderExtensionCommitment(extension) {
|
|
209
209
|
const { kind = 1 } = extension;
|
|
210
|
+
const kindValue = requireByte(kind, "kind");
|
|
211
|
+
const maxSupply = BigInt(extension.maxSupply);
|
|
212
|
+
const fixedPrefix = [
|
|
213
|
+
Uint8Array.of(kindValue),
|
|
214
|
+
requireBytes(extension.creator, 32, "creator"),
|
|
215
|
+
requireBytes(extension.ticker, 32, "ticker"),
|
|
216
|
+
requireBytes(extension.name, 32, "name"),
|
|
217
|
+
signedI64Payload(BigInt(extension.displayScale)),
|
|
218
|
+
];
|
|
219
|
+
if (kindValue === 2) {
|
|
220
|
+
if (Number(extension.mintPolicy) !== 0) {
|
|
221
|
+
throw new Error("wide holder extension is fixed-supply only");
|
|
222
|
+
}
|
|
223
|
+
const { high, low } = unsignedWideAmountLimbs(maxSupply, "maxSupply");
|
|
224
|
+
return blake3(
|
|
225
|
+
concatBytes(
|
|
226
|
+
new TextEncoder().encode("KASPACOM/KCC20/HOLDER/V2"),
|
|
227
|
+
...fixedPrefix,
|
|
228
|
+
signedI64Payload(high),
|
|
229
|
+
signedI64Payload(low),
|
|
230
|
+
signedI64Payload(BigInt(extension.mintLaneCount ?? 1)),
|
|
231
|
+
Uint8Array.of(requireByte(extension.mintPolicy, "mintPolicy")),
|
|
232
|
+
signedI64Payload(BigInt(extension.mintPriceSompi)),
|
|
233
|
+
requireBytes(extension.treasury, 32, "treasury"),
|
|
234
|
+
requireBytes(extension.protocolFeeRecipient, 32, "protocolFeeRecipient"),
|
|
235
|
+
signedI64Payload(BigInt(extension.protocolFeeBps)),
|
|
236
|
+
),
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
if (kindValue !== 1) throw new Error("unsupported holder extension kind");
|
|
210
240
|
return blake3(
|
|
211
241
|
concatBytes(
|
|
212
242
|
new TextEncoder().encode("KASPACOM/KCC20/HOLDER/V1"),
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
requireBytes(extension.ticker, 32, "ticker"),
|
|
216
|
-
requireBytes(extension.name, 32, "name"),
|
|
217
|
-
signedI64Payload(BigInt(extension.displayScale)),
|
|
218
|
-
signedI64Payload(BigInt(extension.maxSupply)),
|
|
243
|
+
...fixedPrefix,
|
|
244
|
+
signedI64Payload(maxSupply),
|
|
219
245
|
signedI64Payload(BigInt(extension.mintLaneCount ?? 1)),
|
|
220
246
|
Uint8Array.of(requireByte(extension.mintPolicy, "mintPolicy")),
|
|
221
247
|
signedI64Payload(BigInt(extension.mintPriceSompi)),
|
|
@@ -226,6 +252,20 @@ export function holderExtensionCommitment(extension) {
|
|
|
226
252
|
);
|
|
227
253
|
}
|
|
228
254
|
|
|
255
|
+
function unsignedWideAmountLimbs(value, label) {
|
|
256
|
+
if (typeof value === "number" && !Number.isSafeInteger(value)) {
|
|
257
|
+
throw new Error(`${label} must not be an unsafe JavaScript number`);
|
|
258
|
+
}
|
|
259
|
+
const amount = BigInt(value);
|
|
260
|
+
const base = 1n << 63n;
|
|
261
|
+
const high = amount / base;
|
|
262
|
+
const low = amount % base;
|
|
263
|
+
if (amount <= 0n || high > base - 1n) {
|
|
264
|
+
throw new Error(`${label} is outside the unsigned two-limb range`);
|
|
265
|
+
}
|
|
266
|
+
return { high, low };
|
|
267
|
+
}
|
|
268
|
+
|
|
229
269
|
export function buildKcc20ScriptForState(artifact, state) {
|
|
230
270
|
const normalized = normalizeSilverScriptArtifact(artifact);
|
|
231
271
|
const script = Uint8Array.from(normalized.script);
|
package/src/artifacts.ts
CHANGED
|
@@ -8,9 +8,9 @@ export const KCC20_CURRENT_NATIVE_ARTIFACT_KEY =
|
|
|
8
8
|
|
|
9
9
|
export const KCC20_ARTIFACT_SCRIPT_SHA256 = Object.freeze({
|
|
10
10
|
"KCC20SnapshotBootstrap.placeholder.json":
|
|
11
|
-
"
|
|
11
|
+
"a22b93b0d8cd04096d83d25bbb6c38f0c07ac025b70226677db484fa93fe09ef",
|
|
12
12
|
"KCC20SnapshotController.placeholder.json":
|
|
13
|
-
"
|
|
13
|
+
"8a73c57e309860ff799a71ca9352afa84f71638061cfa427cc5e8d36d1b8b025",
|
|
14
14
|
[KCC20_CURRENT_NATIVE_ARTIFACT_KEY]:
|
|
15
15
|
"dd9cb334cd9e9220b15644782f271ef3ecdba34fd0762010e3d6bc18176837ae",
|
|
16
16
|
"KCC20FeeTicket.placeholder.json":
|
|
@@ -33,17 +33,17 @@ export const KCC20_SNAPSHOT_ARTIFACT_PINS = Object.freeze({
|
|
|
33
33
|
key: "KCC20SnapshotBootstrap.placeholder.json",
|
|
34
34
|
contractName: "KCC20SnapshotBootstrap",
|
|
35
35
|
artifactSha256:
|
|
36
|
-
"
|
|
37
|
-
scriptLength:
|
|
36
|
+
"daddabe50454e1c6c187c0b027cc8887f79f0f0086a14f26c6a2a56054991ebb",
|
|
37
|
+
scriptLength: 35913,
|
|
38
38
|
stateStart: 1,
|
|
39
|
-
stateLength:
|
|
39
|
+
stateLength: 414,
|
|
40
40
|
}),
|
|
41
41
|
controller: Object.freeze({
|
|
42
42
|
key: "KCC20SnapshotController.placeholder.json",
|
|
43
43
|
contractName: "KCC20SnapshotController",
|
|
44
44
|
artifactSha256:
|
|
45
|
-
"
|
|
46
|
-
scriptLength:
|
|
45
|
+
"a73727cfe03108097e1226566cff9dbbbf42a5dc8526aac3196817ffc642212f",
|
|
46
|
+
scriptLength: 15350,
|
|
47
47
|
stateStart: 1,
|
|
48
48
|
stateLength: 432,
|
|
49
49
|
}),
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/** Fee arithmetic for new escrow markets only. Legacy trading is unchanged. */
|
|
2
|
+
export const KCC20_ESCROW_POLICY_VERSION = 1 as const;
|
|
3
|
+
export const KCC20_ESCROW_PROTOCOL_BPS = 100;
|
|
4
|
+
export const KCC20_ESCROW_MAX_CREATOR_BPS = 500;
|
|
5
|
+
export const KCC20_ESCROW_MIN_FEE_SOMPI = 50_000_000n;
|
|
6
|
+
export const KCC20_ESCROW_MAX_CLAIM_RECEIPTS = 5;
|
|
7
|
+
export const KCC20_ESCROW_MAX_INT = 9_223_372_036_854_775_807n;
|
|
8
|
+
|
|
9
|
+
export interface Kcc20EscrowFeeAmounts {
|
|
10
|
+
protocolFeeSompi: bigint;
|
|
11
|
+
creatorFeeSompi: bigint;
|
|
12
|
+
totalFeeSompi: bigint;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function amount(value: bigint, name: string): bigint {
|
|
16
|
+
if (typeof value !== "bigint" || value < 0n || value > KCC20_ESCROW_MAX_INT) {
|
|
17
|
+
throw new Error(`${name} must be a nonnegative signed-64-bit bigint`);
|
|
18
|
+
}
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function add(left: bigint, right: bigint): bigint {
|
|
23
|
+
return amount(left + right, "sum");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function fee(gross: bigint, bps: number): bigint {
|
|
27
|
+
// Match the contract without overflowing the intermediate multiplication.
|
|
28
|
+
const rate = BigInt(bps);
|
|
29
|
+
const percentage = (gross / 10_000n) * rate + ((gross % 10_000n) * rate) / 10_000n;
|
|
30
|
+
return percentage < KCC20_ESCROW_MIN_FEE_SOMPI ? KCC20_ESCROW_MIN_FEE_SOMPI : percentage;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** A waiver here is a quote assumption, never proof of FeeTicket eligibility. */
|
|
34
|
+
export function calculateKcc20EscrowFees(input: {
|
|
35
|
+
grossSompi: bigint;
|
|
36
|
+
creatorFeeBps: number;
|
|
37
|
+
protocolWaived: boolean;
|
|
38
|
+
}): Kcc20EscrowFeeAmounts {
|
|
39
|
+
const gross = amount(input.grossSompi, "grossSompi");
|
|
40
|
+
if (gross < 50_000_000n) throw new Error("fill gross must be at least 0.5 KAS");
|
|
41
|
+
if (!Number.isInteger(input.creatorFeeBps) || input.creatorFeeBps < 0 || input.creatorFeeBps > KCC20_ESCROW_MAX_CREATOR_BPS) {
|
|
42
|
+
throw new Error("creatorFeeBps must be an integer from 0 to 500");
|
|
43
|
+
}
|
|
44
|
+
if (typeof input.protocolWaived !== "boolean") throw new Error("protocolWaived must be boolean");
|
|
45
|
+
const protocolFeeSompi = input.protocolWaived ? 0n : fee(gross, KCC20_ESCROW_PROTOCOL_BPS);
|
|
46
|
+
const creatorFeeSompi = input.creatorFeeBps === 0 ? 0n : fee(gross, input.creatorFeeBps);
|
|
47
|
+
return { protocolFeeSompi, creatorFeeSompi, totalFeeSompi: add(protocolFeeSompi, creatorFeeSompi) };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Trading-price components only; excludes network fees and storage deposits.
|
|
51
|
+
* One matched ask/bid is one fill. Call separately for independent fills. */
|
|
52
|
+
export function quoteKcc20EscrowSettlement(input: {
|
|
53
|
+
grossSompi: bigint;
|
|
54
|
+
creatorFeeBps: number;
|
|
55
|
+
protocolWaived: boolean;
|
|
56
|
+
kind: "take-ask" | "fill-bid" | "cross-match";
|
|
57
|
+
}) {
|
|
58
|
+
const fees = calculateKcc20EscrowFees(input);
|
|
59
|
+
if (!["take-ask", "fill-bid", "cross-match"].includes(input.kind)) throw new Error("unsupported settlement kind");
|
|
60
|
+
if (input.kind === "fill-bid" && input.grossSompi < fees.totalFeeSompi) {
|
|
61
|
+
throw new Error("fill gross cannot cover protocol and creator fees");
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
...fees,
|
|
65
|
+
grossSompi: input.grossSompi,
|
|
66
|
+
buyerPaysSompi: input.kind === "take-ask" ? add(input.grossSompi, fees.totalFeeSompi) : input.grossSompi,
|
|
67
|
+
sellerReceivesSompi: input.kind === "fill-bid" ? input.grossSompi - fees.totalFeeSompi : input.grossSompi,
|
|
68
|
+
matcherPaysSompi: input.kind === "cross-match" ? fees.totalFeeSompi : 0n,
|
|
69
|
+
requiresFeeOutput: fees.totalFeeSompi > 0n,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Arithmetic for one atomic trade group. Artifact and UTXO checks belong to
|
|
74
|
+
* the signed transaction builder; this quote does not authorize a FeeTicket. */
|
|
75
|
+
export function quoteKcc20EscrowTradeGroup(input: {
|
|
76
|
+
kind: "take-ask" | "fill-bid" | "cross-match";
|
|
77
|
+
fillGrossSompi: readonly bigint[];
|
|
78
|
+
creatorFeeBps: number;
|
|
79
|
+
protocolWaived: boolean;
|
|
80
|
+
}) {
|
|
81
|
+
const maxFills = input.kind === "take-ask" ? 16 : input.kind === "fill-bid" ? 10 : input.kind === "cross-match" ? 1 : 0;
|
|
82
|
+
if (!Array.isArray(input.fillGrossSompi) || input.fillGrossSompi.length < 1 || input.fillGrossSompi.length > maxFills) {
|
|
83
|
+
throw new Error("invalid trade group size");
|
|
84
|
+
}
|
|
85
|
+
if (input.kind === "cross-match" && input.protocolWaived) {
|
|
86
|
+
throw new Error("crossed matches cannot use a FeeTicket");
|
|
87
|
+
}
|
|
88
|
+
const feeVector = input.fillGrossSompi.map(grossSompi =>
|
|
89
|
+
quoteKcc20EscrowSettlement({
|
|
90
|
+
grossSompi, creatorFeeBps: input.creatorFeeBps,
|
|
91
|
+
protocolWaived: input.protocolWaived, kind: input.kind,
|
|
92
|
+
}));
|
|
93
|
+
const totals = feeVector.reduce((sum, fill) => ({
|
|
94
|
+
grossSompi: add(sum.grossSompi, fill.grossSompi),
|
|
95
|
+
protocolFeeSompi: add(sum.protocolFeeSompi, fill.protocolFeeSompi),
|
|
96
|
+
creatorFeeSompi: add(sum.creatorFeeSompi, fill.creatorFeeSompi),
|
|
97
|
+
buyerPaysSompi: add(sum.buyerPaysSompi, fill.buyerPaysSompi),
|
|
98
|
+
sellerReceivesSompi: add(sum.sellerReceivesSompi, fill.sellerReceivesSompi),
|
|
99
|
+
matcherPaysSompi: add(sum.matcherPaysSompi, fill.matcherPaysSompi),
|
|
100
|
+
}), {
|
|
101
|
+
grossSompi: 0n, protocolFeeSompi: 0n, creatorFeeSompi: 0n,
|
|
102
|
+
buyerPaysSompi: 0n, sellerReceivesSompi: 0n, matcherPaysSompi: 0n,
|
|
103
|
+
});
|
|
104
|
+
return {
|
|
105
|
+
...totals,
|
|
106
|
+
feeVector: feeVector.map(fill => ({
|
|
107
|
+
protocolFeeSompi: fill.protocolFeeSompi,
|
|
108
|
+
creatorFeeSompi: fill.creatorFeeSompi,
|
|
109
|
+
})),
|
|
110
|
+
receiptValueSompi: add(totals.protocolFeeSompi, totals.creatorFeeSompi),
|
|
111
|
+
requiresFeeOutput: totals.protocolFeeSompi + totals.creatorFeeSompi > 0n,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Accounting description only. The transaction builder must authenticate scripts and live UTXOs. */
|
|
116
|
+
export interface Kcc20FeeReceiptAmounts {
|
|
117
|
+
outpoint: { transactionId: string; index: number };
|
|
118
|
+
tokenId: string;
|
|
119
|
+
marketId: string;
|
|
120
|
+
policyId: string;
|
|
121
|
+
protocolAccountId: string;
|
|
122
|
+
creatorAccountId: string;
|
|
123
|
+
valueSompi: bigint;
|
|
124
|
+
protocolSompi: bigint;
|
|
125
|
+
creatorSompi: bigint;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function id(value: string): string {
|
|
129
|
+
if (typeof value !== "string" || !/^[0-9a-fA-F]{64}$/.test(value)) throw new Error("identity must be 32-byte hex");
|
|
130
|
+
return value.toLowerCase();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Computes a single claim's conservation amounts, without authorizing a spend. */
|
|
134
|
+
export function calculateKcc20FeeClaim(
|
|
135
|
+
receipts: readonly Kcc20FeeReceiptAmounts[],
|
|
136
|
+
party: "protocol" | "creator",
|
|
137
|
+
) {
|
|
138
|
+
if (party !== "protocol" && party !== "creator") throw new Error("unknown claiming party");
|
|
139
|
+
if (!Array.isArray(receipts) || receipts.length < 1 || receipts.length > KCC20_ESCROW_MAX_CLAIM_RECEIPTS) {
|
|
140
|
+
throw new Error("claim must select between one and five receipts");
|
|
141
|
+
}
|
|
142
|
+
const seen = new Set<string>();
|
|
143
|
+
let group: string | undefined;
|
|
144
|
+
let protocol = 0n;
|
|
145
|
+
let creator = 0n;
|
|
146
|
+
let consumed = 0n;
|
|
147
|
+
for (const receipt of receipts) {
|
|
148
|
+
const txid = id(receipt.outpoint.transactionId);
|
|
149
|
+
if (!Number.isInteger(receipt.outpoint.index) || receipt.outpoint.index < 0 || receipt.outpoint.index > 0xffffffff) {
|
|
150
|
+
throw new Error("outpoint index must be uint32");
|
|
151
|
+
}
|
|
152
|
+
const outpoint = `${txid}:${receipt.outpoint.index}`;
|
|
153
|
+
if (seen.has(outpoint)) throw new Error("duplicate receipt outpoint");
|
|
154
|
+
seen.add(outpoint);
|
|
155
|
+
const identity = [receipt.tokenId, receipt.marketId, receipt.policyId, receipt.protocolAccountId, receipt.creatorAccountId].map(id).join(":");
|
|
156
|
+
const zero = "0".repeat(64);
|
|
157
|
+
if ([receipt.tokenId, receipt.marketId, receipt.policyId, receipt.protocolAccountId].some(value => id(value) === zero)) {
|
|
158
|
+
throw new Error("token, market, policy and protocol account must be nonzero");
|
|
159
|
+
}
|
|
160
|
+
if (id(receipt.protocolAccountId) === id(receipt.creatorAccountId)) throw new Error("fee accounts must differ");
|
|
161
|
+
if (id(receipt.creatorAccountId) === zero && receipt.creatorSompi !== 0n) throw new Error("disabled creator account cannot hold fees");
|
|
162
|
+
if (group !== undefined && identity !== group) throw new Error("receipts must share token, market, policy and accounts");
|
|
163
|
+
group = identity;
|
|
164
|
+
const p = amount(receipt.protocolSompi, "protocolSompi");
|
|
165
|
+
const c = amount(receipt.creatorSompi, "creatorSompi");
|
|
166
|
+
const value = amount(receipt.valueSompi, "valueSompi");
|
|
167
|
+
if (value === 0n || value !== add(p, c)) throw new Error("receipt value must equal nonzero entitlements");
|
|
168
|
+
if ((p > 0n && p < KCC20_ESCROW_MIN_FEE_SOMPI) || (c > 0n && c < KCC20_ESCROW_MIN_FEE_SOMPI)) {
|
|
169
|
+
throw new Error("nonzero entitlement is below the policy minimum");
|
|
170
|
+
}
|
|
171
|
+
if ((party === "protocol" ? p : c) === 0n) throw new Error("receipt has no entitlement for claimant");
|
|
172
|
+
protocol = add(protocol, p);
|
|
173
|
+
creator = add(creator, c);
|
|
174
|
+
consumed = add(consumed, value);
|
|
175
|
+
}
|
|
176
|
+
const claimSompi = party === "protocol" ? protocol : creator;
|
|
177
|
+
const remainingProtocolSompi = party === "protocol" ? 0n : protocol;
|
|
178
|
+
const remainingCreatorSompi = party === "creator" ? 0n : creator;
|
|
179
|
+
return { claimSompi, remainingProtocolSompi, remainingCreatorSompi, consumedSompi: consumed,
|
|
180
|
+
remainderSompi: add(remainingProtocolSompi, remainingCreatorSompi) };
|
|
181
|
+
}
|
|
@@ -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,8 @@ 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";
|
|
21
23
|
export * from "./trading-operation.js";
|
|
22
24
|
export * from "./protocol.js";
|
|
23
25
|
export * from "./runtime.js";
|
|
@@ -49,7 +49,8 @@ export function prepareSnapshotDeployment(kw: any, definition: SnapshotDeploymen
|
|
|
49
49
|
throw new Error("shard allocations do not equal fixed token supply");
|
|
50
50
|
}
|
|
51
51
|
const extension = {
|
|
52
|
-
kind:
|
|
52
|
+
kind: BigInt(definition.totalSupply) > 9_223_372_036_854_775_807n ? 2 : 1,
|
|
53
|
+
creator: definition.creator, ticker: bytesToHex(asciiToBytes32(definition.ticker)),
|
|
53
54
|
name: bytesToHex(asciiToBytes32(definition.ticker)), displayScale: definition.displayScale,
|
|
54
55
|
maxSupply: definition.totalSupply, mintLaneCount: 1, mintPolicy: 0, mintPriceSompi: "0",
|
|
55
56
|
treasury: definition.creator, protocolFeeRecipient: definition.creator, protocolFeeBps: "0",
|
|
@@ -618,7 +618,7 @@ export function createKcc20SnapshotPsktBuilder(
|
|
|
618
618
|
params.holderExtensionCommitment,
|
|
619
619
|
"holder extension commitment",
|
|
620
620
|
),
|
|
621
|
-
totalSupply:
|
|
621
|
+
totalSupply: positiveWideAmount(params.totalSupply, "total supply"),
|
|
622
622
|
claimNotBeforeDaaScore: positiveI64(
|
|
623
623
|
params.claimNotBeforeDaaScore,
|
|
624
624
|
"claim-not-before DAA score",
|
|
@@ -1830,6 +1830,22 @@ export function createKcc20SnapshotPsktBuilder(
|
|
|
1830
1830
|
return amount;
|
|
1831
1831
|
}
|
|
1832
1832
|
|
|
1833
|
+
function positiveWideAmount(value, label) {
|
|
1834
|
+
let amount;
|
|
1835
|
+
try {
|
|
1836
|
+
if (typeof value === "number" && !Number.isSafeInteger(value)) {
|
|
1837
|
+
throw new Error("unsafe integer");
|
|
1838
|
+
}
|
|
1839
|
+
amount = BigInt(value);
|
|
1840
|
+
} catch {
|
|
1841
|
+
throw new Error(`${label} must be a positive unsigned two-limb integer`);
|
|
1842
|
+
}
|
|
1843
|
+
if (amount <= 0n || amount > (1n << 126n) - 1n) {
|
|
1844
|
+
throw new Error(`${label} must be a positive unsigned two-limb integer`);
|
|
1845
|
+
}
|
|
1846
|
+
return amount;
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1833
1849
|
function nonnegativeI64(value, label) {
|
|
1834
1850
|
let amount;
|
|
1835
1851
|
try {
|