@kaspacom/kcc20-tx-builder 0.1.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.
Files changed (234) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +104 -0
  3. package/artifacts/KCC20.placeholder.json +77479 -0
  4. package/artifacts/KCC20FeeTicket.placeholder.json +16846 -0
  5. package/artifacts/KCC20Orderbook.placeholder.json +78001 -0
  6. package/artifacts/KCC20Vesting.placeholder.json +4298 -0
  7. package/artifacts/KCC20Wrapper.placeholder.json +12246 -0
  8. package/dist/abi.d.ts +44 -0
  9. package/dist/abi.d.ts.map +1 -0
  10. package/dist/abi.js +336 -0
  11. package/dist/abi.js.map +1 -0
  12. package/dist/action-source-resolver.d.ts +80 -0
  13. package/dist/action-source-resolver.d.ts.map +1 -0
  14. package/dist/action-source-resolver.js +412 -0
  15. package/dist/action-source-resolver.js.map +1 -0
  16. package/dist/artifacts.d.ts +14 -0
  17. package/dist/artifacts.d.ts.map +1 -0
  18. package/dist/artifacts.js +44 -0
  19. package/dist/artifacts.js.map +1 -0
  20. package/dist/builder.d.ts +12 -0
  21. package/dist/builder.d.ts.map +1 -0
  22. package/dist/builder.js +18 -0
  23. package/dist/builder.js.map +1 -0
  24. package/dist/cjs/abi.d.ts +43 -0
  25. package/dist/cjs/abi.js +361 -0
  26. package/dist/cjs/abi.js.map +1 -0
  27. package/dist/cjs/action-source-resolver.d.ts +79 -0
  28. package/dist/cjs/action-source-resolver.js +435 -0
  29. package/dist/cjs/action-source-resolver.js.map +1 -0
  30. package/dist/cjs/artifacts.d.ts +13 -0
  31. package/dist/cjs/artifacts.js +51 -0
  32. package/dist/cjs/artifacts.js.map +1 -0
  33. package/dist/cjs/builder.d.ts +11 -0
  34. package/dist/cjs/builder.js +22 -0
  35. package/dist/cjs/builder.js.map +1 -0
  36. package/dist/cjs/deploy-operation.d.ts +103 -0
  37. package/dist/cjs/deploy-operation.js +335 -0
  38. package/dist/cjs/deploy-operation.js.map +1 -0
  39. package/dist/cjs/encoding.d.ts +5 -0
  40. package/dist/cjs/encoding.js +51 -0
  41. package/dist/cjs/encoding.js.map +1 -0
  42. package/dist/cjs/engine.d.ts +40 -0
  43. package/dist/cjs/engine.js +8972 -0
  44. package/dist/cjs/engine.js.map +1 -0
  45. package/dist/cjs/errors.d.ts +7 -0
  46. package/dist/cjs/errors.js +23 -0
  47. package/dist/cjs/errors.js.map +1 -0
  48. package/dist/cjs/fee-ticket-operation.d.ts +204 -0
  49. package/dist/cjs/fee-ticket-operation.js +327 -0
  50. package/dist/cjs/fee-ticket-operation.js.map +1 -0
  51. package/dist/cjs/index.d.ts +27 -0
  52. package/dist/cjs/index.js +45 -0
  53. package/dist/cjs/index.js.map +1 -0
  54. package/dist/cjs/ladder-limit-orders.d.ts +41 -0
  55. package/dist/cjs/ladder-limit-orders.js +129 -0
  56. package/dist/cjs/ladder-limit-orders.js.map +1 -0
  57. package/dist/cjs/mint-availability-operation.d.ts +56 -0
  58. package/dist/cjs/mint-availability-operation.js +95 -0
  59. package/dist/cjs/mint-availability-operation.js.map +1 -0
  60. package/dist/cjs/models.d.ts +90 -0
  61. package/dist/cjs/models.js +6 -0
  62. package/dist/cjs/models.js.map +1 -0
  63. package/dist/cjs/operations.d.ts +6 -0
  64. package/dist/cjs/operations.js +41 -0
  65. package/dist/cjs/operations.js.map +1 -0
  66. package/dist/cjs/package.json +1 -0
  67. package/dist/cjs/paid-mint-amount.d.ts +69 -0
  68. package/dist/cjs/paid-mint-amount.js +281 -0
  69. package/dist/cjs/paid-mint-amount.js.map +1 -0
  70. package/dist/cjs/protocol.d.ts +21 -0
  71. package/dist/cjs/protocol.js +25 -0
  72. package/dist/cjs/protocol.js.map +1 -0
  73. package/dist/cjs/runtime.d.ts +27 -0
  74. package/dist/cjs/runtime.js +3 -0
  75. package/dist/cjs/runtime.js.map +1 -0
  76. package/dist/cjs/sale-common.d.ts +287 -0
  77. package/dist/cjs/sale-common.js +928 -0
  78. package/dist/cjs/sale-common.js.map +1 -0
  79. package/dist/cjs/source-provider.d.ts +11 -0
  80. package/dist/cjs/source-provider.js +3 -0
  81. package/dist/cjs/source-provider.js.map +1 -0
  82. package/dist/cjs/source-validation.d.ts +9 -0
  83. package/dist/cjs/source-validation.js +33 -0
  84. package/dist/cjs/source-validation.js.map +1 -0
  85. package/dist/cjs/token-amount.d.ts +31 -0
  86. package/dist/cjs/token-amount.js +252 -0
  87. package/dist/cjs/token-amount.js.map +1 -0
  88. package/dist/cjs/token-operation.d.ts +135 -0
  89. package/dist/cjs/token-operation.js +236 -0
  90. package/dist/cjs/token-operation.js.map +1 -0
  91. package/dist/cjs/trade-fee.d.ts +60 -0
  92. package/dist/cjs/trade-fee.js +185 -0
  93. package/dist/cjs/trade-fee.js.map +1 -0
  94. package/dist/cjs/trading-operation.d.ts +212 -0
  95. package/dist/cjs/trading-operation.js +587 -0
  96. package/dist/cjs/trading-operation.js.map +1 -0
  97. package/dist/cjs/verify-operation.d.ts +79 -0
  98. package/dist/cjs/verify-operation.js +246 -0
  99. package/dist/cjs/verify-operation.js.map +1 -0
  100. package/dist/cjs/wallet-operation.d.ts +52 -0
  101. package/dist/cjs/wallet-operation.js +58 -0
  102. package/dist/cjs/wallet-operation.js.map +1 -0
  103. package/dist/cjs/wasm-provider.d.ts +15 -0
  104. package/dist/cjs/wasm-provider.js +20 -0
  105. package/dist/cjs/wasm-provider.js.map +1 -0
  106. package/dist/cjs/wrapper-operation.d.ts +196 -0
  107. package/dist/cjs/wrapper-operation.js +317 -0
  108. package/dist/cjs/wrapper-operation.js.map +1 -0
  109. package/dist/deploy-operation.d.ts +104 -0
  110. package/dist/deploy-operation.d.ts.map +1 -0
  111. package/dist/deploy-operation.js +327 -0
  112. package/dist/deploy-operation.js.map +1 -0
  113. package/dist/encoding.d.ts +6 -0
  114. package/dist/encoding.d.ts.map +1 -0
  115. package/dist/encoding.js +44 -0
  116. package/dist/encoding.js.map +1 -0
  117. package/dist/engine.d.ts +41 -0
  118. package/dist/engine.d.ts.map +1 -0
  119. package/dist/engine.js +8969 -0
  120. package/dist/engine.js.map +1 -0
  121. package/dist/errors.d.ts +8 -0
  122. package/dist/errors.d.ts.map +1 -0
  123. package/dist/errors.js +18 -0
  124. package/dist/errors.js.map +1 -0
  125. package/dist/fee-ticket-operation.d.ts +205 -0
  126. package/dist/fee-ticket-operation.d.ts.map +1 -0
  127. package/dist/fee-ticket-operation.js +320 -0
  128. package/dist/fee-ticket-operation.js.map +1 -0
  129. package/dist/index.d.ts +28 -0
  130. package/dist/index.d.ts.map +1 -0
  131. package/dist/index.js +28 -0
  132. package/dist/index.js.map +1 -0
  133. package/dist/ladder-limit-orders.d.ts +42 -0
  134. package/dist/ladder-limit-orders.d.ts.map +1 -0
  135. package/dist/ladder-limit-orders.js +121 -0
  136. package/dist/ladder-limit-orders.js.map +1 -0
  137. package/dist/mint-availability-operation.d.ts +57 -0
  138. package/dist/mint-availability-operation.d.ts.map +1 -0
  139. package/dist/mint-availability-operation.js +91 -0
  140. package/dist/mint-availability-operation.js.map +1 -0
  141. package/dist/models.d.ts +91 -0
  142. package/dist/models.d.ts.map +1 -0
  143. package/dist/models.js +3 -0
  144. package/dist/models.js.map +1 -0
  145. package/dist/operations.d.ts +7 -0
  146. package/dist/operations.d.ts.map +1 -0
  147. package/dist/operations.js +36 -0
  148. package/dist/operations.js.map +1 -0
  149. package/dist/paid-mint-amount.d.ts +70 -0
  150. package/dist/paid-mint-amount.d.ts.map +1 -0
  151. package/dist/paid-mint-amount.js +268 -0
  152. package/dist/paid-mint-amount.js.map +1 -0
  153. package/dist/protocol.d.ts +22 -0
  154. package/dist/protocol.d.ts.map +1 -0
  155. package/dist/protocol.js +22 -0
  156. package/dist/protocol.js.map +1 -0
  157. package/dist/runtime.d.ts +28 -0
  158. package/dist/runtime.d.ts.map +1 -0
  159. package/dist/runtime.js +2 -0
  160. package/dist/runtime.js.map +1 -0
  161. package/dist/sale-common.d.ts +288 -0
  162. package/dist/sale-common.d.ts.map +1 -0
  163. package/dist/sale-common.js +844 -0
  164. package/dist/sale-common.js.map +1 -0
  165. package/dist/source-provider.d.ts +12 -0
  166. package/dist/source-provider.d.ts.map +1 -0
  167. package/dist/source-provider.js +2 -0
  168. package/dist/source-provider.js.map +1 -0
  169. package/dist/source-validation.d.ts +10 -0
  170. package/dist/source-validation.d.ts.map +1 -0
  171. package/dist/source-validation.js +28 -0
  172. package/dist/source-validation.js.map +1 -0
  173. package/dist/token-amount.d.ts +32 -0
  174. package/dist/token-amount.d.ts.map +1 -0
  175. package/dist/token-amount.js +237 -0
  176. package/dist/token-amount.js.map +1 -0
  177. package/dist/token-operation.d.ts +136 -0
  178. package/dist/token-operation.d.ts.map +1 -0
  179. package/dist/token-operation.js +231 -0
  180. package/dist/token-operation.js.map +1 -0
  181. package/dist/trade-fee.d.ts +61 -0
  182. package/dist/trade-fee.d.ts.map +1 -0
  183. package/dist/trade-fee.js +173 -0
  184. package/dist/trade-fee.js.map +1 -0
  185. package/dist/trading-operation.d.ts +213 -0
  186. package/dist/trading-operation.d.ts.map +1 -0
  187. package/dist/trading-operation.js +580 -0
  188. package/dist/trading-operation.js.map +1 -0
  189. package/dist/verify-operation.d.ts +80 -0
  190. package/dist/verify-operation.d.ts.map +1 -0
  191. package/dist/verify-operation.js +243 -0
  192. package/dist/verify-operation.js.map +1 -0
  193. package/dist/wallet-operation.d.ts +53 -0
  194. package/dist/wallet-operation.d.ts.map +1 -0
  195. package/dist/wallet-operation.js +55 -0
  196. package/dist/wallet-operation.js.map +1 -0
  197. package/dist/wasm-provider.d.ts +16 -0
  198. package/dist/wasm-provider.d.ts.map +1 -0
  199. package/dist/wasm-provider.js +17 -0
  200. package/dist/wasm-provider.js.map +1 -0
  201. package/dist/wrapper-operation.d.ts +197 -0
  202. package/dist/wrapper-operation.d.ts.map +1 -0
  203. package/dist/wrapper-operation.js +311 -0
  204. package/dist/wrapper-operation.js.map +1 -0
  205. package/index.d.ts +1 -0
  206. package/package.json +84 -0
  207. package/src/abi.ts +493 -0
  208. package/src/action-source-resolver.ts +660 -0
  209. package/src/artifacts.ts +87 -0
  210. package/src/builder.ts +37 -0
  211. package/src/deploy-operation.ts +458 -0
  212. package/src/encoding.ts +58 -0
  213. package/src/engine.ts +13226 -0
  214. package/src/errors.ts +35 -0
  215. package/src/fee-ticket-operation.ts +518 -0
  216. package/src/index.ts +27 -0
  217. package/src/ladder-limit-orders.ts +184 -0
  218. package/src/mint-availability-operation.ts +146 -0
  219. package/src/models.ts +103 -0
  220. package/src/operations.ts +46 -0
  221. package/src/paid-mint-amount.ts +402 -0
  222. package/src/protocol.ts +28 -0
  223. package/src/runtime.ts +29 -0
  224. package/src/sale-common.ts +1238 -0
  225. package/src/source-provider.ts +12 -0
  226. package/src/source-validation.ts +47 -0
  227. package/src/token-amount.ts +302 -0
  228. package/src/token-operation.ts +384 -0
  229. package/src/trade-fee.ts +264 -0
  230. package/src/trading-operation.ts +842 -0
  231. package/src/verify-operation.ts +400 -0
  232. package/src/wallet-operation.ts +86 -0
  233. package/src/wasm-provider.ts +32 -0
  234. package/src/wrapper-operation.ts +531 -0
package/src/errors.ts ADDED
@@ -0,0 +1,35 @@
1
+ export type Kcc20BuilderErrorCode =
2
+ | "INVALID_INPUT"
3
+ | "MISSING_SOURCE"
4
+ | "STALE_SOURCE"
5
+ | "ARTIFACT_MISMATCH"
6
+ | "UNSUPPORTED_OPERATION"
7
+ | "WASM_CAPABILITY_MISSING"
8
+ | "SERIALIZATION_FAILED";
9
+
10
+ export class Kcc20BuilderError extends Error {
11
+ readonly code: Kcc20BuilderErrorCode;
12
+ readonly details: Readonly<Record<string, string>>;
13
+
14
+ constructor(
15
+ code: Kcc20BuilderErrorCode,
16
+ message: string,
17
+ details: Record<string, string> = {},
18
+ ) {
19
+ super(message);
20
+ this.name = "Kcc20BuilderError";
21
+ this.code = code;
22
+ this.details = Object.freeze({ ...details });
23
+ }
24
+ }
25
+
26
+ export function requireHex64(value: string, label: string): string {
27
+ const normalized = value.trim().replace(/^0x/i, "").toLowerCase();
28
+ if (!/^[0-9a-f]{64}$/.test(normalized)) {
29
+ throw new Kcc20BuilderError(
30
+ "INVALID_INPUT",
31
+ `${label} must be 32-byte hex`,
32
+ );
33
+ }
34
+ return normalized;
35
+ }
@@ -0,0 +1,518 @@
1
+ import { buildKcc20WalletOperationFromPlan } from "./wallet-operation.js";
2
+ import {
3
+ ownerNativeHolderUtxos,
4
+ selectOwnerFeeTicketUtxos,
5
+ selectOwnerNativeHolderUtxo,
6
+ summarizeHolderUtxos,
7
+ type Kcc20IndexedCovenantUtxo,
8
+ } from "./action-source-resolver.js";
9
+ import { normalizeKcc20OwnerIdentifier } from "./token-amount.js";
10
+
11
+ const HEX_64_RE = /^[a-fA-F0-9]{64}$/;
12
+ const U64_MAX = (1n << 64n) - 1n;
13
+
14
+ export interface FeeTicketRootDeployDraft {
15
+ utilityTokenId: string;
16
+ utilityTokenAmount: string;
17
+ discountBps: number;
18
+ supportedMarketIds?: string[];
19
+ }
20
+
21
+ export interface FeeTicketWalletInfo {
22
+ walletAddress: string;
23
+ kcc20Owner: string;
24
+ }
25
+
26
+ export interface FeeTicketRootOperationOptions {
27
+ network?: string;
28
+ createdAt?: string;
29
+ requestId?: string;
30
+ }
31
+
32
+ export function buildFeeTicketRootDeployOperation(
33
+ walletInfo: FeeTicketWalletInfo,
34
+ draft: FeeTicketRootDeployDraft,
35
+ options: FeeTicketRootOperationOptions = {},
36
+ ) {
37
+ const utilityTokenId = requireHex64(draft.utilityTokenId, "utilityTokenId");
38
+ const utilityTokenAmount = requireU64(
39
+ draft.utilityTokenAmount,
40
+ "utilityTokenAmount",
41
+ );
42
+ const discountBps = requireBps(draft.discountBps, "discountBps");
43
+ const supportedMarketIds = (draft.supportedMarketIds ?? []).map((id) =>
44
+ requireHex64(id, "supportedMarketIds"),
45
+ );
46
+ const requestId =
47
+ options.requestId ?? `fee-ticket-root-deploy-${randomOperationId()}`;
48
+ const network = options.network ?? "testnet-10";
49
+
50
+ return buildKcc20WalletOperationFromPlan(
51
+ walletInfo,
52
+ {
53
+ builderKey: "fee-ticket.deploy-root",
54
+ operation: "deploy-fee-ticket-root",
55
+ contract: "KCC20FeeTicket",
56
+ action: "deployRoot",
57
+ description: "Build a FeeTicket root deploy request.",
58
+ params: {
59
+ utilityTokenId,
60
+ utilityTokenAmount: utilityTokenAmount.toString(),
61
+ discountBps,
62
+ supportedMarketIds,
63
+ rootOwner: walletInfo.kcc20Owner,
64
+ rootOwnerType: "pubkey",
65
+ },
66
+ sourceRequirements: [
67
+ {
68
+ type: "kaspa-funding-utxo",
69
+ ownerAddress: walletInfo.walletAddress,
70
+ purpose: "fund FeeTicket root deploy transaction",
71
+ },
72
+ {
73
+ type: "compiled-fee-ticket-artifact",
74
+ contract: "FeeTicket",
75
+ purpose: "construct FeeTicket root covenant output",
76
+ },
77
+ ],
78
+ signerRequirements: [
79
+ {
80
+ type: "wallet-owner",
81
+ owner: walletInfo.kcc20Owner,
82
+ address: walletInfo.walletAddress,
83
+ },
84
+ ],
85
+ warnings: [
86
+ "This builds the on-chain FeeTicket root transaction only. Save the returned root covenant ID after broadcast so the platform config can trust it.",
87
+ ],
88
+ },
89
+ { ...options, requestId, network },
90
+ );
91
+ }
92
+
93
+ export interface FeeTicketRootInfo {
94
+ configured: boolean;
95
+ enabled: boolean;
96
+ rootId?: string;
97
+ rootOwner?: string;
98
+ utilityTokenId?: string;
99
+ utilityTokenAmount?: string;
100
+ discountBps?: number;
101
+ supportedMarketIds?: string[];
102
+ batchCreateSupported?: boolean;
103
+ maxTicketBatchQuantity?: number;
104
+ }
105
+
106
+ export function buildFeeTicketCreateOperation(
107
+ walletInfo: FeeTicketWalletInfo,
108
+ input: {
109
+ root: FeeTicketRootInfo;
110
+ quantity: number;
111
+ rootUtxos: readonly Kcc20IndexedCovenantUtxo[];
112
+ utilityTokenUtxos: readonly Kcc20IndexedCovenantUtxo[];
113
+ },
114
+ options: FeeTicketRootOperationOptions = {},
115
+ ) {
116
+ const root = requireConfiguredRoot(input.root);
117
+ if (!Number.isInteger(input.quantity) || input.quantity < 1) {
118
+ throw new Error("quantity must be a positive integer");
119
+ }
120
+ const maxQuantity = root.maxTicketBatchQuantity ?? 1;
121
+ if (input.quantity > maxQuantity) {
122
+ throw new Error(
123
+ `Active FeeTicket root supports at most ${maxQuantity} ticket(s) per transaction`,
124
+ );
125
+ }
126
+ if (input.quantity > 1 && root.batchCreateSupported !== true) {
127
+ throw new Error("Active FeeTicket root does not support batch creation");
128
+ }
129
+ const rootOwner = requireHex64(root.rootOwner!, "rootOwner");
130
+ const activeRoot = selectSingleRoot(input.rootUtxos, rootOwner);
131
+ const denomination = requireU64(
132
+ root.utilityTokenAmount!,
133
+ "utilityTokenAmount",
134
+ );
135
+ if (denomination <= 0n)
136
+ throw new Error("utilityTokenAmount must be positive");
137
+ const total = denomination * BigInt(input.quantity);
138
+ if (total > U64_MAX)
139
+ throw new Error("FeeTicket batch utility token amount exceeds u64 max");
140
+ const utilityCandidates = ownerNativeHolderUtxos(
141
+ input.utilityTokenUtxos,
142
+ walletInfo.kcc20Owner,
143
+ );
144
+ const utility = selectOwnerNativeHolderUtxo(
145
+ input.utilityTokenUtxos,
146
+ walletInfo.kcc20Owner,
147
+ total.toString(),
148
+ );
149
+ const utilitySummary = summarizeHolderUtxos(
150
+ utilityCandidates,
151
+ total.toString(),
152
+ );
153
+ if (!utility) {
154
+ throw new Error(
155
+ utilitySummary.fragmented
156
+ ? "Utility-token balance is fragmented; consolidate token UTXOs first"
157
+ : "No utility-token holder UTXO has enough balance",
158
+ );
159
+ }
160
+ return buildKcc20WalletOperationFromPlan(
161
+ walletInfo,
162
+ {
163
+ builderKey: "fee-ticket.create-from-utility-burn",
164
+ operation: "create-fee-ticket",
165
+ contract: "FeeTicket",
166
+ action: "createFromUtilityBurn",
167
+ description:
168
+ "Build a FeeTicket creation request from utility-token burn.",
169
+ params: {
170
+ rootId: root.rootId,
171
+ rootOwner,
172
+ utilityTokenId: root.utilityTokenId,
173
+ utilityTokenAmount: root.utilityTokenAmount,
174
+ utilityTokenUtxo: utility,
175
+ activeRootUtxo: activeRoot,
176
+ activeRootCandidateUtxos: [activeRoot],
177
+ quantity: input.quantity,
178
+ totalUtilityTokenAmount: total.toString(),
179
+ discountBps: root.discountBps,
180
+ supportedMarketIds: root.supportedMarketIds ?? [],
181
+ ticketOwner: walletInfo.kcc20Owner,
182
+ ticketOwnerType: "pubkey",
183
+ },
184
+ sourceRequirements: [
185
+ activeRootRequirement(
186
+ root.rootId!,
187
+ rootOwner,
188
+ activeRoot,
189
+ `spend and recreate FeeTicket root while minting ${input.quantity} ticket(s)`,
190
+ ),
191
+ {
192
+ type: "kcc20-utility-token-holder-utxo",
193
+ covenantId: root.utilityTokenId,
194
+ owner: walletInfo.kcc20Owner,
195
+ minimumTokenAmount: total.toString(),
196
+ ...utilitySummary,
197
+ selectedOutpoint: utility.outpoint,
198
+ amount: utility.state?.["amount"],
199
+ address: utility.address,
200
+ identifierType: utility.state?.["identifierType"],
201
+ purpose: "burn utility tokens to mint fee discount tickets",
202
+ },
203
+ ],
204
+ warnings: [
205
+ "Wallet must burn the configured utility-token amount before ticket issuance.",
206
+ "One FeeTicket UTXO represents one fee credit.",
207
+ ],
208
+ },
209
+ options,
210
+ );
211
+ }
212
+
213
+ export function buildFeeTicketBurnOperation(
214
+ walletInfo: FeeTicketWalletInfo,
215
+ input: {
216
+ root: FeeTicketRootInfo;
217
+ ticketOutpoints: readonly string[];
218
+ rootUtxos: readonly Kcc20IndexedCovenantUtxo[];
219
+ },
220
+ options: FeeTicketRootOperationOptions = {},
221
+ ) {
222
+ const root = requireConfiguredRoot(input.root);
223
+ const outpoints = requireDistinctOutpoints(input.ticketOutpoints);
224
+ const selected = selectOwnerFeeTicketUtxos(
225
+ input.rootUtxos,
226
+ walletInfo.kcc20Owner,
227
+ outpoints,
228
+ );
229
+ return buildKcc20WalletOperationFromPlan(
230
+ walletInfo,
231
+ {
232
+ builderKey: "fee-ticket.burn",
233
+ operation: "burn-fee-ticket",
234
+ contract: "FeeTicket",
235
+ action: "burn",
236
+ description: "Build a fee discount ticket burn/cleanup request.",
237
+ params: {
238
+ rootId: root.rootId,
239
+ ticketOutpoints: outpoints,
240
+ ticketUtxos: selected,
241
+ owner: walletInfo.kcc20Owner,
242
+ },
243
+ sourceRequirements: selected.map((ticket) => ({
244
+ type: "fee-ticket-utxo",
245
+ rootId: root.rootId,
246
+ outpoint: ticket.outpoint,
247
+ owner: walletInfo.kcc20Owner,
248
+ selectedOutpoint: ticket.outpoint,
249
+ amountSompi: ticket.amountSompi,
250
+ address: ticket.address,
251
+ })),
252
+ warnings: ["FeeTicket cleanup burns unused tickets only."],
253
+ },
254
+ options,
255
+ );
256
+ }
257
+
258
+ export function buildFeeTicketTransferOperation(
259
+ walletInfo: FeeTicketWalletInfo,
260
+ input: {
261
+ root: FeeTicketRootInfo;
262
+ recipientOwner: string;
263
+ ticketOutpoints: readonly string[];
264
+ rootUtxos: readonly Kcc20IndexedCovenantUtxo[];
265
+ },
266
+ options: FeeTicketRootOperationOptions = {},
267
+ ) {
268
+ const root = requireConfiguredRoot(input.root);
269
+ const owner = normalizeKcc20OwnerIdentifier(
270
+ walletInfo.kcc20Owner,
271
+ "wallet owner",
272
+ );
273
+ const recipientOwner = normalizeKcc20OwnerIdentifier(
274
+ input.recipientOwner,
275
+ "recipientOwner",
276
+ );
277
+ if (recipientOwner === owner)
278
+ throw new Error("recipientOwner must be different from the wallet owner");
279
+ const outpoints = requireDistinctOutpoints(input.ticketOutpoints);
280
+ const selected = selectOwnerFeeTicketUtxos(input.rootUtxos, owner, outpoints);
281
+ return buildKcc20WalletOperationFromPlan(
282
+ walletInfo,
283
+ {
284
+ builderKey: "fee-ticket.transfer",
285
+ operation: "transfer-fee-ticket",
286
+ contract: "FeeTicket",
287
+ action: "transferByOwnerSig",
288
+ description: "Build a fee discount ticket owner-transfer request.",
289
+ params: {
290
+ rootId: root.rootId,
291
+ utilityTokenId: root.utilityTokenId,
292
+ owner,
293
+ recipientOwner,
294
+ ticketOutpoints: outpoints,
295
+ ticketUtxos: selected,
296
+ },
297
+ sourceRequirements: selected.map((ticket) => ({
298
+ type: "fee-ticket-utxo",
299
+ rootId: root.rootId,
300
+ outpoint: ticket.outpoint,
301
+ owner,
302
+ recipientOwner,
303
+ purpose: "transfer FeeTicket ownership while preserving locked KAS",
304
+ selectedOutpoint: ticket.outpoint,
305
+ amountSompi: ticket.amountSompi,
306
+ address: ticket.address,
307
+ })),
308
+ warnings: [
309
+ "Transferred coupons remain one-use FeeTicket UTXOs and retain their locked KAS.",
310
+ "Only the recipient owner can use, burn, or transfer the recreated coupons.",
311
+ ],
312
+ },
313
+ options,
314
+ );
315
+ }
316
+
317
+ export function buildFeeTicketRootUpdateOperation(
318
+ walletInfo: FeeTicketWalletInfo,
319
+ input: {
320
+ root: FeeTicketRootInfo;
321
+ utilityTokenAmount?: string;
322
+ discountBps?: number;
323
+ supportedMarketIds?: string[];
324
+ enabled?: boolean;
325
+ rootUtxos: readonly Kcc20IndexedCovenantUtxo[];
326
+ },
327
+ options: FeeTicketRootOperationOptions = {},
328
+ ) {
329
+ const root = requireConfiguredRoot(input.root, false);
330
+ const rootOwner = requireHex64(root.rootOwner!, "rootOwner");
331
+ if (rootOwner !== walletInfo.kcc20Owner.toLowerCase()) {
332
+ throw new Error("FeeTicket root update requires the configured root owner");
333
+ }
334
+ const activeRoot = selectSingleRoot(input.rootUtxos, rootOwner);
335
+ const utilityTokenAmount = (input.utilityTokenAmount ??
336
+ root.utilityTokenAmount)!;
337
+ if (requireU64(utilityTokenAmount, "utilityTokenAmount") <= 0n) {
338
+ throw new Error("utilityTokenAmount must be positive");
339
+ }
340
+ const discountBps = requireBps(
341
+ input.discountBps ?? root.discountBps!,
342
+ "discountBps",
343
+ );
344
+ const supportedMarketIds = (
345
+ input.supportedMarketIds ??
346
+ root.supportedMarketIds ??
347
+ []
348
+ ).map((id) => requireHex64(id, "supportedMarketIds"));
349
+ return buildKcc20WalletOperationFromPlan(
350
+ walletInfo,
351
+ {
352
+ builderKey: "fee-ticket.update-denomination",
353
+ operation: "update-fee-ticket-denomination",
354
+ contract: "FeeTicket",
355
+ action: "updateDenomination",
356
+ description: "Build a FeeTicket root denomination update request.",
357
+ params: {
358
+ rootId: root.rootId,
359
+ utilityTokenId: root.utilityTokenId,
360
+ currentUtilityTokenAmount: root.utilityTokenAmount,
361
+ utilityTokenAmount,
362
+ discountBps,
363
+ supportedMarketIds,
364
+ enabled: input.enabled ?? root.enabled,
365
+ rootOwner,
366
+ rootOwnerType: "pubkey",
367
+ activeRootUtxo: activeRoot,
368
+ activeRootCandidateUtxos: [activeRoot],
369
+ },
370
+ sourceRequirements: [
371
+ activeRootRequirement(
372
+ root.rootId!,
373
+ rootOwner,
374
+ activeRoot,
375
+ "spend and recreate FeeTicket root with updated denomination",
376
+ ),
377
+ {
378
+ type: "kaspa-funding-utxo",
379
+ ownerAddress: walletInfo.walletAddress,
380
+ purpose: "fund FeeTicket root update transaction",
381
+ },
382
+ ],
383
+ warnings: [
384
+ "Update backend root configuration only after the signed transaction is broadcast and indexed.",
385
+ ],
386
+ },
387
+ options,
388
+ );
389
+ }
390
+
391
+ function requireConfiguredRoot(
392
+ root: FeeTicketRootInfo,
393
+ requireEnabled = true,
394
+ ): Required<
395
+ Pick<
396
+ FeeTicketRootInfo,
397
+ | "rootId"
398
+ | "rootOwner"
399
+ | "utilityTokenId"
400
+ | "utilityTokenAmount"
401
+ | "discountBps"
402
+ >
403
+ > &
404
+ FeeTicketRootInfo {
405
+ if (!root.configured || (requireEnabled && !root.enabled))
406
+ throw new Error("FeeTicket root is not configured");
407
+ requireHex64(root.rootId!, "rootId");
408
+ requireHex64(root.rootOwner!, "rootOwner");
409
+ requireHex64(root.utilityTokenId!, "utilityTokenId");
410
+ requireU64(root.utilityTokenAmount!, "utilityTokenAmount");
411
+ requireBps(root.discountBps!, "discountBps");
412
+ return root as Required<
413
+ Pick<
414
+ FeeTicketRootInfo,
415
+ | "rootId"
416
+ | "rootOwner"
417
+ | "utilityTokenId"
418
+ | "utilityTokenAmount"
419
+ | "discountBps"
420
+ >
421
+ > &
422
+ FeeTicketRootInfo;
423
+ }
424
+
425
+ function selectSingleRoot(
426
+ utxos: readonly Kcc20IndexedCovenantUtxo[],
427
+ rootOwner: string,
428
+ ): Kcc20IndexedCovenantUtxo {
429
+ const candidates = utxos.filter(
430
+ (utxo) =>
431
+ Number(utxo.state?.["mode"] ?? utxo.state?.["stateMode"]) === 1 &&
432
+ (!utxo.state?.["ownerIdentifier"] ||
433
+ String(utxo.state["ownerIdentifier"]).toLowerCase() === rootOwner),
434
+ );
435
+ if (candidates.length !== 1)
436
+ throw new Error(
437
+ candidates.length
438
+ ? "FeeTicket root has multiple active UTXOs"
439
+ : "FeeTicket root has no active UTXO",
440
+ );
441
+ return candidates[0];
442
+ }
443
+
444
+ function activeRootRequirement(
445
+ rootId: string,
446
+ owner: string,
447
+ root: Kcc20IndexedCovenantUtxo,
448
+ purpose: string,
449
+ ) {
450
+ return {
451
+ type: "active-fee-ticket-root",
452
+ rootId,
453
+ owner,
454
+ selectedOutpoint: root.outpoint,
455
+ amountSompi: root.amountSompi,
456
+ address: root.address,
457
+ purpose,
458
+ };
459
+ }
460
+
461
+ function requireDistinctOutpoints(values: readonly string[]): string[] {
462
+ if (!values.length)
463
+ throw new Error("at least one FeeTicket outpoint is required");
464
+ const normalized = values.map((value) => {
465
+ const item = String(value).trim().toLowerCase();
466
+ if (!/^[a-f0-9]{64}:\d+$/.test(item))
467
+ throw new Error(`invalid FeeTicket outpoint: ${value}`);
468
+ return item;
469
+ });
470
+ if (new Set(normalized).size !== normalized.length)
471
+ throw new Error("FeeTicket outpoints must be distinct");
472
+ return normalized;
473
+ }
474
+
475
+ function requireHex64(value: string, field: string): string {
476
+ const normalized = String(value ?? "")
477
+ .trim()
478
+ .toLowerCase()
479
+ .replace(/^0x/, "");
480
+ if (!HEX_64_RE.test(normalized)) {
481
+ throw new Error(`${field} must be 64 hex characters`);
482
+ }
483
+ return normalized;
484
+ }
485
+
486
+ function requireU64(value: string, field: string): bigint {
487
+ const normalized = String(value ?? "").trim();
488
+ if (!/^(0|[1-9]\d*)$/.test(normalized)) {
489
+ throw new Error(`${field} must be an unsigned integer`);
490
+ }
491
+ const parsed = BigInt(normalized);
492
+ if (parsed > U64_MAX) {
493
+ throw new Error(`${field} exceeds u64 max`);
494
+ }
495
+ return parsed;
496
+ }
497
+
498
+ function requireBps(value: number, field: string): number {
499
+ if (!Number.isInteger(value) || value < 0 || value > 10_000) {
500
+ throw new Error(`${field} must be 0..10000`);
501
+ }
502
+ return value;
503
+ }
504
+
505
+ function randomOperationId(): string {
506
+ return globalThis.crypto?.randomUUID?.() ?? fallbackRandomId();
507
+ }
508
+
509
+ function fallbackRandomId(): string {
510
+ const bytes = new Uint8Array(16);
511
+ globalThis.crypto?.getRandomValues?.(bytes);
512
+ if (bytes.some((byte) => byte !== 0)) {
513
+ return [...bytes]
514
+ .map((byte) => byte.toString(16).padStart(2, "0"))
515
+ .join("");
516
+ }
517
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
518
+ }
package/src/index.ts ADDED
@@ -0,0 +1,27 @@
1
+ export * from "./artifacts.js";
2
+ export * from "./action-source-resolver.js";
3
+ export * from "./abi.js";
4
+ export * from "./builder.js";
5
+ export * from "./deploy-operation.js";
6
+ export * from "./errors.js";
7
+ export * from "./encoding.js";
8
+ export * from "./engine.js";
9
+ export * from "./fee-ticket-operation.js";
10
+ export * from "./ladder-limit-orders.js";
11
+ export * from "./mint-availability-operation.js";
12
+ export * from "./models.js";
13
+ export * from "./operations.js";
14
+ export * from "./paid-mint-amount.js";
15
+ export * from "./token-amount.js";
16
+ export * from "./token-operation.js";
17
+ export * from "./trade-fee.js";
18
+ export * from "./trading-operation.js";
19
+ export * from "./protocol.js";
20
+ export * from "./runtime.js";
21
+ export * as saleCommon from "./sale-common.js";
22
+ export * from "./source-provider.js";
23
+ export * from "./source-validation.js";
24
+ export * from "./wasm-provider.js";
25
+ export * from "./wallet-operation.js";
26
+ export * from "./wrapper-operation.js";
27
+ export * from "./verify-operation.js";