@kaspacom/kcc20-tx-builder 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +2 -2
  3. package/RELEASING.md +19 -14
  4. package/artifacts/ArgentKCC20.reference.json +12428 -0
  5. package/artifacts/ArgentKCC20Wrapper.placeholder.json +12455 -0
  6. package/artifacts/KCC20.placeholder.json +1447 -1973
  7. package/artifacts/KCC20FeeTicket.placeholder.json +816 -1447
  8. package/artifacts/KCC20Orderbook.placeholder.json +1 -2
  9. package/artifacts/KCC20SnapshotBootstrap.placeholder.json +101 -100
  10. package/artifacts/KCC20SnapshotController.placeholder.json +101 -100
  11. package/artifacts/KCC20Vesting.placeholder.json +427 -429
  12. package/artifacts/KCC20Wrapper.placeholder.json +733 -734
  13. package/artifacts/kcc20-snapshot-bootstrap/KCC20SnapshotBootstrap.placeholder.json +101 -100
  14. package/artifacts/kcc20-snapshot-controller/KCC20SnapshotController.placeholder.json +101 -100
  15. package/artifacts/snapshot-standards.lock.json +6 -3
  16. package/artifacts/token-profiles.json +109 -0
  17. package/artifacts/token-profiles.sha256 +1 -0
  18. package/dist/action-source-resolver.d.ts.map +1 -1
  19. package/dist/action-source-resolver.js +11 -2
  20. package/dist/action-source-resolver.js.map +1 -1
  21. package/dist/artifacts.d.ts +10 -10
  22. package/dist/artifacts.d.ts.map +1 -1
  23. package/dist/artifacts.js +10 -10
  24. package/dist/artifacts.js.map +1 -1
  25. package/dist/cjs/action-source-resolver.js +11 -2
  26. package/dist/cjs/action-source-resolver.js.map +1 -1
  27. package/dist/cjs/artifacts.d.ts +10 -10
  28. package/dist/cjs/artifacts.js +10 -10
  29. package/dist/cjs/artifacts.js.map +1 -1
  30. package/dist/cjs/engine.d.ts +10 -0
  31. package/dist/cjs/engine.js +129 -77
  32. package/dist/cjs/engine.js.map +1 -1
  33. package/dist/cjs/index.d.ts +5 -0
  34. package/dist/cjs/index.js +5 -0
  35. package/dist/cjs/index.js.map +1 -1
  36. package/dist/cjs/profile-transfer.d.ts +60 -0
  37. package/dist/cjs/profile-transfer.js +248 -0
  38. package/dist/cjs/profile-transfer.js.map +1 -0
  39. package/dist/cjs/snapshot-signerless-transactions.js +2 -5
  40. package/dist/cjs/snapshot-signerless-transactions.js.map +1 -1
  41. package/dist/cjs/snapshot-signerless.js +5 -3
  42. package/dist/cjs/snapshot-signerless.js.map +1 -1
  43. package/dist/cjs/token-authorization.d.ts +42 -0
  44. package/dist/cjs/token-authorization.js +93 -0
  45. package/dist/cjs/token-authorization.js.map +1 -0
  46. package/dist/cjs/token-profiles.d.ts +82 -0
  47. package/dist/cjs/token-profiles.generated.d.ts +3 -0
  48. package/dist/cjs/token-profiles.generated.js +114 -0
  49. package/dist/cjs/token-profiles.generated.js.map +1 -0
  50. package/dist/cjs/token-profiles.js +257 -0
  51. package/dist/cjs/token-profiles.js.map +1 -0
  52. package/dist/cjs/transfer-payload.d.ts +9 -0
  53. package/dist/cjs/transfer-payload.js +37 -0
  54. package/dist/cjs/transfer-payload.js.map +1 -0
  55. package/dist/cjs/wrapper-operation.d.ts +1 -0
  56. package/dist/cjs/wrapper-operation.js +1 -0
  57. package/dist/cjs/wrapper-operation.js.map +1 -1
  58. package/dist/engine.d.ts +10 -0
  59. package/dist/engine.d.ts.map +1 -1
  60. package/dist/engine.js +130 -78
  61. package/dist/engine.js.map +1 -1
  62. package/dist/index.d.ts +5 -0
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +5 -0
  65. package/dist/index.js.map +1 -1
  66. package/dist/profile-transfer.d.ts +61 -0
  67. package/dist/profile-transfer.d.ts.map +1 -0
  68. package/dist/profile-transfer.js +245 -0
  69. package/dist/profile-transfer.js.map +1 -0
  70. package/dist/snapshot-signerless-transactions.d.ts.map +1 -1
  71. package/dist/snapshot-signerless-transactions.js +2 -5
  72. package/dist/snapshot-signerless-transactions.js.map +1 -1
  73. package/dist/snapshot-signerless.d.ts.map +1 -1
  74. package/dist/snapshot-signerless.js +5 -3
  75. package/dist/snapshot-signerless.js.map +1 -1
  76. package/dist/token-authorization.d.ts +43 -0
  77. package/dist/token-authorization.d.ts.map +1 -0
  78. package/dist/token-authorization.js +88 -0
  79. package/dist/token-authorization.js.map +1 -0
  80. package/dist/token-profiles.d.ts +83 -0
  81. package/dist/token-profiles.d.ts.map +1 -0
  82. package/dist/token-profiles.generated.d.ts +4 -0
  83. package/dist/token-profiles.generated.d.ts.map +1 -0
  84. package/dist/token-profiles.generated.js +111 -0
  85. package/dist/token-profiles.generated.js.map +1 -0
  86. package/dist/token-profiles.js +245 -0
  87. package/dist/token-profiles.js.map +1 -0
  88. package/dist/transfer-payload.d.ts +10 -0
  89. package/dist/transfer-payload.d.ts.map +1 -0
  90. package/dist/transfer-payload.js +34 -0
  91. package/dist/transfer-payload.js.map +1 -0
  92. package/dist/wrapper-operation.d.ts +1 -0
  93. package/dist/wrapper-operation.d.ts.map +1 -1
  94. package/dist/wrapper-operation.js +1 -0
  95. package/dist/wrapper-operation.js.map +1 -1
  96. package/docs/COMPATIBILITY.json +24 -33
  97. package/docs/LAST_CALL.md +50 -0
  98. package/examples/README.md +3 -0
  99. package/examples/backend/cli.ts +2 -1
  100. package/examples/frontend/main.ts +2 -1
  101. package/examples/shared/build.ts +3 -0
  102. package/examples/shared/offline-profile.ts +27 -0
  103. package/examples/shared/recipe-fixtures.ts +3 -2
  104. package/examples/test/browser.ts +2 -2
  105. package/examples/test/build.test.ts +21 -8
  106. package/examples/test/sources.test.ts +3 -2
  107. package/package.json +2 -2
  108. package/src/action-source-resolver.ts +22 -4
  109. package/src/artifacts.ts +12 -10
  110. package/src/engine.ts +215 -105
  111. package/src/index.ts +7 -0
  112. package/src/profile-transfer.ts +425 -0
  113. package/src/snapshot-signerless-transactions.ts +7 -16
  114. package/src/snapshot-signerless.ts +5 -3
  115. package/src/token-authorization.ts +135 -0
  116. package/src/token-profiles.generated.ts +112 -0
  117. package/src/token-profiles.ts +456 -0
  118. package/src/transfer-payload.ts +45 -0
  119. package/src/wrapper-operation.ts +2 -0
package/src/engine.ts CHANGED
@@ -1,5 +1,19 @@
1
1
  // @ts-nocheck
2
+ import { planTokenOwnerWitness } from "./token-authorization.js";
2
3
  import { sha256 } from "@noble/hashes/sha256";
4
+ import {
5
+ authenticateTokenArtifact,
6
+ authenticateTokenWrapper,
7
+ validateTokenProfileRegistry,
8
+ requireTokenCapability,
9
+ resolveTokenProfilePriceScale,
10
+ } from "./token-profiles.js";
11
+ import { buildAuthenticatedKcc20TransferPayload } from "./transfer-payload.js";
12
+ import {
13
+ BUNDLED_TOKEN_PROFILES,
14
+ BUNDLED_TOKEN_PROFILE_DIGEST,
15
+ } from "./token-profiles.generated.js";
16
+ import type { TokenProfileRegistry } from "./token-profiles.js";
3
17
  import type { KaspaWasmRuntime } from "./runtime.js";
4
18
  import {
5
19
  DEFAULT_COMPUTE_BUDGET,
@@ -55,6 +69,7 @@ import {
55
69
  buildKcc20SetPublicMintActiveSigScript,
56
70
  buildKcc20TransferSigScript,
57
71
  dispatchTagFor,
72
+ templateHash,
58
73
  encodeKcc20StateRecordArrays,
59
74
  holderExtensionCommitment,
60
75
  mintExtensionCommitment,
@@ -81,6 +96,9 @@ export interface Kcc20PsktBuilderEngineOptions {
81
96
  };
82
97
  rpcClientFactory?: (config: unknown) => any;
83
98
  allowBackendOnly?: boolean;
99
+ walletSigningCapabilities?: { schnorr: boolean; ecdsa: boolean };
100
+ /** Trusted host registry; never populate this from transaction request data. */
101
+ tokenProfileRegistry?: { manifest: TokenProfileRegistry; sha256: string };
84
102
  }
85
103
 
86
104
  export interface FeeTicketBatchCreateCapability {
@@ -210,6 +228,10 @@ export function createKcc20PsktBuilderEngine(
210
228
  const WRAPPED_ARTIFACT_PATH = "KCC20Orderbook.placeholder.json";
211
229
  const FEE_TICKET_ARTIFACT_PATH = "KCC20FeeTicket.placeholder.json";
212
230
  const staticArtifactCache = new Map();
231
+ const tokenProfiles = validateTokenProfileRegistry(
232
+ options.tokenProfileRegistry?.manifest ?? BUNDLED_TOKEN_PROFILES,
233
+ options.tokenProfileRegistry?.sha256 ?? BUNDLED_TOKEN_PROFILE_DIGEST,
234
+ );
213
235
 
214
236
  function readStaticArtifact(path) {
215
237
  let cached = staticArtifactCache.get(path);
@@ -224,7 +246,18 @@ export function createKcc20PsktBuilderEngine(
224
246
  ? (artifact.artifact ?? artifact)
225
247
  : artifact;
226
248
  const normalized = normalizeSilverScriptArtifact(artifactValue);
227
- assertKcc20ArtifactScriptHash(path, normalized);
249
+ const profile = tokenProfiles.profiles.find(
250
+ (p) => p.artifactKey === path,
251
+ );
252
+ if (profile) authenticateTokenArtifact(profile, artifactValue);
253
+ else {
254
+ const ownerProfile = tokenProfiles.profiles.find(
255
+ (p) => p.wrapper?.artifactKey === path,
256
+ );
257
+ if (ownerProfile)
258
+ authenticateTokenWrapper(ownerProfile, artifactValue);
259
+ else assertKcc20ArtifactScriptHash(path, normalized);
260
+ }
228
261
  return normalized;
229
262
  })
230
263
  .catch((error) => {
@@ -244,18 +277,28 @@ export function createKcc20PsktBuilderEngine(
244
277
  }
245
278
 
246
279
  async function resolveKcc20ArtifactForState(state, address, network) {
247
- const artifact = await readStaticArtifact(KCC20_ARTIFACT_PATH);
248
- const script = buildKcc20ScriptForState(artifact, state);
249
- const candidateAddress = kaspaWasm
250
- .addressFromScriptPublicKey(
251
- kaspaWasm.payToScriptHashScript(script),
252
- network,
280
+ for (const profile of tokenProfiles.profiles) {
281
+ const artifact = await readStaticArtifact(profile.artifactKey);
282
+ const script = buildKcc20ScriptForState(artifact, state);
283
+ const candidateAddress = kaspaWasm
284
+ .addressFromScriptPublicKey(
285
+ kaspaWasm.payToScriptHashScript(script),
286
+ network,
287
+ )
288
+ .toString();
289
+ if (candidateAddress !== address) continue;
290
+ requireTokenCapability(profile, "transfer");
291
+ if (
292
+ !profile.ownerSchemes.includes(
293
+ Number(state.ownerScheme ?? state.identifierType),
294
+ )
253
295
  )
254
- .toString();
255
- if (candidateAddress !== address) {
256
- throw new Error("KCC20 UTXO address does not match the current artifact");
296
+ throw new Error("owner scheme is unsupported by this token profile");
297
+ return { version: profile.id, profile, artifact, script };
257
298
  }
258
- return { version: "standard-native-v1", artifact, script };
299
+ throw new Error(
300
+ "KCC20 UTXO address does not match a bundled token profile",
301
+ );
259
302
  }
260
303
 
261
304
  function artifactEntrypointDispatchTag(artifact, entrypoint) {
@@ -306,13 +349,13 @@ export function createKcc20PsktBuilderEngine(
306
349
  ];
307
350
  }
308
351
 
309
- async function readWrapperArtifacts() {
310
- return [
311
- {
312
- artifact: await readStaticArtifact(WRAPPER_ARTIFACT_PATH),
313
- version: "standard-native-v1",
314
- },
315
- ];
352
+ async function readWrapperArtifacts(profile) {
353
+ if (!profile.wrapper) return [];
354
+ return [{
355
+ artifact: await readStaticArtifact(profile.wrapper.artifactKey),
356
+ version: "standard-native-v1",
357
+ tokenProfileId: profile.id,
358
+ }];
316
359
  }
317
360
 
318
361
  function wrappedArtifactSupportsEntrypoint(info, name) {
@@ -545,7 +588,6 @@ export function createKcc20PsktBuilderEngine(
545
588
  }
546
589
 
547
590
  const builderKey = input.request?.builderKey;
548
-
549
591
  if (
550
592
  builderKey === "kcc20orderbook.matcher-settle-crossed" &&
551
593
  !options.allowBackendOnly
@@ -872,10 +914,7 @@ export function createKcc20PsktBuilderEngine(
872
914
  payToScriptHashScript,
873
915
  } = kw;
874
916
 
875
- const utilityScript = buildNativeKcc20ScriptForState(
876
- kcc20Artifact.script,
877
- utilityState,
878
- );
917
+ const utilityScript = buildKcc20ScriptForState(kcc20Artifact, utilityState);
879
918
  const rootAddress = requireKaspaAddress(rootUtxo.address);
880
919
  const utilityAddress = requireKaspaAddress(utilityUtxo.address);
881
920
  const rootArtifactInfo = selectFeeTicketArtifactForState(
@@ -2606,13 +2645,7 @@ export function createKcc20PsktBuilderEngine(
2606
2645
  params.wrappedRootAmount ?? "0",
2607
2646
  "wrappedRootAmount",
2608
2647
  );
2609
- const priceScale = parsePositiveU64(
2610
- params.priceScale ??
2611
- params.tokenDisplayScale ??
2612
- DEFAULT_KCC20_PRICE_SCALE,
2613
- "priceScale",
2614
- );
2615
- assertWrapperArtifactPriceScale(priceScale);
2648
+ const requestedPriceScale = params.priceScale ?? params.tokenDisplayScale;
2616
2649
  const network = request.network || env.KASPA_NETWORK || "testnet-10";
2617
2650
  const walletAddress = requireKaspaAddress(request.owner?.walletAddress);
2618
2651
  const rpcUrl = env.KASPA_WRPC_URL || DEFAULT_WRPC_URL;
@@ -2630,7 +2663,31 @@ export function createKcc20PsktBuilderEngine(
2630
2663
  "computeBudget",
2631
2664
  );
2632
2665
 
2633
- const wrapperArtifact = await readStaticArtifact(WRAPPER_ARTIFACT_PATH);
2666
+ const evidence = params.tokenUtxo;
2667
+ if (!evidence)
2668
+ throw new Error(
2669
+ "authenticated token output is required before wrapper deployment",
2670
+ );
2671
+ const sourceState = kcc20StateFromUtxo(evidence, request.owner);
2672
+ const sourceProfile = await resolveKcc20ArtifactForState(
2673
+ sourceState,
2674
+ requireKaspaAddress(evidence.address),
2675
+ network,
2676
+ );
2677
+ requireTokenCapability(sourceProfile.profile, "wrap");
2678
+ if (!sourceProfile.profile.wrapper)
2679
+ throw new Error("token profile has no wrapper adapter");
2680
+ const priceScale = resolveTokenProfilePriceScale(
2681
+ sourceProfile.profile,
2682
+ requestedPriceScale === undefined
2683
+ ? undefined
2684
+ : parsePositiveU64(requestedPriceScale, "priceScale"),
2685
+ DEFAULT_KCC20_PRICE_SCALE,
2686
+ );
2687
+ assertWrapperArtifactPriceScale(priceScale);
2688
+ const wrapperArtifact = await readStaticArtifact(
2689
+ sourceProfile.profile.wrapper.artifactKey,
2690
+ );
2634
2691
  const wrappedArtifacts = await readWrappedArtifacts();
2635
2692
  const wrappedArtifact = wrappedArtifacts[0].artifact;
2636
2693
  const wrapperState = {
@@ -2688,6 +2745,29 @@ export function createKcc20PsktBuilderEngine(
2688
2745
  });
2689
2746
  await rpc.connect();
2690
2747
  try {
2748
+ const evidenceUtxos = await getUtxosByAddresses(rpc, [evidence.address]);
2749
+ const sourceEntry = findUtxoEntry(
2750
+ evidenceUtxos.entries,
2751
+ requireHex32(evidence.txidHex, "token evidence txid"),
2752
+ parseVout(evidence.vout, "token evidence vout"),
2753
+ );
2754
+ if (!sourceEntry)
2755
+ throw new Error("token evidence is spent or unavailable");
2756
+ requireMatchingCovenantId(
2757
+ sourceEntry,
2758
+ contractCanonicalTokenId,
2759
+ "wrapper token evidence",
2760
+ );
2761
+ const sourceSpk =
2762
+ sourceEntry.scriptPublicKey ??
2763
+ sourceEntry.entry?.scriptPublicKey ??
2764
+ sourceEntry.utxoEntry?.scriptPublicKey;
2765
+ if (
2766
+ !sourceSpk ||
2767
+ kw.addressFromScriptPublicKey(sourceSpk, network)?.toString() !==
2768
+ evidence.address
2769
+ )
2770
+ throw new Error("token evidence script does not match RPC");
2691
2771
  const utxos = await getUtxosByAddresses(rpc, [walletAddress]);
2692
2772
  const fundingEntry = selectFundingEntry(
2693
2773
  utxos.entries,
@@ -2812,6 +2892,10 @@ export function createKcc20PsktBuilderEngine(
2812
2892
  submitTransactionSupported: true,
2813
2893
  metadata: {
2814
2894
  builderKey: "kcc20wrapper.deploy-market",
2895
+ tokenProfileId: sourceProfile.profile.id,
2896
+ tokenProfileRegistryHash:
2897
+ options.tokenProfileRegistry?.sha256 ??
2898
+ BUNDLED_TOKEN_PROFILE_DIGEST,
2815
2899
  contract: "KCC20Wrapper",
2816
2900
  network,
2817
2901
  walletAddress,
@@ -2942,10 +3026,7 @@ export function createKcc20PsktBuilderEngine(
2942
3026
  "computeBudget",
2943
3027
  );
2944
3028
 
2945
- const [wrapperArtifacts, wrappedArtifacts] = await Promise.all([
2946
- readWrapperArtifacts(),
2947
- readWrappedArtifacts(),
2948
- ]);
3029
+ const wrappedArtifacts = await readWrappedArtifacts();
2949
3030
  const holderState = kcc20StateFromUtxo(holderUtxo, request.owner);
2950
3031
  if (holderState.identifierType !== 0) {
2951
3032
  throw new Error("wrap requires pubkey-owned KCC20 holder UTXO");
@@ -2966,6 +3047,8 @@ export function createKcc20PsktBuilderEngine(
2966
3047
  network,
2967
3048
  );
2968
3049
  const kcc20Artifact = kcc20ArtifactInfo.artifact;
3050
+ requireTokenCapability(kcc20ArtifactInfo.profile, "wrap");
3051
+ const wrapperArtifacts = await readWrapperArtifacts(kcc20ArtifactInfo.profile);
2969
3052
  const remaining = holderState.amount - tokenAmount;
2970
3053
  const priceScale = parsePositiveU64(
2971
3054
  wrapperUtxo.state?.priceScale ??
@@ -3007,7 +3090,9 @@ export function createKcc20PsktBuilderEngine(
3007
3090
 
3008
3091
  const wrapperArtifactInfo = selectWrapperArtifactForUtxo({
3009
3092
  kw,
3010
- artifacts: wrapperArtifacts,
3093
+ artifacts: wrapperArtifacts.filter(
3094
+ (info) => info.tokenProfileId === kcc20ArtifactInfo.profile.id,
3095
+ ),
3011
3096
  state: wrapperState,
3012
3097
  utxo: wrapperUtxo,
3013
3098
  network,
@@ -3024,10 +3109,7 @@ export function createKcc20PsktBuilderEngine(
3024
3109
  const wrapperArtifact = wrapperArtifactInfo.artifact;
3025
3110
  const wrappedArtifact = wrappedArtifactInfo.artifact;
3026
3111
  const wrapperScript = wrapperArtifactInfo.script;
3027
- const holderScript = buildNativeKcc20ScriptForState(
3028
- kcc20Artifact.script,
3029
- holderState,
3030
- );
3112
+ const holderScript = buildKcc20ScriptForState(kcc20Artifact, holderState);
3031
3113
  const wrapperAddress = requireKaspaAddress(wrapperUtxo.address);
3032
3114
  const holderAddress = requireKaspaAddress(holderUtxo.address);
3033
3115
  assertScriptAddress(
@@ -3072,7 +3154,7 @@ export function createKcc20PsktBuilderEngine(
3072
3154
  (entries) =>
3073
3155
  Boolean(
3074
3156
  findUtxoEntry(entries, wrapperTxid, wrapperVout) &&
3075
- findUtxoEntry(entries, holderTxid, holderVout),
3157
+ findUtxoEntry(entries, holderTxid, holderVout),
3076
3158
  ),
3077
3159
  covenantUtxos,
3078
3160
  );
@@ -3358,10 +3440,7 @@ export function createKcc20PsktBuilderEngine(
3358
3440
  "computeBudget",
3359
3441
  );
3360
3442
 
3361
- const [wrapperArtifacts, wrappedArtifacts] = await Promise.all([
3362
- readWrapperArtifacts(),
3363
- readWrappedArtifacts(),
3364
- ]);
3443
+ const wrappedArtifacts = await readWrappedArtifacts();
3365
3444
  const reserveState = kcc20StateFromUtxo(reserveUtxo, request.owner);
3366
3445
  if (reserveState.ownerScheme !== 4 || reserveState.isMintAuthority) {
3367
3446
  throw new Error("KCC20 reserve UTXO must be covenant-owned holder state");
@@ -3375,6 +3454,8 @@ export function createKcc20PsktBuilderEngine(
3375
3454
  network,
3376
3455
  );
3377
3456
  const kcc20Artifact = kcc20ArtifactInfo.artifact;
3457
+ requireTokenCapability(kcc20ArtifactInfo.profile, "unwrap");
3458
+ const wrapperArtifacts = await readWrapperArtifacts(kcc20ArtifactInfo.profile);
3378
3459
  const wrappedState = kcc20V3WrappedStateFromUtxo(
3379
3460
  wrappedUtxo,
3380
3461
  canonicalTokenId,
@@ -3442,7 +3523,9 @@ export function createKcc20PsktBuilderEngine(
3442
3523
 
3443
3524
  const wrapperArtifactInfo = selectWrapperArtifactForUtxo({
3444
3525
  kw,
3445
- artifacts: wrapperArtifacts,
3526
+ artifacts: wrapperArtifacts.filter(
3527
+ (info) => info.tokenProfileId === kcc20ArtifactInfo.profile.id,
3528
+ ),
3446
3529
  state: wrapperState,
3447
3530
  utxo: wrapperUtxo,
3448
3531
  network,
@@ -3467,10 +3550,7 @@ export function createKcc20PsktBuilderEngine(
3467
3550
  const wrappedScript = wrappedArtifactInfo.script;
3468
3551
  const refundAware =
3469
3552
  wrappedArtifactRefundsHolderDeposits(wrappedArtifactInfo);
3470
- const reserveScript = buildNativeKcc20ScriptForState(
3471
- kcc20Artifact.script,
3472
- reserveState,
3473
- );
3553
+ const reserveScript = buildKcc20ScriptForState(kcc20Artifact, reserveState);
3474
3554
  const wrapperAddress = wrapperArtifactInfo.address;
3475
3555
  const wrappedAddress = wrappedArtifactInfo.address;
3476
3556
  const reserveAddress = requireKaspaAddress(reserveUtxo.address);
@@ -8621,6 +8701,7 @@ export function createKcc20PsktBuilderEngine(
8621
8701
  minterAddress,
8622
8702
  network,
8623
8703
  );
8704
+ requireTokenCapability(resolvedArtifact.profile, "mint");
8624
8705
  const artifact = resolvedArtifact.artifact;
8625
8706
  const recipientState = {
8626
8707
  amount: mintAmount,
@@ -8967,6 +9048,13 @@ export function createKcc20PsktBuilderEngine(
8967
9048
  if (requestedMinterUtxos.length === 0) {
8968
9049
  throw new Error("active KCC20 mint-authority UTXO is required");
8969
9050
  }
9051
+ // The caller submits larger updates as sequential batches. Never truncate
9052
+ // this array: every supplied authority must be represented in the result.
9053
+ if (requestedMinterUtxos.length > 5) {
9054
+ throw new Error(
9055
+ "Public mint availability updates support at most 5 authorities per transaction",
9056
+ );
9057
+ }
8970
9058
  if (typeof params.active !== "boolean") {
8971
9059
  throw new Error("active must be a boolean");
8972
9060
  }
@@ -9056,6 +9144,7 @@ export function createKcc20PsktBuilderEngine(
9056
9144
  minterAddress,
9057
9145
  network,
9058
9146
  );
9147
+ requireTokenCapability(resolvedArtifact.profile, "mint");
9059
9148
  dispatchTagFor(resolvedArtifact.artifact, "set_public_mint_active");
9060
9149
  const minterTxid = requireHex32(
9061
9150
  activeMinterUtxo.txidHex,
@@ -9386,6 +9475,10 @@ export function createKcc20PsktBuilderEngine(
9386
9475
  ) {
9387
9476
  throw new Error("consolidation requires holder UTXOs from one artifact");
9388
9477
  }
9478
+ const profile = resolvedArtifacts[0].profile;
9479
+ if (holderUtxos.length > profile.maxTokenInputs) {
9480
+ throw new Error("consolidation exceeds the token profile input bound");
9481
+ }
9389
9482
  const artifact = resolvedArtifacts[0].artifact;
9390
9483
  for (const peerState of states.slice(1)) {
9391
9484
  assertKcc20MergeableStates(primaryState, peerState, owner);
@@ -9835,14 +9928,6 @@ export function createKcc20PsktBuilderEngine(
9835
9928
  if (inputState.isMintAuthority || inputState.amount <= 0n) {
9836
9929
  throw new Error("KCC20 transfer requires a holder UTXO");
9837
9930
  }
9838
- if (inputState.ownerScheme !== 0) {
9839
- throw new Error("KCC20 transfer requires a Schnorr P2PK holder UTXO");
9840
- }
9841
- if (bytesToHex(inputState.ownerIdentifier) !== owner) {
9842
- throw new Error(
9843
- "KCC20 transfer requires wallet to own the active holder UTXO",
9844
- );
9845
- }
9846
9931
  if (transferAmount > inputState.amount) {
9847
9932
  throw new Error("tokenAmount exceeds active holder balance");
9848
9933
  }
@@ -9856,6 +9941,25 @@ export function createKcc20PsktBuilderEngine(
9856
9941
  network,
9857
9942
  );
9858
9943
  const artifact = resolvedArtifact.artifact;
9944
+ if (params.mode && params.mode !== "owner")
9945
+ throw new Error(
9946
+ "borrowed receive requires the explicit borrow transaction builder",
9947
+ );
9948
+ const authorization = planTokenOwnerWitness({
9949
+ profile: resolvedArtifact.profile,
9950
+ owner: bytesToHex(inputState.ownerIdentifier),
9951
+ ownerScheme: inputState.ownerScheme,
9952
+ publicKey: request.owner?.publicKey ?? request.owner?.kcc20Owner,
9953
+ capabilities: options.walletSigningCapabilities ?? {
9954
+ schnorr: true,
9955
+ ecdsa: false,
9956
+ },
9957
+ role: "leader",
9958
+ });
9959
+ if (!resolvedArtifact.profile.ownerSchemes.includes(recipientOwnerScheme))
9960
+ throw new Error(
9961
+ "recipient owner scheme is unsupported by the token profile",
9962
+ );
9859
9963
  const recipientState = {
9860
9964
  ...inputState,
9861
9965
  owner: hexToBytes(recipientOwner),
@@ -9901,18 +10005,28 @@ export function createKcc20PsktBuilderEngine(
9901
10005
  `active holder UTXO ${tokenTxid}:${tokenVout} not found`,
9902
10006
  );
9903
10007
  }
9904
- const rpcScript = tokenEntry.scriptPublicKey ??
9905
- tokenEntry.entry?.scriptPublicKey ?? tokenEntry.utxoEntry?.scriptPublicKey;
9906
- if (!rpcScript || kw.addressFromScriptPublicKey(rpcScript, network)?.toString() !== tokenAddress) {
9907
- throw new Error("active holder RPC script does not match rebuilt covenant state");
10008
+ const rpcScript =
10009
+ tokenEntry.scriptPublicKey ??
10010
+ tokenEntry.entry?.scriptPublicKey ??
10011
+ tokenEntry.utxoEntry?.scriptPublicKey;
10012
+ if (
10013
+ !rpcScript ||
10014
+ kw.addressFromScriptPublicKey(rpcScript, network)?.toString() !==
10015
+ tokenAddress
10016
+ ) {
10017
+ throw new Error(
10018
+ "active holder RPC script does not match rebuilt covenant state",
10019
+ );
9908
10020
  }
9909
- if (activeHolderUtxo.amountSompi != null &&
9910
- BigInt(activeHolderUtxo.amountSompi) !== utxoAmountSompi(tokenEntry)) {
10021
+ if (
10022
+ activeHolderUtxo.amountSompi != null &&
10023
+ BigInt(activeHolderUtxo.amountSompi) !== utxoAmountSompi(tokenEntry)
10024
+ ) {
9911
10025
  throw new Error("active holder KAS amount does not match RPC");
9912
10026
  }
9913
10027
  const spendCovenantId = params.activeHolderNativeCovenantId
9914
10028
  ? requireMatchingCovenantId(tokenEntry, covenantId, "active holder")
9915
- : covenantIdFromUtxoEntry(tokenEntry) ?? covenantId;
10029
+ : (covenantIdFromUtxoEntry(tokenEntry) ?? covenantId);
9916
10030
 
9917
10031
  let outputSompi = 0n;
9918
10032
  const recipientOutputIndex = 0;
@@ -9991,9 +10105,9 @@ export function createKcc20PsktBuilderEngine(
9991
10105
  outputs,
9992
10106
  subnetworkId: SUBNETWORK_ID_NATIVE,
9993
10107
  gas: 0n,
9994
- payload: buildTransferPayload({
10108
+ payload: buildAuthenticatedKcc20TransferPayload({
9995
10109
  covenantId: spendCovenantId,
9996
- fromOwner: owner,
10110
+ fromOwnerIdentifier: bytesToHex(inputState.ownerIdentifier),
9997
10111
  recipientOwner,
9998
10112
  recipientOwnerScheme,
9999
10113
  tokenAmount: transferAmount,
@@ -10007,11 +10121,13 @@ export function createKcc20PsktBuilderEngine(
10007
10121
  inputIndex: 0,
10008
10122
  scriptHex: bytesToHex(tokenScript),
10009
10123
  signType: 1,
10124
+ signatureAlgorithm: authorization.signatureAlgorithm,
10010
10125
  signatureScript: {
10011
10126
  mode: "ordered-args",
10012
10127
  args: kcc20TransferOrderedArgs(
10013
10128
  artifact,
10014
10129
  changeState ? [recipientState, changeState] : [recipientState],
10130
+ authorization.argument,
10015
10131
  ),
10016
10132
  },
10017
10133
  },
@@ -10038,6 +10154,10 @@ export function createKcc20PsktBuilderEngine(
10038
10154
  scripts,
10039
10155
  submitTransactionSupported: true,
10040
10156
  metadata: {
10157
+ tokenProfileId: resolvedArtifact.profile.id,
10158
+ tokenProfileRegistryHash:
10159
+ options.tokenProfileRegistry?.sha256 ??
10160
+ BUNDLED_TOKEN_PROFILE_DIGEST,
10041
10161
  builderKey: "kcc20.transfer",
10042
10162
  operation: "transfer-token",
10043
10163
  action: "transfer",
@@ -11264,37 +11384,6 @@ export function createKcc20PsktBuilderEngine(
11264
11384
  );
11265
11385
  }
11266
11386
 
11267
- function buildTransferPayload(fields) {
11268
- return new TextEncoder().encode(
11269
- JSON.stringify({
11270
- tn12: {
11271
- v: 1,
11272
- tmpl: "KCC20",
11273
- op: "transfer",
11274
- args: [
11275
- { name: "covenantId", type: "byte[32]", value: fields.covenantId },
11276
- { name: "fromOwner", type: "byte[32]", value: fields.fromOwner },
11277
- {
11278
- name: "recipientOwner",
11279
- type: "byte[32]",
11280
- value: fields.recipientOwner,
11281
- },
11282
- {
11283
- name: "recipientOwnerScheme",
11284
- type: "u64",
11285
- value: String(fields.recipientOwnerScheme),
11286
- },
11287
- {
11288
- name: "tokenAmount",
11289
- type: "u64",
11290
- value: fields.tokenAmount.toString(),
11291
- },
11292
- ],
11293
- },
11294
- }),
11295
- );
11296
- }
11297
-
11298
11387
  function tokenOutput(
11299
11388
  kw,
11300
11389
  artifact,
@@ -13046,9 +13135,9 @@ export function createKcc20PsktBuilderEngine(
13046
13135
  );
13047
13136
  const publicMintActive = Boolean(
13048
13137
  source.publicMintActive ??
13049
- state.publicMintActive ??
13050
- mintPolicy.publicMintActive ??
13051
- false,
13138
+ state.publicMintActive ??
13139
+ mintPolicy.publicMintActive ??
13140
+ false,
13052
13141
  );
13053
13142
  const extension = {
13054
13143
  kind: Number(source.kind ?? 1),
@@ -13186,20 +13275,41 @@ export function createKcc20PsktBuilderEngine(
13186
13275
  return values;
13187
13276
  }
13188
13277
 
13189
- function kcc20TransferOrderedArgs(artifact, nextStates) {
13278
+ function profileForArtifact(artifact) {
13279
+ const parts = templateParts(artifact);
13280
+ const hash = bytesToHex(templateHash(parts.prefix, parts.suffix));
13281
+ const profile = tokenProfiles.profiles.find((p) => p.templateHash === hash);
13282
+ if (!profile) throw new Error("token artifact has no bundled profile");
13283
+ return profile;
13284
+ }
13285
+
13286
+ function kcc20TransferOrderedArgs(artifact, nextStates, witness) {
13287
+ const profile = profileForArtifact(artifact);
13288
+ if (nextStates.length > profile.maxTokenOutputs)
13289
+ throw new Error("transfer exceeds profile output bound");
13190
13290
  const fields =
13191
13291
  encodeKcc20StateRecordArrays(nextStates).map(dataTemplateArg);
13192
13292
  return [
13193
13293
  ...fields,
13194
- { type: "signature", prefixHex: "00" },
13195
- dataTemplateArg(dispatchTagFor(artifact, "transfer")),
13294
+ witness ?? { type: "signature", prefixHex: "00" },
13295
+ dataTemplateArg(
13296
+ dispatchTagFor(
13297
+ artifact,
13298
+ profileForArtifact(artifact).transferEntrypoint,
13299
+ ),
13300
+ ),
13196
13301
  ];
13197
13302
  }
13198
13303
 
13199
13304
  function kcc20TransferDelegatorOrderedArgs(artifact) {
13200
13305
  return [
13201
13306
  { type: "signature" },
13202
- dataTemplateArg(dispatchTagFor(artifact, "transfer_delegator")),
13307
+ dataTemplateArg(
13308
+ dispatchTagFor(
13309
+ artifact,
13310
+ profileForArtifact(artifact).delegatorEntrypoint,
13311
+ ),
13312
+ ),
13203
13313
  ];
13204
13314
  }
13205
13315
 
package/src/index.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  export * from "./artifacts.js";
2
+ export * from "./token-profiles.js";
3
+ export * from "./token-profiles.generated.js";
2
4
  export * from "./action-source-resolver.js";
3
5
  export * from "./abi.js";
4
6
  export * from "./builder.js";
@@ -34,3 +36,8 @@ export * as snapshot from "./snapshot-signerless.js";
34
36
  export * as snapshotTransactions from "./snapshot-signerless-transactions.js";
35
37
  export * from "./snapshot-deployment.js";
36
38
  export * from "./snapshot-verification.js";
39
+
40
+ export * from "./token-authorization.js";
41
+
42
+ export * from "./profile-transfer.js";
43
+ export * from "./transfer-payload.js";