@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
@@ -0,0 +1,531 @@
1
+ import { buildKcc20WalletOperationFromPlan } from "./wallet-operation.js";
2
+ import {
3
+ ownerNativeHolderUtxos,
4
+ ownerWrappedHolderUtxos,
5
+ resolveActiveWrapperUtxo,
6
+ selectOwnerNativeHolderUtxo,
7
+ selectOwnerWrappedHolderUtxo,
8
+ selectWrapperReserveUtxo,
9
+ summarizeHolderUtxos,
10
+ type Kcc20IndexedCovenantUtxo,
11
+ type Kcc20WrapperSourceInfo,
12
+ } from "./action-source-resolver.js";
13
+ import {
14
+ kcc20DisplayScaleForDecimals,
15
+ normalizeKcc20Decimals,
16
+ parseKcc20DisplayAmountToBaseUnits,
17
+ } from "./token-amount.js";
18
+
19
+ export interface Kcc20WrapperWalletInfo {
20
+ walletAddress: string;
21
+ kcc20Owner: string;
22
+ }
23
+
24
+ export interface Kcc20WrapperMarketInfo {
25
+ wrapperId: string;
26
+ marketId: string;
27
+ canonicalTokenId?: string | null;
28
+ contractCanonicalTokenId?: string | null;
29
+ feeTicketId?: string | null;
30
+ }
31
+
32
+ export interface Kcc20WrapperActiveResolution {
33
+ selected?: Record<string, unknown> | null;
34
+ candidates?: Record<string, unknown>[];
35
+ ambiguous?: boolean;
36
+ activeCovenantId?: string | null;
37
+ }
38
+
39
+ export interface Kcc20WrapperHolderSummary {
40
+ aggregateAmount?: string;
41
+ maxSingleUtxoAmount?: string;
42
+ utxoCount?: number;
43
+ fragmented?: boolean;
44
+ maxSingleUtxoOutpoint?: string;
45
+ recommendedAction?: string;
46
+ }
47
+
48
+ export interface Kcc20BuildWrapOperationInput {
49
+ canonicalCovenantId: string;
50
+ wrappedMarketId: string;
51
+ tokenAmount: string;
52
+ priceScale?: string;
53
+ wrapper: Kcc20WrapperMarketInfo;
54
+ activeHolderUtxo: Record<string, unknown> | null;
55
+ activeHolderSummary?: Kcc20WrapperHolderSummary;
56
+ activeWrapperResolution?: Kcc20WrapperActiveResolution;
57
+ activeHolderNativeCovenantId?: string | null;
58
+ }
59
+
60
+ export interface Kcc20WrapOperationOptions {
61
+ network?: string;
62
+ createdAt?: string;
63
+ requestId?: string;
64
+ }
65
+
66
+ export function buildKcc20WrapTokenOperation(
67
+ walletInfo: Kcc20WrapperWalletInfo,
68
+ input: Kcc20BuildWrapOperationInput,
69
+ options: Kcc20WrapOperationOptions = {},
70
+ ) {
71
+ const appCanonicalId = normalizeHex32(
72
+ input.canonicalCovenantId,
73
+ "canonicalCovenantId",
74
+ );
75
+ const wrappedMarketId = normalizeHex32(
76
+ input.wrappedMarketId || input.wrapper.marketId,
77
+ "wrappedMarketId",
78
+ );
79
+ const holderNativeCovenantId = input.activeHolderNativeCovenantId
80
+ ? normalizeHex32(
81
+ input.activeHolderNativeCovenantId,
82
+ "activeHolderNativeCovenantId",
83
+ )
84
+ : undefined;
85
+ const contractCanonicalId = normalizeHex32(
86
+ input.wrapper.contractCanonicalTokenId ??
87
+ input.wrapper.canonicalTokenId ??
88
+ holderNativeCovenantId ??
89
+ appCanonicalId,
90
+ "contractCanonicalCovenantId",
91
+ );
92
+ const activeWrapperCovenantId = input.activeWrapperResolution
93
+ ?.activeCovenantId
94
+ ? normalizeHex32(
95
+ input.activeWrapperResolution.activeCovenantId,
96
+ "activeWrapperCovenantId",
97
+ )
98
+ : undefined;
99
+ const tokenAmount = parsePositiveIntegerString(
100
+ input.tokenAmount,
101
+ "tokenAmount",
102
+ );
103
+ const priceScale = input.priceScale
104
+ ? parsePositiveIntegerString(input.priceScale, "priceScale")
105
+ : undefined;
106
+
107
+ return buildKcc20WalletOperationFromPlan(
108
+ walletInfo,
109
+ {
110
+ builderKey: "kcc20wrapper.wrap",
111
+ operation: "wrap-token",
112
+ contract: "KCC20Wrapper",
113
+ action: "wrap",
114
+ description: "Build a KCC20 to DEX Orderbook V1 deposit request.",
115
+ params: {
116
+ canonicalCovenantId: contractCanonicalId,
117
+ appCanonicalCovenantId: appCanonicalId,
118
+ contractCanonicalCovenantId: contractCanonicalId,
119
+ ...(holderNativeCovenantId
120
+ ? { activeHolderNativeCovenantId: holderNativeCovenantId }
121
+ : {}),
122
+ wrappedMarketId,
123
+ tokenAmount,
124
+ ...(priceScale ? { priceScale } : {}),
125
+ recipientOwner: normalizeHex32(walletInfo.kcc20Owner, "kcc20Owner"),
126
+ recipientOwnerType: "pubkey",
127
+ feeTicketId: input.wrapper.feeTicketId ?? null,
128
+ ...(activeWrapperCovenantId ? { activeWrapperCovenantId } : {}),
129
+ activeHolderUtxo: input.activeHolderUtxo,
130
+ activeWrapperUtxo: input.activeWrapperResolution?.selected,
131
+ activeWrapperCandidateUtxos:
132
+ input.activeWrapperResolution?.candidates ?? [],
133
+ },
134
+ sourceRequirements: [
135
+ {
136
+ type: "kcc20-holder-utxo",
137
+ covenantId: appCanonicalId,
138
+ ...(contractCanonicalId !== appCanonicalId
139
+ ? { contractCanonicalCovenantId: contractCanonicalId }
140
+ : {}),
141
+ ...(holderNativeCovenantId
142
+ ? { activeHolderNativeCovenantId: holderNativeCovenantId }
143
+ : {}),
144
+ owner: normalizeHex32(walletInfo.kcc20Owner, "kcc20Owner"),
145
+ minimumTokenAmount: tokenAmount,
146
+ ...holderSummaryFields(input.activeHolderSummary),
147
+ outpoint: input.activeHolderUtxo
148
+ ? utxoOutpoint(input.activeHolderUtxo)
149
+ : undefined,
150
+ amount: utxoStateAmount(input.activeHolderUtxo),
151
+ },
152
+ wrapperReserveOutput({
153
+ wrapperId: wrappedMarketId,
154
+ activeWrapperResolution: input.activeWrapperResolution,
155
+ purpose:
156
+ "increase wrapper reserve and issue DEX Orderbook V1 holder output",
157
+ }),
158
+ ],
159
+ warnings: [
160
+ "Wallet must preserve canonical reserve accounting between KCC20 escrow and orderbook supply.",
161
+ ],
162
+ },
163
+ options,
164
+ );
165
+ }
166
+
167
+ export interface Kcc20BuildWrapFromSnapshotInput {
168
+ canonicalCovenantId: string;
169
+ tokenDecimals?: number | null;
170
+ wrappedMarketId: string;
171
+ tokenAmount: string;
172
+ wrapper: Kcc20WrapperSourceInfo;
173
+ canonicalTokenUtxos: readonly Kcc20IndexedCovenantUtxo[];
174
+ wrapperUtxos: readonly Kcc20IndexedCovenantUtxo[];
175
+ }
176
+
177
+ /**
178
+ * Resolves the native holder and wrapper controller in the shared package.
179
+ * The host only supplies a generic indexed snapshot; no action planner is
180
+ * consulted.
181
+ */
182
+ export function buildKcc20WrapTokenOperationFromSnapshot(
183
+ walletInfo: Kcc20WrapperWalletInfo,
184
+ input: Kcc20BuildWrapFromSnapshotInput,
185
+ options: Kcc20WrapOperationOptions = {},
186
+ ) {
187
+ if (input.wrapper.enabled === false) {
188
+ throw new Error("Wrapped trading is disabled for this market");
189
+ }
190
+ const decimals = normalizeKcc20Decimals(input.tokenDecimals);
191
+ const tokenAmount = parseKcc20DisplayAmountToBaseUnits(
192
+ input.tokenAmount,
193
+ decimals,
194
+ "tokenAmount",
195
+ );
196
+ const ownerUtxos = ownerNativeHolderUtxos(
197
+ input.canonicalTokenUtxos,
198
+ walletInfo.kcc20Owner,
199
+ );
200
+ const holder = selectOwnerNativeHolderUtxo(
201
+ input.canonicalTokenUtxos,
202
+ walletInfo.kcc20Owner,
203
+ tokenAmount,
204
+ );
205
+ const summary = summarizeHolderUtxos(ownerUtxos, tokenAmount);
206
+ if (!holder) {
207
+ throw new Error(
208
+ summary.fragmented
209
+ ? "No single KCC20 holder UTXO is large enough; consolidate the token balance first"
210
+ : "No KCC20 holder UTXO is available for this wallet and amount",
211
+ );
212
+ }
213
+ const wrapperResolution = resolveActiveWrapperUtxo(
214
+ input.wrapper,
215
+ input.wrapperUtxos,
216
+ );
217
+ return buildKcc20WrapTokenOperation(
218
+ walletInfo,
219
+ {
220
+ canonicalCovenantId: input.canonicalCovenantId,
221
+ wrappedMarketId: input.wrapper.marketId || input.wrappedMarketId,
222
+ tokenAmount,
223
+ priceScale: kcc20DisplayScaleForDecimals(decimals).toString(),
224
+ wrapper: input.wrapper,
225
+ activeHolderUtxo: holder,
226
+ activeHolderSummary: summary,
227
+ activeWrapperResolution: wrapperResolution,
228
+ activeHolderNativeCovenantId:
229
+ holder.covenantId ??
230
+ input.wrapper.contractCanonicalTokenId ??
231
+ input.canonicalCovenantId,
232
+ },
233
+ options,
234
+ );
235
+ }
236
+
237
+ export interface Kcc20BuildUnwrapFromSnapshotInput {
238
+ canonicalCovenantId: string;
239
+ tokenDecimals?: number | null;
240
+ wrappedMarketId: string;
241
+ tokenAmount: string;
242
+ wrapper: Kcc20WrapperSourceInfo;
243
+ canonicalTokenUtxos: readonly Kcc20IndexedCovenantUtxo[];
244
+ wrappedTokenUtxos: readonly Kcc20IndexedCovenantUtxo[];
245
+ wrapperUtxos: readonly Kcc20IndexedCovenantUtxo[];
246
+ }
247
+
248
+ export function buildKcc20UnwrapTokenOperationFromSnapshot(
249
+ walletInfo: Kcc20WrapperWalletInfo,
250
+ input: Kcc20BuildUnwrapFromSnapshotInput,
251
+ options: Kcc20WrapOperationOptions = {},
252
+ ) {
253
+ if (input.wrapper.enabled === false) {
254
+ throw new Error("Wrapped trading is disabled for this market");
255
+ }
256
+ const appCanonicalId = normalizeHex32(
257
+ input.canonicalCovenantId,
258
+ "canonicalCovenantId",
259
+ );
260
+ const contractCanonicalId = normalizeHex32(
261
+ input.wrapper.contractCanonicalTokenId ??
262
+ input.wrapper.canonicalTokenId ??
263
+ appCanonicalId,
264
+ "contractCanonicalCovenantId",
265
+ );
266
+ const marketId = normalizeHex32(
267
+ input.wrapper.marketId || input.wrappedMarketId,
268
+ "wrappedMarketId",
269
+ );
270
+ const decimals = normalizeKcc20Decimals(input.tokenDecimals);
271
+ const tokenAmount = parseKcc20DisplayAmountToBaseUnits(
272
+ input.tokenAmount,
273
+ decimals,
274
+ "tokenAmount",
275
+ );
276
+ const holderCandidates = ownerWrappedHolderUtxos(
277
+ input.wrappedTokenUtxos,
278
+ walletInfo.kcc20Owner,
279
+ contractCanonicalId,
280
+ );
281
+ const holder = selectOwnerWrappedHolderUtxo(
282
+ input.wrappedTokenUtxos,
283
+ walletInfo.kcc20Owner,
284
+ tokenAmount,
285
+ contractCanonicalId,
286
+ );
287
+ const holderSummary = summarizeHolderUtxos(holderCandidates, tokenAmount);
288
+ if (!holder) {
289
+ throw new Error(
290
+ holderSummary.fragmented
291
+ ? "No single wrapped holder UTXO is large enough; consolidate the wrapped balance first"
292
+ : "No wrapped holder UTXO is available for this wallet and amount",
293
+ );
294
+ }
295
+ const wrapperResolution = resolveActiveWrapperUtxo(
296
+ input.wrapper,
297
+ input.wrapperUtxos,
298
+ );
299
+ const reserveIds = [
300
+ wrapperResolution.activeCovenantId,
301
+ input.wrapper.activeCovenantId,
302
+ input.wrapper.wrapperId,
303
+ marketId,
304
+ ].filter((value): value is string => Boolean(value));
305
+ const reserve = selectWrapperReserveUtxo(
306
+ input.canonicalTokenUtxos,
307
+ reserveIds,
308
+ tokenAmount,
309
+ );
310
+ if (!reserve) {
311
+ throw new Error(
312
+ "Wrapper reserve does not contain enough canonical token balance",
313
+ );
314
+ }
315
+
316
+ return buildKcc20WalletOperationFromPlan(
317
+ walletInfo,
318
+ {
319
+ builderKey: "kcc20wrapper.unwrap",
320
+ operation: "unwrap",
321
+ contract: "KCC20Wrapper",
322
+ action: "unwrap",
323
+ description:
324
+ "Build a KCC20 DEX Orderbook V1 to Token V1 withdrawal request.",
325
+ params: {
326
+ canonicalCovenantId: contractCanonicalId,
327
+ appCanonicalCovenantId: appCanonicalId,
328
+ contractCanonicalCovenantId: contractCanonicalId,
329
+ wrappedMarketId: marketId,
330
+ tokenAmount,
331
+ recipientOwner: normalizeHex32(walletInfo.kcc20Owner, "kcc20Owner"),
332
+ recipientOwnerType: "pubkey",
333
+ ...(wrapperResolution.activeCovenantId
334
+ ? { activeWrapperCovenantId: wrapperResolution.activeCovenantId }
335
+ : {}),
336
+ activeWrappedHolderUtxo: holder,
337
+ activeReserveUtxo: reserve,
338
+ activeWrapperUtxo: wrapperResolution.selected,
339
+ activeWrapperCandidateUtxos: wrapperResolution.candidates,
340
+ },
341
+ sourceRequirements: [
342
+ {
343
+ type: "kcc20-orderbook-holder-utxo",
344
+ covenantId: marketId,
345
+ contractCanonicalCovenantId: contractCanonicalId,
346
+ owner: walletInfo.kcc20Owner,
347
+ minimumTokenAmount: tokenAmount,
348
+ ...holderSummaryFields(holderSummary),
349
+ outpoint: holder.outpoint,
350
+ amount: holder.state?.["amount"],
351
+ },
352
+ wrapperReserveOutput({
353
+ wrapperId: marketId,
354
+ activeWrapperResolution: wrapperResolution,
355
+ reserveUtxo: reserve,
356
+ purpose:
357
+ "release canonical Token V1 reserve and burn DEX Orderbook V1 amount",
358
+ }),
359
+ ],
360
+ warnings: [
361
+ "Wallet must prove wrapped burn and canonical reserve release in the same transaction.",
362
+ ],
363
+ },
364
+ options,
365
+ );
366
+ }
367
+
368
+ export function buildKcc20WrapperMarketDeployOperation(
369
+ walletInfo: Kcc20WrapperWalletInfo,
370
+ input: {
371
+ canonicalCovenantId: string;
372
+ contractCanonicalCovenantId?: string | null;
373
+ tokenDecimals?: number | null;
374
+ wrappedRootAmount?: string;
375
+ feeTicketId?: string | null;
376
+ },
377
+ options: Kcc20WrapOperationOptions = {},
378
+ ) {
379
+ const appCanonicalId = normalizeHex32(
380
+ input.canonicalCovenantId,
381
+ "canonicalCovenantId",
382
+ );
383
+ const contractCanonicalId = normalizeHex32(
384
+ input.contractCanonicalCovenantId ?? appCanonicalId,
385
+ "contractCanonicalCovenantId",
386
+ );
387
+ const rootAmount = String(input.wrappedRootAmount ?? "0").trim();
388
+ if (!/^\d+$/.test(rootAmount)) {
389
+ throw new Error("wrappedRootAmount must be an unsigned integer string");
390
+ }
391
+ const decimals = normalizeKcc20Decimals(input.tokenDecimals);
392
+ const feeTicketId = input.feeTicketId
393
+ ? normalizeHex32(input.feeTicketId, "feeTicketId")
394
+ : null;
395
+ return buildKcc20WalletOperationFromPlan(
396
+ walletInfo,
397
+ {
398
+ builderKey: "kcc20wrapper.deploy-market",
399
+ operation: "deploy-wrapper-market",
400
+ contract: "KCC20Wrapper",
401
+ action: "deployMarket",
402
+ description: "Build a KCC20 Wrapper Reserve V1 market deploy request.",
403
+ params: {
404
+ canonicalCovenantId: appCanonicalId,
405
+ contractCanonicalCovenantId: contractCanonicalId,
406
+ feeTicketId,
407
+ wrappedRootAmount: rootAmount,
408
+ priceScale: kcc20DisplayScaleForDecimals(decimals).toString(),
409
+ tokenDecimals: String(decimals),
410
+ deployerOwner: normalizeHex32(walletInfo.kcc20Owner, "kcc20Owner"),
411
+ deployerOwnerType: "pubkey",
412
+ },
413
+ sourceRequirements: [
414
+ {
415
+ type: "canonical-kcc20-token",
416
+ covenantId: appCanonicalId,
417
+ ...(contractCanonicalId !== appCanonicalId
418
+ ? { contractCanonicalCovenantId: contractCanonicalId }
419
+ : {}),
420
+ purpose: "bind wrapper market to the canonical token identity",
421
+ },
422
+ {
423
+ type: "kaspa-funding-utxo",
424
+ ownerAddress: walletInfo.walletAddress,
425
+ purpose: "fund wrapper/root outputs and network fee",
426
+ },
427
+ {
428
+ type: "compiled-contract-artifacts",
429
+ templates: ["KCC20Wrapper", "KCC20Orderbook"],
430
+ },
431
+ ...(feeTicketId
432
+ ? [
433
+ {
434
+ type: "fee-ticket-root",
435
+ covenantId: feeTicketId,
436
+ purpose: "bind configured standalone fill discount policy",
437
+ },
438
+ ]
439
+ : []),
440
+ ],
441
+ warnings: [
442
+ "The canonical KCC20 covenantId remains the token identity; the wrapper market ID is only the DEX orderbook mode.",
443
+ "Wrapper deploy builders must create both wrapper reserve and wrapped-root outputs consistently.",
444
+ ],
445
+ },
446
+ options,
447
+ );
448
+ }
449
+
450
+ function wrapperReserveOutput(input: {
451
+ wrapperId: string;
452
+ activeWrapperResolution?: Kcc20WrapperActiveResolution;
453
+ reserveUtxo?: Record<string, unknown> | null;
454
+ purpose: string;
455
+ }): Record<string, unknown> {
456
+ const selected = input.activeWrapperResolution?.selected;
457
+ return {
458
+ type: "wrapper-reserve-output",
459
+ wrapperId: input.wrapperId,
460
+ ...(input.reserveUtxo
461
+ ? {
462
+ reserveOutpoint: utxoOutpoint(input.reserveUtxo),
463
+ reserveAmount: utxoStateAmount(input.reserveUtxo),
464
+ }
465
+ : {}),
466
+ ...(selected
467
+ ? {
468
+ selectedOutpoint: selected["outpoint"],
469
+ amountSompi: selected["amountSompi"],
470
+ address: selected["address"],
471
+ }
472
+ : {}),
473
+ ...(input.activeWrapperResolution?.ambiguous
474
+ ? {
475
+ ambiguous: true,
476
+ candidateOutpoints: (
477
+ input.activeWrapperResolution.candidates ?? []
478
+ ).map((candidate) => candidate["outpoint"]),
479
+ }
480
+ : {}),
481
+ purpose: input.purpose,
482
+ };
483
+ }
484
+
485
+ function holderSummaryFields(
486
+ summary?: Kcc20WrapperHolderSummary,
487
+ ): Record<string, unknown> {
488
+ if (!summary) return {};
489
+ return {
490
+ aggregateAmount: summary.aggregateAmount,
491
+ maxSingleUtxoAmount: summary.maxSingleUtxoAmount,
492
+ utxoCount: summary.utxoCount,
493
+ fragmented: summary.fragmented,
494
+ maxSingleUtxoOutpoint: summary.maxSingleUtxoOutpoint,
495
+ recommendedAction: summary.recommendedAction,
496
+ };
497
+ }
498
+
499
+ function utxoOutpoint(utxo: Record<string, unknown>): string | undefined {
500
+ const txidHex = typeof utxo["txidHex"] === "string" ? utxo["txidHex"] : "";
501
+ const vout = utxo["vout"];
502
+ if (!txidHex || (typeof vout !== "number" && typeof vout !== "string")) {
503
+ return undefined;
504
+ }
505
+ return `${txidHex}:${vout}`;
506
+ }
507
+
508
+ function utxoStateAmount(
509
+ utxo: Record<string, unknown> | null,
510
+ ): string | undefined {
511
+ const state =
512
+ utxo && typeof utxo["state"] === "object" && utxo["state"] !== null
513
+ ? (utxo["state"] as Record<string, unknown>)
514
+ : undefined;
515
+ return typeof state?.["amount"] === "string" ? state["amount"] : undefined;
516
+ }
517
+
518
+ function parsePositiveIntegerString(value: string, field: string): string {
519
+ if (!/^[1-9]\d*$/.test(value)) {
520
+ throw new Error(`${field} must be a positive integer string`);
521
+ }
522
+ return value;
523
+ }
524
+
525
+ function normalizeHex32(value: string, field: string): string {
526
+ const normalized = value.trim().toLowerCase().replace(/^0x/, "");
527
+ if (!/^[0-9a-f]{64}$/.test(normalized)) {
528
+ throw new Error(`${field} must be 64 hex characters`);
529
+ }
530
+ return normalized;
531
+ }