@kaspacom/kcc20-tx-builder 0.2.0 → 0.4.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 (127) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +54 -8
  3. package/RELEASING.md +93 -0
  4. package/SECURITY.md +16 -0
  5. package/artifacts/KCC20SnapshotBootstrap.placeholder.json +101677 -0
  6. package/artifacts/KCC20SnapshotController.placeholder.json +46547 -0
  7. package/artifacts/kcc20-snapshot-bootstrap/KCC20SnapshotBootstrap.placeholder.json +101677 -0
  8. package/artifacts/kcc20-snapshot-controller/KCC20SnapshotController.placeholder.json +46547 -0
  9. package/artifacts/snapshot-standards.lock.json +56 -0
  10. package/dist/artifacts.d.ts +31 -0
  11. package/dist/artifacts.d.ts.map +1 -1
  12. package/dist/artifacts.js +31 -0
  13. package/dist/artifacts.js.map +1 -1
  14. package/dist/cjs/artifacts.d.ts +31 -0
  15. package/dist/cjs/artifacts.js +32 -1
  16. package/dist/cjs/artifacts.js.map +1 -1
  17. package/dist/cjs/engine.js +58 -44
  18. package/dist/cjs/engine.js.map +1 -1
  19. package/dist/cjs/index.d.ts +7 -0
  20. package/dist/cjs/index.js +8 -1
  21. package/dist/cjs/index.js.map +1 -1
  22. package/dist/cjs/operations.d.ts +1 -1
  23. package/dist/cjs/operations.js +4 -0
  24. package/dist/cjs/operations.js.map +1 -1
  25. package/dist/cjs/sale-common.d.ts +1 -1
  26. package/dist/cjs/sale-common.js +39 -4
  27. package/dist/cjs/sale-common.js.map +1 -1
  28. package/dist/cjs/snapshot-deployment.d.ts +82 -0
  29. package/dist/cjs/snapshot-deployment.js +82 -0
  30. package/dist/cjs/snapshot-deployment.js.map +1 -0
  31. package/dist/cjs/snapshot-operation.d.ts +27 -0
  32. package/dist/cjs/snapshot-operation.js +2433 -0
  33. package/dist/cjs/snapshot-operation.js.map +1 -0
  34. package/dist/cjs/snapshot-signerless-transactions.d.ts +132 -0
  35. package/dist/cjs/snapshot-signerless-transactions.js +746 -0
  36. package/dist/cjs/snapshot-signerless-transactions.js.map +1 -0
  37. package/dist/cjs/snapshot-signerless.d.ts +226 -0
  38. package/dist/cjs/snapshot-signerless.js +1022 -0
  39. package/dist/cjs/snapshot-signerless.js.map +1 -0
  40. package/dist/cjs/snapshot-verification.d.ts +57 -0
  41. package/dist/cjs/snapshot-verification.js +96 -0
  42. package/dist/cjs/snapshot-verification.js.map +1 -0
  43. package/dist/cjs/token-operation.js +3 -3
  44. package/dist/cjs/token-operation.js.map +1 -1
  45. package/dist/engine.d.ts.map +1 -1
  46. package/dist/engine.js +58 -44
  47. package/dist/engine.js.map +1 -1
  48. package/dist/index.d.ts +7 -0
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +7 -0
  51. package/dist/index.js.map +1 -1
  52. package/dist/operations.d.ts +1 -1
  53. package/dist/operations.d.ts.map +1 -1
  54. package/dist/operations.js +4 -0
  55. package/dist/operations.js.map +1 -1
  56. package/dist/sale-common.d.ts +1 -1
  57. package/dist/sale-common.d.ts.map +1 -1
  58. package/dist/sale-common.js +39 -4
  59. package/dist/sale-common.js.map +1 -1
  60. package/dist/snapshot-deployment.d.ts +83 -0
  61. package/dist/snapshot-deployment.d.ts.map +1 -0
  62. package/dist/snapshot-deployment.js +78 -0
  63. package/dist/snapshot-deployment.js.map +1 -0
  64. package/dist/snapshot-operation.d.ts +28 -0
  65. package/dist/snapshot-operation.d.ts.map +1 -0
  66. package/dist/snapshot-operation.js +2428 -0
  67. package/dist/snapshot-operation.js.map +1 -0
  68. package/dist/snapshot-signerless-transactions.d.ts +133 -0
  69. package/dist/snapshot-signerless-transactions.d.ts.map +1 -0
  70. package/dist/snapshot-signerless-transactions.js +735 -0
  71. package/dist/snapshot-signerless-transactions.js.map +1 -0
  72. package/dist/snapshot-signerless.d.ts +227 -0
  73. package/dist/snapshot-signerless.d.ts.map +1 -0
  74. package/dist/snapshot-signerless.js +993 -0
  75. package/dist/snapshot-signerless.js.map +1 -0
  76. package/dist/snapshot-verification.d.ts +58 -0
  77. package/dist/snapshot-verification.d.ts.map +1 -0
  78. package/dist/snapshot-verification.js +92 -0
  79. package/dist/snapshot-verification.js.map +1 -0
  80. package/dist/token-operation.js +3 -3
  81. package/dist/token-operation.js.map +1 -1
  82. package/docs/API.md +67 -0
  83. package/docs/COMPATIBILITY.json +84 -0
  84. package/docs/INTEGRATION.md +215 -0
  85. package/docs/LIVE_TRANSFER.md +106 -0
  86. package/docs/RECIPES.md +175 -0
  87. package/docs/TRANSACTION_GUIDE.md +163 -0
  88. package/docs/snapshot-start.md +27 -0
  89. package/examples/README.md +81 -0
  90. package/examples/backend/cli.ts +55 -0
  91. package/examples/backend/recipe.ts +39 -0
  92. package/examples/backend/runtime.ts +13 -0
  93. package/examples/frontend/index.html +48 -0
  94. package/examples/frontend/main.ts +213 -0
  95. package/examples/frontend/runtime.ts +14 -0
  96. package/examples/package-lock.json +1287 -0
  97. package/examples/package.json +23 -0
  98. package/examples/shared/build.ts +179 -0
  99. package/examples/shared/fixture.ts +24 -0
  100. package/examples/shared/live-transfer.ts +155 -0
  101. package/examples/shared/recipe-fixtures.ts +543 -0
  102. package/examples/shared/recipes.ts +55 -0
  103. package/examples/shared/signing.ts +110 -0
  104. package/examples/shared/timeout.ts +8 -0
  105. package/examples/test/browser.ts +116 -0
  106. package/examples/test/build.test.ts +197 -0
  107. package/examples/test/recipes.test.ts +59 -0
  108. package/examples/test/sources.test.ts +73 -0
  109. package/examples/tsconfig.json +26 -0
  110. package/examples/vendor/NOTICE.md +15 -0
  111. package/examples/vendor/kaspa/LICENSE +15 -0
  112. package/examples/vendor/kaspa/kaspa.d.ts +8908 -0
  113. package/examples/vendor/kaspa/kaspa.js +15202 -0
  114. package/examples/vendor/kaspa/kaspa_bg.wasm +0 -0
  115. package/examples/vendor/kaspa/package.json +24 -0
  116. package/package.json +21 -4
  117. package/src/artifacts.ts +35 -0
  118. package/src/engine.ts +91 -97
  119. package/src/index.ts +8 -0
  120. package/src/operations.ts +4 -0
  121. package/src/sale-common.ts +44 -3
  122. package/src/snapshot-deployment.ts +108 -0
  123. package/src/snapshot-operation.ts +3592 -0
  124. package/src/snapshot-signerless-transactions.ts +1272 -0
  125. package/src/snapshot-signerless.ts +1525 -0
  126. package/src/snapshot-verification.ts +86 -0
  127. package/src/token-operation.ts +3 -3
@@ -0,0 +1,543 @@
1
+ import { buildStandaloneFillFeeQuote } from "@kaspacom/kcc20-tx-builder/trade-fee";
2
+ import {
3
+ buildKcc20ScriptForState,
4
+ mintExtensionCommitment,
5
+ } from "@kaspacom/kcc20-tx-builder/abi";
6
+ import {
7
+ buildKcc20OrderbookScriptForState,
8
+ buildKcc20WrapperScriptForState,
9
+ buildKcc20FeeTicketScriptForState,
10
+ } from "@kaspacom/kcc20-tx-builder/sale-common";
11
+ import type { Kcc20IndexedCovenantUtxo } from "@kaspacom/kcc20-tx-builder/action-source-resolver";
12
+ import {
13
+ createBuilder,
14
+ deployOperation,
15
+ network,
16
+ type Runtime,
17
+ type Operation,
18
+ } from "./build.ts";
19
+ import { fixture, owner, recipient } from "./fixture.ts";
20
+ import { recipes } from "./recipes.ts";
21
+
22
+ // These are independent synthetic source snapshots, not a simulated chain ledger.
23
+ // Never broadcast their transactions or fund their public test-vector addresses.
24
+ export async function recipeFixtures(
25
+ wasm: Runtime,
26
+ loadArtifact: (key: string) => Promise<unknown>,
27
+ ) {
28
+ const data = fixture(wasm);
29
+ const build = createBuilder(wasm, data.sources, loadArtifact);
30
+ const options = {
31
+ network,
32
+ createdAt: "2026-01-01T00:00:00.000Z",
33
+ requestId: "offline-recipe",
34
+ };
35
+ const deploy = await build(deployOperation(data.wallet));
36
+ const baseHolder = data.addDeploy(deploy);
37
+ const tokenId = baseHolder.covenantId!;
38
+ const marketId = "22".repeat(32),
39
+ feeId = "33".repeat(32),
40
+ zero = "00".repeat(32);
41
+ const hex = (value: string) =>
42
+ Uint8Array.from(value.match(/../g)!, (x) => parseInt(x, 16));
43
+ const [native, wrapped, wrapper, fee] = await Promise.all(
44
+ ["KCC20", "KCC20Orderbook", "KCC20Wrapper", "KCC20FeeTicket"].map(
45
+ async (name) =>
46
+ (await loadArtifact(`${name}.placeholder.json`)) as {
47
+ script: number[];
48
+ },
49
+ ),
50
+ );
51
+ let sequence = 1;
52
+ function add(
53
+ script: Uint8Array,
54
+ state: Record<string, unknown>,
55
+ covenantId: string,
56
+ amountSompi = "100000000",
57
+ ): Kcc20IndexedCovenantUtxo {
58
+ const scriptPublicKey = wasm.payToScriptHashScript(script);
59
+ const address = wasm
60
+ .addressFromScriptPublicKey(scriptPublicKey, network)!
61
+ .toString();
62
+ const txidHex = (sequence++).toString(16).padStart(64, "0");
63
+ const row = {
64
+ address,
65
+ outpoint: { transactionId: txidHex, index: 0 },
66
+ amount: BigInt(amountSompi),
67
+ scriptPublicKey,
68
+ covenantId,
69
+ blockDaaScore: 1n,
70
+ isCoinbase: false,
71
+ };
72
+ data.rows.set(address, [...(data.rows.get(address) ?? []), row]);
73
+ return {
74
+ txidHex,
75
+ vout: 0,
76
+ outpoint: `${txidHex}:0`,
77
+ address,
78
+ amountSompi,
79
+ covenantId,
80
+ state,
81
+ };
82
+ }
83
+ function holder(amount: string, ownerIdentifier = owner, ownerScheme = 0) {
84
+ const state = { ...baseHolder.state, amount, ownerIdentifier, ownerScheme };
85
+ return add(
86
+ buildKcc20ScriptForState(native, {
87
+ ...state,
88
+ owner: hex(ownerIdentifier),
89
+ borrowScheme: 0,
90
+ borrowGuard: hex(zero),
91
+ extensionCommitment: hex(String(baseHolder.state!.extensionCommitment)),
92
+ }),
93
+ state,
94
+ tokenId,
95
+ );
96
+ }
97
+ const holders = [holder("50000"), holder("30000")];
98
+ const reserve = holder("50000", marketId, 4);
99
+ const wrapperState = {
100
+ canonicalTokenId: tokenId,
101
+ enabled: true,
102
+ priceScale: "100",
103
+ };
104
+ const wrapperUtxo = add(
105
+ buildKcc20WrapperScriptForState(wrapper.script, {
106
+ ...wrapperState,
107
+ canonicalTokenId: hex(tokenId),
108
+ }),
109
+ wrapperState,
110
+ marketId,
111
+ );
112
+ const wrapperInfo = {
113
+ wrapperId: marketId,
114
+ marketId,
115
+ canonicalTokenId: tokenId,
116
+ contractCanonicalTokenId: tokenId,
117
+ activeCovenantId: marketId,
118
+ wrapperAddress: wrapperUtxo.address,
119
+ enabled: true,
120
+ };
121
+ function orderbook(
122
+ mode: number,
123
+ amount: string,
124
+ unitPriceSompi = "100000000",
125
+ ) {
126
+ const state = {
127
+ canonicalTokenId: tokenId,
128
+ ownerIdentifier: owner,
129
+ ownerScheme: 0,
130
+ identifierType: 0,
131
+ amount,
132
+ mode,
133
+ unitPriceSompi,
134
+ feeTicketId: zero,
135
+ priceScale: "100",
136
+ };
137
+ return add(
138
+ buildKcc20OrderbookScriptForState(wrapped.script, {
139
+ ...state,
140
+ canonicalTokenId: hex(tokenId),
141
+ ownerIdentifier: hex(owner),
142
+ feeTicketId: hex(zero),
143
+ amount: BigInt(amount),
144
+ unitPriceSompi: BigInt(unitPriceSompi),
145
+ }),
146
+ state,
147
+ marketId,
148
+ mode === 3 ? "3000000000" : "100000000",
149
+ );
150
+ }
151
+ const root = orderbook(1, "0", "0");
152
+ const wrappedHolders = [
153
+ orderbook(0, "50000", "0"),
154
+ orderbook(0, "30000", "0"),
155
+ ];
156
+ const asks = [orderbook(2, "100"), orderbook(2, "100")];
157
+ const bids = [orderbook(3, "100"), orderbook(3, "100")];
158
+ const context = {
159
+ canonicalCovenantId: tokenId,
160
+ tokenDecimals: 2,
161
+ wrapper: wrapperInfo,
162
+ wrappedTokenUtxos: [root, ...wrappedHolders, ...asks, ...bids],
163
+ };
164
+ const rootInfo = {
165
+ configured: true,
166
+ enabled: true,
167
+ rootId: feeId,
168
+ rootOwner: owner,
169
+ utilityTokenId: tokenId,
170
+ utilityTokenAmount: "100",
171
+ discountBps: 500,
172
+ supportedMarketIds: [marketId],
173
+ batchCreateSupported: true,
174
+ maxTicketBatchQuantity: 2,
175
+ };
176
+ function ticket(mode: number) {
177
+ const state = {
178
+ ownerIdentifier: owner,
179
+ ownerScheme: 0,
180
+ mode,
181
+ utilityTokenId: tokenId,
182
+ denomination: "100",
183
+ };
184
+ return add(
185
+ buildKcc20FeeTicketScriptForState(fee.script, {
186
+ ...state,
187
+ ownerIdentifier: hex(owner),
188
+ utilityTokenId: hex(tokenId),
189
+ }),
190
+ state,
191
+ feeId,
192
+ );
193
+ }
194
+ const rootUtxos = [ticket(1), ticket(2)];
195
+ const publicDeploy = await build(
196
+ recipes.deploy(
197
+ data.wallet,
198
+ {
199
+ ticker: "MINT",
200
+ tokenName: "Public mint fixture",
201
+ decimals: 2,
202
+ maxSupply: "1000",
203
+ premintSupply: "0",
204
+ mintPolicy: "public",
205
+ mintLaneCount: 1,
206
+ mintPricePerTokenSompi: "0",
207
+ },
208
+ options,
209
+ ),
210
+ );
211
+ const extension = {
212
+ ...publicDeploy.metadata.extension,
213
+ publicMintActive: true,
214
+ };
215
+ const binaryExtension = { ...extension };
216
+ for (const key of [
217
+ "creator",
218
+ "ticker",
219
+ "name",
220
+ "treasury",
221
+ "protocolFeeRecipient",
222
+ "holderExtensionCommitment",
223
+ ])
224
+ binaryExtension[key] = hex(extension[key]);
225
+ const commitment = mintExtensionCommitment(binaryExtension);
226
+ const minterState = {
227
+ ownerIdentifier: owner,
228
+ ownerScheme: 0,
229
+ amount: "0",
230
+ isMintAuthority: true,
231
+ mintPolicy: 2,
232
+ remainingSupply: extension.remainingSupply,
233
+ publicMintActive: true,
234
+ extension,
235
+ extensionCommitment: Array.from(commitment as Uint8Array, (x) =>
236
+ x.toString(16).padStart(2, "0"),
237
+ ).join(""),
238
+ };
239
+ const minter = add(
240
+ buildKcc20ScriptForState(native, {
241
+ amount: 0n,
242
+ owner: hex(owner),
243
+ ownerScheme: 0,
244
+ borrowScheme: 0,
245
+ borrowGuard: hex(zero),
246
+ extensionCommitment: commitment,
247
+ }),
248
+ minterState,
249
+ publicDeploy.metadata.covenantId,
250
+ );
251
+ const wallet = data.wallet;
252
+ const publicWallet = {
253
+ kcc20Owner: recipient,
254
+ walletAddress: new wasm.XOnlyPublicKey(recipient)
255
+ .toAddress(network)
256
+ .toString(),
257
+ };
258
+ data.rows.set(publicWallet.walletAddress, [
259
+ {
260
+ ...data.rows.get(wallet.walletAddress)![0],
261
+ address: publicWallet.walletAddress,
262
+ outpoint: { transactionId: "ef".repeat(32), index: 0 },
263
+ scriptPublicKey: wasm.payToAddressScript(publicWallet.walletAddress),
264
+ },
265
+ ]);
266
+ // Real argument tuples: callable directly in either frontend or backend code.
267
+ function sweepQuote(side: "buy" | "sell") {
268
+ const quote = buildStandaloneFillFeeQuote({
269
+ side,
270
+ tokenAmount: "100",
271
+ unitPriceSompi: "100000000",
272
+ priceScale: "100",
273
+ feeTicketApplied: false,
274
+ });
275
+ return {
276
+ grossSompi: (2n * BigInt(quote.grossSompi)).toString(),
277
+ protocolFeeSompi: (2n * BigInt(quote.protocolFeeSompi)).toString(),
278
+ buyerPaysSompi: (2n * BigInt(quote.buyerPaysSompi!)).toString(),
279
+ sellerReceivesSompi: (2n * BigInt(quote.sellerReceivesSompi!)).toString(),
280
+ };
281
+ }
282
+ const args = {
283
+ deploy: [
284
+ wallet,
285
+ {
286
+ ticker: "DEMO",
287
+ tokenName: "Example token",
288
+ decimals: 2,
289
+ maxSupply: "1000",
290
+ premintSupply: "1000",
291
+ mintPolicy: "fixed",
292
+ mintPricePerTokenSompi: "0",
293
+ },
294
+ options,
295
+ ],
296
+ transfer: [
297
+ wallet,
298
+ {
299
+ token: { covenantId: tokenId, decimals: 2 },
300
+ activeUtxos: holders,
301
+ recipientOwner: recipient,
302
+ tokenAmount: "1.00",
303
+ },
304
+ options,
305
+ ],
306
+ mint: [
307
+ wallet,
308
+ {
309
+ token: {
310
+ covenantId: minter.covenantId!,
311
+ decimals: 2,
312
+ mintPolicy: {
313
+ mintable: true,
314
+ policy: "public",
315
+ remainingSupply: extension.remainingSupply,
316
+ },
317
+ },
318
+ activeUtxos: [minter],
319
+ tokenAmount: "1.00",
320
+ },
321
+ options,
322
+ ],
323
+ publicMint: [
324
+ publicWallet,
325
+ {
326
+ token: {
327
+ covenantId: minter.covenantId!,
328
+ decimals: 2,
329
+ mintPolicy: {
330
+ mintable: true,
331
+ policy: "public",
332
+ remainingSupply: extension.remainingSupply,
333
+ publicMintActive: true,
334
+ },
335
+ },
336
+ activeUtxos: [minter],
337
+ tokenAmount: "1.00",
338
+ },
339
+ options,
340
+ ],
341
+ buyOrder: [
342
+ wallet,
343
+ context,
344
+ {
345
+ wrappedMarketId: marketId,
346
+ side: "buy",
347
+ tokenAmount: "1.00",
348
+ unitPriceSompi: "100000000",
349
+ },
350
+ options,
351
+ ],
352
+ fillBid: [
353
+ wallet,
354
+ context,
355
+ {
356
+ wrappedMarketId: marketId,
357
+ side: "sell",
358
+ targetOrderId: bids[0].outpoint!,
359
+ tokenAmount: "1.00",
360
+ unitPriceSompi: "100000000",
361
+ },
362
+ options,
363
+ ],
364
+ sweepBids: [
365
+ wallet,
366
+ context,
367
+ {
368
+ wrappedMarketId: marketId,
369
+ side: "sell",
370
+ tokenAmount: "2.00",
371
+ mode: "market",
372
+ expectedFills: bids.map((x) => ({
373
+ orderId: x.outpoint!,
374
+ tokenAmount: "100",
375
+ unitPriceSompi: "100000000",
376
+ })),
377
+ ...sweepQuote("sell"),
378
+ },
379
+ options,
380
+ ],
381
+ cancelBid: [
382
+ wallet,
383
+ context,
384
+ {
385
+ wrappedMarketId: marketId,
386
+ side: "bid",
387
+ targetOrderId: bids[0].outpoint!,
388
+ },
389
+ options,
390
+ ],
391
+ mintAvailability: [
392
+ wallet,
393
+ {
394
+ covenantId: minter.covenantId!,
395
+ active: false,
396
+ activeMinterUtxo: minter,
397
+ activeMinterUtxos: [minter],
398
+ },
399
+ options,
400
+ ],
401
+ consolidate: [
402
+ wallet,
403
+ {
404
+ token: { covenantId: tokenId, decimals: 2 },
405
+ activeUtxos: holders,
406
+ sourceOutpoints: holders.map((x) => x.outpoint!),
407
+ },
408
+ options,
409
+ ],
410
+ reveal: [
411
+ wallet,
412
+ { covenantId: tokenId, activeTokenUtxo: holders[0], decimals: 2 },
413
+ options,
414
+ ],
415
+ deployMarket: [
416
+ wallet,
417
+ {
418
+ canonicalCovenantId: tokenId,
419
+ tokenDecimals: 2,
420
+ wrappedRootAmount: "0",
421
+ },
422
+ options,
423
+ ],
424
+ wrap: [
425
+ wallet,
426
+ {
427
+ canonicalCovenantId: tokenId,
428
+ tokenDecimals: 2,
429
+ wrappedMarketId: marketId,
430
+ tokenAmount: "1.00",
431
+ wrapper: wrapperInfo,
432
+ canonicalTokenUtxos: holders,
433
+ wrapperUtxos: [wrapperUtxo],
434
+ },
435
+ options,
436
+ ],
437
+ unwrap: [
438
+ wallet,
439
+ {
440
+ canonicalCovenantId: tokenId,
441
+ tokenDecimals: 2,
442
+ wrappedMarketId: marketId,
443
+ tokenAmount: "1.00",
444
+ wrapper: wrapperInfo,
445
+ canonicalTokenUtxos: [reserve],
446
+ wrappedTokenUtxos: wrappedHolders,
447
+ wrapperUtxos: [wrapperUtxo],
448
+ },
449
+ options,
450
+ ],
451
+ order: [
452
+ wallet,
453
+ context,
454
+ {
455
+ wrappedMarketId: marketId,
456
+ side: "sell",
457
+ tokenAmount: "1.00",
458
+ unitPriceSompi: "100000000",
459
+ },
460
+ options,
461
+ ],
462
+ fill: [
463
+ wallet,
464
+ context,
465
+ {
466
+ wrappedMarketId: marketId,
467
+ side: "buy",
468
+ targetOrderId: asks[0].outpoint!,
469
+ tokenAmount: "1.00",
470
+ unitPriceSompi: "100000000",
471
+ },
472
+ options,
473
+ ],
474
+ sweep: [
475
+ wallet,
476
+ context,
477
+ {
478
+ wrappedMarketId: marketId,
479
+ side: "buy",
480
+ tokenAmount: "2.00",
481
+ mode: "market",
482
+ expectedFills: asks.map((x) => ({
483
+ orderId: x.outpoint!,
484
+ tokenAmount: "100",
485
+ unitPriceSompi: "100000000",
486
+ })),
487
+ ...sweepQuote("buy"),
488
+ },
489
+ options,
490
+ ],
491
+ cancel: [
492
+ wallet,
493
+ context,
494
+ {
495
+ wrappedMarketId: marketId,
496
+ side: "ask",
497
+ targetOrderId: asks[0].outpoint!,
498
+ },
499
+ options,
500
+ ],
501
+ consolidateWrapped: [
502
+ wallet,
503
+ context,
504
+ wrappedHolders.map((x) => x.outpoint!),
505
+ options,
506
+ ],
507
+ deployFeeTicketRoot: [
508
+ wallet,
509
+ { utilityTokenId: tokenId, utilityTokenAmount: "100", discountBps: 500 },
510
+ options,
511
+ ],
512
+ updateFeeTicketRoot: [
513
+ wallet,
514
+ { root: rootInfo, rootUtxos, utilityTokenAmount: "200" },
515
+ options,
516
+ ],
517
+ createFeeTicket: [
518
+ wallet,
519
+ { root: rootInfo, quantity: 1, rootUtxos, utilityTokenUtxos: holders },
520
+ options,
521
+ ],
522
+ burnFeeTicket: [
523
+ wallet,
524
+ { root: rootInfo, rootUtxos, ticketOutpoints: [rootUtxos[1].outpoint!] },
525
+ options,
526
+ ],
527
+ transferFeeTicket: [
528
+ wallet,
529
+ {
530
+ root: rootInfo,
531
+ rootUtxos,
532
+ ticketOutpoints: [rootUtxos[1].outpoint!],
533
+ recipientOwner: recipient,
534
+ },
535
+ options,
536
+ ],
537
+ } satisfies { [K in keyof typeof recipes]: Parameters<(typeof recipes)[K]> };
538
+ function operation(name: keyof typeof recipes): Operation {
539
+ const fn = recipes[name] as (...values: any[]) => Operation;
540
+ return fn(...args[name]);
541
+ }
542
+ return { ...data, args, operation, build, minter, context, bids, options };
543
+ }
@@ -0,0 +1,55 @@
1
+ import { buildKcc20DeployTokenOperation } from "@kaspacom/kcc20-tx-builder/deploy-operation";
2
+ import {
3
+ buildKcc20MintTokenOperation,
4
+ buildKcc20TransferTokenOperation,
5
+ buildKcc20NativeConsolidationOperation,
6
+ } from "@kaspacom/kcc20-tx-builder/token-operation";
7
+ import { buildKcc20MintAvailabilityOperation } from "@kaspacom/kcc20-tx-builder/mint-availability-operation";
8
+ import { buildKcc20VerifyTokenOperation } from "@kaspacom/kcc20-tx-builder/verify-operation";
9
+ import {
10
+ buildKcc20WrapperMarketDeployOperation,
11
+ buildKcc20WrapTokenOperationFromSnapshot,
12
+ buildKcc20UnwrapTokenOperationFromSnapshot,
13
+ } from "@kaspacom/kcc20-tx-builder/wrapper-operation";
14
+ import {
15
+ buildKcc20OrderOperation,
16
+ buildKcc20FillOperation,
17
+ buildKcc20SweepOperation,
18
+ buildKcc20CancelOperation,
19
+ buildKcc20WrappedConsolidationOperation,
20
+ } from "@kaspacom/kcc20-tx-builder/trading-operation";
21
+ import {
22
+ buildFeeTicketRootDeployOperation,
23
+ buildFeeTicketRootUpdateOperation,
24
+ buildFeeTicketCreateOperation,
25
+ buildFeeTicketBurnOperation,
26
+ buildFeeTicketTransferOperation,
27
+ } from "@kaspacom/kcc20-tx-builder/fee-ticket-operation";
28
+ // Each callable keeps the package's existing parameter types for editor help.
29
+ // Buy/sell, fill, sweep and cancellation select a builder from their draft.
30
+ export const recipes = {
31
+ deploy: buildKcc20DeployTokenOperation,
32
+ transfer: buildKcc20TransferTokenOperation,
33
+ mint: buildKcc20MintTokenOperation,
34
+ publicMint: buildKcc20MintTokenOperation,
35
+ mintAvailability: buildKcc20MintAvailabilityOperation,
36
+ consolidate: buildKcc20NativeConsolidationOperation,
37
+ reveal: buildKcc20VerifyTokenOperation,
38
+ deployMarket: buildKcc20WrapperMarketDeployOperation,
39
+ wrap: buildKcc20WrapTokenOperationFromSnapshot,
40
+ unwrap: buildKcc20UnwrapTokenOperationFromSnapshot,
41
+ order: buildKcc20OrderOperation,
42
+ buyOrder: buildKcc20OrderOperation,
43
+ fillBid: buildKcc20FillOperation,
44
+ sweepBids: buildKcc20SweepOperation,
45
+ cancelBid: buildKcc20CancelOperation,
46
+ fill: buildKcc20FillOperation,
47
+ sweep: buildKcc20SweepOperation,
48
+ cancel: buildKcc20CancelOperation,
49
+ consolidateWrapped: buildKcc20WrappedConsolidationOperation,
50
+ deployFeeTicketRoot: buildFeeTicketRootDeployOperation,
51
+ updateFeeTicketRoot: buildFeeTicketRootUpdateOperation,
52
+ createFeeTicket: buildFeeTicketCreateOperation,
53
+ burnFeeTicket: buildFeeTicketBurnOperation,
54
+ transferFeeTicket: buildFeeTicketTransferOperation,
55
+ };
@@ -0,0 +1,110 @@
1
+ import type { Built, Runtime } from "./build.ts";
2
+ function bytes(hex: string): Uint8Array {
3
+ if (!/^(?:[a-f0-9]{2})*$/i.test(hex))
4
+ throw new Error("Invalid hexadecimal script");
5
+ return Uint8Array.from(hex.match(/../g) ?? [], (x) => parseInt(x, 16));
6
+ }
7
+ // The wallet signs covenant inputs too; their signatures then need ABI arguments.
8
+ export function signingInputs(result: Built) {
9
+ const inputs = new Map(
10
+ result.signInputs.map((input) => [input.index, input]),
11
+ );
12
+ for (const script of result.scripts ?? []) {
13
+ if (!inputs.has(script.inputIndex)) {
14
+ inputs.set(script.inputIndex, {
15
+ index: script.inputIndex,
16
+ sighashType: script.signType ?? 1,
17
+ });
18
+ }
19
+ }
20
+ return [...inputs.values()].sort((a, b) => a.index - b.index);
21
+ }
22
+ export function attachScripts(
23
+ wasm: Runtime,
24
+ signedJson: string,
25
+ result: Built,
26
+ ): string {
27
+ const tx = wasm.Transaction.deserializeFromSafeJSON(signedJson);
28
+ const unsigned = wasm.Transaction.deserializeFromSafeJSON(
29
+ result.psktTransactionJson,
30
+ );
31
+ const requested = signingInputs(result);
32
+ for (const input of requested) {
33
+ if (
34
+ !Number.isInteger(input.index) ||
35
+ input.index < 0 ||
36
+ input.index >= unsigned.inputs.length
37
+ )
38
+ throw new Error("Invalid signing input index");
39
+ }
40
+ const signingIndexes = new Set(requested.map((input) => input.index));
41
+ function intent(transaction: InstanceType<Runtime["Transaction"]>) {
42
+ const data = JSON.parse(transaction.serializeToSafeJSON());
43
+ delete data.id;
44
+ delete data.mass; // Derived fields may be recalculated by a signer.
45
+ for (const index of signingIndexes)
46
+ delete data.inputs[index].signatureScript;
47
+ return JSON.stringify(data);
48
+ }
49
+ if (intent(tx) !== intent(unsigned))
50
+ throw new Error("Wallet changed the transaction intent");
51
+ for (const { index, sighashType } of requested) {
52
+ const encoded = tx.inputs[index]?.signatureScript;
53
+ if (!encoded)
54
+ throw new Error(`Wallet omitted signature for input ${index}`);
55
+ const raw = typeof encoded === "string" ? bytes(encoded) : encoded;
56
+ // A wallet returns a P2PK script: PUSH65, Schnorr signature, sighash byte.
57
+ if (raw.length !== 66 || raw[0] !== 65 || raw[65] !== sighashType) {
58
+ throw new Error(
59
+ `Invalid signature encoding or sighash for input ${index}`,
60
+ );
61
+ }
62
+ }
63
+ for (const hint of result.scripts ?? []) {
64
+ const input = tx.inputs[hint.inputIndex];
65
+ if (!input)
66
+ throw new Error("Covenant input missing from signed transaction");
67
+ if (!input.signatureScript) throw new Error("Missing wallet signature");
68
+ let signature =
69
+ typeof input.signatureScript === "string"
70
+ ? bytes(input.signatureScript)
71
+ : input.signatureScript;
72
+ if (signature.length === 66 && signature[0] === 65)
73
+ signature = signature.slice(1);
74
+ if (signature.length !== 65)
75
+ throw new Error("Expected Schnorr signature and sighash byte");
76
+ const template = hint.signatureScript;
77
+ let prefix: string | Uint8Array = input.signatureScript;
78
+ if (template && template.mode !== "wrap-signature") {
79
+ if (!["ordered-args", "signature-first-args"].includes(template.mode))
80
+ throw new Error("Unsupported script template");
81
+ const builder = new wasm.ScriptBuilder();
82
+ if (template.mode === "signature-first-args") builder.addData(signature);
83
+ let signatures = 0;
84
+ for (const arg of template.args ?? []) {
85
+ if (arg.type === "i64") builder.addI64(BigInt(arg.value));
86
+ else if (arg.type === "data") builder.addData(bytes(arg.hex));
87
+ else if (
88
+ arg.type === "byte" &&
89
+ Number.isInteger(arg.value) &&
90
+ arg.value >= 0 &&
91
+ arg.value <= 255
92
+ )
93
+ builder.addData(Uint8Array.of(arg.value));
94
+ else if (arg.type === "signature" && template.mode === "ordered-args") {
95
+ builder.addData(
96
+ Uint8Array.from([...bytes(arg.prefixHex ?? ""), ...signature]),
97
+ );
98
+ signatures++;
99
+ } else throw new Error("Unsupported script argument");
100
+ }
101
+ if (template.mode === "ordered-args" && signatures !== 1)
102
+ throw new Error("Expected one signature slot");
103
+ prefix = builder.drain();
104
+ }
105
+ input.signatureScript = wasm.ScriptBuilder.fromScript(
106
+ hint.scriptHex,
107
+ ).encodePayToScriptHashSignatureScript(prefix);
108
+ }
109
+ return tx.serializeToSafeJSON();
110
+ }
@@ -0,0 +1,8 @@
1
+ export async function withTimeout<T>(operation: Promise<T>, label: string, milliseconds = 20_000): Promise<T> {
2
+ let timer: ReturnType<typeof setTimeout>;
3
+ try {
4
+ return await Promise.race([operation, new Promise<never>((_, reject) => {
5
+ timer = setTimeout(() => reject(new Error(`${label} timed out`)), milliseconds);
6
+ })]);
7
+ } finally { clearTimeout(timer!); }
8
+ }