@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/dist/engine.js CHANGED
@@ -1,7 +1,11 @@
1
1
  // @ts-nocheck
2
+ import { planTokenOwnerWitness } from "./token-authorization.js";
2
3
  import { sha256 } from "@noble/hashes/sha256";
4
+ import { authenticateTokenArtifact, authenticateTokenWrapper, validateTokenProfileRegistry, requireTokenCapability, resolveTokenProfilePriceScale, } from "./token-profiles.js";
5
+ import { buildAuthenticatedKcc20TransferPayload } from "./transfer-payload.js";
6
+ import { BUNDLED_TOKEN_PROFILES, BUNDLED_TOKEN_PROFILE_DIGEST, } from "./token-profiles.generated.js";
3
7
  import { DEFAULT_COMPUTE_BUDGET, DEFAULT_KCC20_FEE_BPS, DEFAULT_KCC20_MIN_PROTOCOL_FEE_SOMPI, DEFAULT_KCC20_PRICE_SCALE, DEFAULT_TOKEN_OUTPUT_SOMPI, KCC20_ORDERBOOK_EXPANDED_SWEEP_OUTPUT_SOMPI, KCC20_ORDERBOOK_MAX_SWEEP_BID_LEGS, KCC20_ORDERBOOK_MAX_EXPANDED_SWEEP_BID_LEGS, KCC20_PLATFORM_PROTOCOL_FEE_RECIPIENT, KCC20_ORDERBOOK_PROTOCOL_FEE_RECIPIENT, SUBNETWORK_ID_NATIVE, SOMPI_PER_TKAS, asciiToBytes32, buildKcc20FeeTicketScriptForState, buildNativeKcc20ScriptForState, buildKcc20WrapperScriptForState, buildKcc20OrderbookScriptForState, buildKcc20V3CreateBidSigScript, buildKcc20V3SellIntoBidSigScript, buildKcc20OrderbookFillBidFromSellerSweepSigScript, buildKcc20OrderbookFillAskSigScript, buildKcc20OrderbookFillBidSigScript, buildKcc20OrderbookSellIntoBidsSigScript, buildKcc20OrderbookSellIntoBids10SigScript, buildKcc20OrderbookCrossAskSigScript, buildKcc20OrderbookCrossAskLegacySigScript, buildKcc20OrderbookCrossBidSigScript, buildKcc20OrderbookCrossBidLegacySigScript, bytesToHex, calculateFeeSplit, findOutputIndex, hexToBytes, kcc20PsktBuilderError, mintPolicyName, normalizeSignature, p2pkScriptPubKey, p2shScriptHashHex, parseMintPolicy, requireHex32, selectFundingEntry, setVersionOneInputMassFields, stateToReceiptFields, utxoAmountSompi, ZERO_HASH, } from "./sale-common.js";
4
- import { buildKcc20BurnSigScript, buildKcc20MintByOwnerSigScript, buildKcc20MintPublicSigScript, buildKcc20ScriptForState, buildKcc20SetPublicMintActiveSigScript, buildKcc20TransferSigScript, dispatchTagFor, encodeKcc20StateRecordArrays, holderExtensionCommitment, mintExtensionCommitment, normalizeSilverScriptArtifact, ownerAuthorizationWitness, } from "./abi.js";
8
+ import { buildKcc20BurnSigScript, buildKcc20MintByOwnerSigScript, buildKcc20MintPublicSigScript, buildKcc20ScriptForState, buildKcc20SetPublicMintActiveSigScript, buildKcc20TransferSigScript, dispatchTagFor, templateHash, encodeKcc20StateRecordArrays, holderExtensionCommitment, mintExtensionCommitment, normalizeSilverScriptArtifact, ownerAuthorizationWitness, } from "./abi.js";
5
9
  import { KCC20_BUILDER_KEYS } from "./operations.js";
6
10
  import { createKcc20SnapshotPsktBuilder } from "./snapshot-operation.js";
7
11
  import { splitKcc20MintSupply } from "./protocol.js";
@@ -102,6 +106,7 @@ export function createKcc20PsktBuilderEngine(options) {
102
106
  const WRAPPED_ARTIFACT_PATH = "KCC20Orderbook.placeholder.json";
103
107
  const FEE_TICKET_ARTIFACT_PATH = "KCC20FeeTicket.placeholder.json";
104
108
  const staticArtifactCache = new Map();
109
+ const tokenProfiles = validateTokenProfileRegistry(options.tokenProfileRegistry?.manifest ?? BUNDLED_TOKEN_PROFILES, options.tokenProfileRegistry?.sha256 ?? BUNDLED_TOKEN_PROFILE_DIGEST);
105
110
  function readStaticArtifact(path) {
106
111
  let cached = staticArtifactCache.get(path);
107
112
  if (!cached) {
@@ -114,7 +119,16 @@ export function createKcc20PsktBuilderEngine(options) {
114
119
  ? (artifact.artifact ?? artifact)
115
120
  : artifact;
116
121
  const normalized = normalizeSilverScriptArtifact(artifactValue);
117
- assertKcc20ArtifactScriptHash(path, normalized);
122
+ const profile = tokenProfiles.profiles.find((p) => p.artifactKey === path);
123
+ if (profile)
124
+ authenticateTokenArtifact(profile, artifactValue);
125
+ else {
126
+ const ownerProfile = tokenProfiles.profiles.find((p) => p.wrapper?.artifactKey === path);
127
+ if (ownerProfile)
128
+ authenticateTokenWrapper(ownerProfile, artifactValue);
129
+ else
130
+ assertKcc20ArtifactScriptHash(path, normalized);
131
+ }
118
132
  return normalized;
119
133
  })
120
134
  .catch((error) => {
@@ -132,15 +146,20 @@ export function createKcc20PsktBuilderEngine(options) {
132
146
  };
133
147
  }
134
148
  async function resolveKcc20ArtifactForState(state, address, network) {
135
- const artifact = await readStaticArtifact(KCC20_ARTIFACT_PATH);
136
- const script = buildKcc20ScriptForState(artifact, state);
137
- const candidateAddress = kaspaWasm
138
- .addressFromScriptPublicKey(kaspaWasm.payToScriptHashScript(script), network)
139
- .toString();
140
- if (candidateAddress !== address) {
141
- throw new Error("KCC20 UTXO address does not match the current artifact");
149
+ for (const profile of tokenProfiles.profiles) {
150
+ const artifact = await readStaticArtifact(profile.artifactKey);
151
+ const script = buildKcc20ScriptForState(artifact, state);
152
+ const candidateAddress = kaspaWasm
153
+ .addressFromScriptPublicKey(kaspaWasm.payToScriptHashScript(script), network)
154
+ .toString();
155
+ if (candidateAddress !== address)
156
+ continue;
157
+ requireTokenCapability(profile, "transfer");
158
+ if (!profile.ownerSchemes.includes(Number(state.ownerScheme ?? state.identifierType)))
159
+ throw new Error("owner scheme is unsupported by this token profile");
160
+ return { version: profile.id, profile, artifact, script };
142
161
  }
143
- return { version: "standard-native-v1", artifact, script };
162
+ throw new Error("KCC20 UTXO address does not match a bundled token profile");
144
163
  }
145
164
  function artifactEntrypointDispatchTag(artifact, entrypoint) {
146
165
  try {
@@ -175,13 +194,14 @@ export function createKcc20PsktBuilderEngine(options) {
175
194
  },
176
195
  ];
177
196
  }
178
- async function readWrapperArtifacts() {
179
- return [
180
- {
181
- artifact: await readStaticArtifact(WRAPPER_ARTIFACT_PATH),
197
+ async function readWrapperArtifacts(profile) {
198
+ if (!profile.wrapper)
199
+ return [];
200
+ return [{
201
+ artifact: await readStaticArtifact(profile.wrapper.artifactKey),
182
202
  version: "standard-native-v1",
183
- },
184
- ];
203
+ tokenProfileId: profile.id,
204
+ }];
185
205
  }
186
206
  function wrappedArtifactSupportsEntrypoint(info, name) {
187
207
  return Array.isArray(info?.artifact?.abi)
@@ -541,7 +561,7 @@ export function createKcc20PsktBuilderEngine(options) {
541
561
  };
542
562
  const kw = kaspaWasm;
543
563
  const { CovenantBinding, Encoding, Hash, RpcClient, ScriptBuilder, Transaction, TransactionOutput, addressFromScriptPublicKey, payToAddressScript, payToScriptHashScript, } = kw;
544
- const utilityScript = buildNativeKcc20ScriptForState(kcc20Artifact.script, utilityState);
564
+ const utilityScript = buildKcc20ScriptForState(kcc20Artifact, utilityState);
545
565
  const rootAddress = requireKaspaAddress(rootUtxo.address);
546
566
  const utilityAddress = requireKaspaAddress(utilityUtxo.address);
547
567
  const rootArtifactInfo = selectFeeTicketArtifactForState(feeTicketArtifacts, rootState, rootAddress, network, "active FeeTicket root UTXO address");
@@ -1659,10 +1679,7 @@ export function createKcc20PsktBuilderEngine(options) {
1659
1679
  const feeTicketId = requireHex32(params.feeTicketId || ZERO_HASH, "fee ticket id");
1660
1680
  const deployerOwner = requireHex32(params.deployerOwner || request.owner?.kcc20Owner, "deployer owner");
1661
1681
  const rootAmount = parseU64(params.wrappedRootAmount ?? "0", "wrappedRootAmount");
1662
- const priceScale = parsePositiveU64(params.priceScale ??
1663
- params.tokenDisplayScale ??
1664
- DEFAULT_KCC20_PRICE_SCALE, "priceScale");
1665
- assertWrapperArtifactPriceScale(priceScale);
1682
+ const requestedPriceScale = params.priceScale ?? params.tokenDisplayScale;
1666
1683
  const network = request.network || env.KASPA_NETWORK || "testnet-10";
1667
1684
  const walletAddress = requireKaspaAddress(request.owner?.walletAddress);
1668
1685
  const rpcUrl = env.KASPA_WRPC_URL || DEFAULT_WRPC_URL;
@@ -1671,7 +1688,19 @@ export function createKcc20PsktBuilderEngine(options) {
1671
1688
  DEFAULT_DEPLOY_TKAS, "KCC20_WRAPPER_DEPLOY_OUTPUT_TKAS");
1672
1689
  const priorityFee = wrapperDeployPriorityFee(env.KCC20_WRAPPER_DEPLOY_PRIORITY_FEE_SOMPI);
1673
1690
  const computeBudget = parsePositiveNumber(env.KCC20_WRAPPER_DEPLOY_COMPUTE_BUDGET || 600, "computeBudget");
1674
- const wrapperArtifact = await readStaticArtifact(WRAPPER_ARTIFACT_PATH);
1691
+ const evidence = params.tokenUtxo;
1692
+ if (!evidence)
1693
+ throw new Error("authenticated token output is required before wrapper deployment");
1694
+ const sourceState = kcc20StateFromUtxo(evidence, request.owner);
1695
+ const sourceProfile = await resolveKcc20ArtifactForState(sourceState, requireKaspaAddress(evidence.address), network);
1696
+ requireTokenCapability(sourceProfile.profile, "wrap");
1697
+ if (!sourceProfile.profile.wrapper)
1698
+ throw new Error("token profile has no wrapper adapter");
1699
+ const priceScale = resolveTokenProfilePriceScale(sourceProfile.profile, requestedPriceScale === undefined
1700
+ ? undefined
1701
+ : parsePositiveU64(requestedPriceScale, "priceScale"), DEFAULT_KCC20_PRICE_SCALE);
1702
+ assertWrapperArtifactPriceScale(priceScale);
1703
+ const wrapperArtifact = await readStaticArtifact(sourceProfile.profile.wrapper.artifactKey);
1675
1704
  const wrappedArtifacts = await readWrappedArtifacts();
1676
1705
  const wrappedArtifact = wrappedArtifacts[0].artifact;
1677
1706
  const wrapperState = {
@@ -1704,6 +1733,18 @@ export function createKcc20PsktBuilderEngine(options) {
1704
1733
  });
1705
1734
  await rpc.connect();
1706
1735
  try {
1736
+ const evidenceUtxos = await getUtxosByAddresses(rpc, [evidence.address]);
1737
+ const sourceEntry = findUtxoEntry(evidenceUtxos.entries, requireHex32(evidence.txidHex, "token evidence txid"), parseVout(evidence.vout, "token evidence vout"));
1738
+ if (!sourceEntry)
1739
+ throw new Error("token evidence is spent or unavailable");
1740
+ requireMatchingCovenantId(sourceEntry, contractCanonicalTokenId, "wrapper token evidence");
1741
+ const sourceSpk = sourceEntry.scriptPublicKey ??
1742
+ sourceEntry.entry?.scriptPublicKey ??
1743
+ sourceEntry.utxoEntry?.scriptPublicKey;
1744
+ if (!sourceSpk ||
1745
+ kw.addressFromScriptPublicKey(sourceSpk, network)?.toString() !==
1746
+ evidence.address)
1747
+ throw new Error("token evidence script does not match RPC");
1707
1748
  const utxos = await getUtxosByAddresses(rpc, [walletAddress]);
1708
1749
  const fundingEntry = selectFundingEntry(utxos.entries, deploySompi * 2n + priorityFee + 10000000n);
1709
1750
  const fundingChangeOutputIndex = 2;
@@ -1784,6 +1825,9 @@ export function createKcc20PsktBuilderEngine(options) {
1784
1825
  submitTransactionSupported: true,
1785
1826
  metadata: {
1786
1827
  builderKey: "kcc20wrapper.deploy-market",
1828
+ tokenProfileId: sourceProfile.profile.id,
1829
+ tokenProfileRegistryHash: options.tokenProfileRegistry?.sha256 ??
1830
+ BUNDLED_TOKEN_PROFILE_DIGEST,
1787
1831
  contract: "KCC20Wrapper",
1788
1832
  network,
1789
1833
  walletAddress,
@@ -1863,10 +1907,7 @@ export function createKcc20PsktBuilderEngine(options) {
1863
1907
  DEFAULT_TOKEN_OUTPUT_SOMPI.toString(), "tokenOutputSompi");
1864
1908
  const priorityFee = parseU64(env.KCC20_WRAP_PRIORITY_FEE_SOMPI || DEFAULT_PRIORITY_FEE.toString(), "priorityFee");
1865
1909
  const computeBudget = parsePositiveNumber(env.KCC20_WRAP_COMPUTE_BUDGET || 700, "computeBudget");
1866
- const [wrapperArtifacts, wrappedArtifacts] = await Promise.all([
1867
- readWrapperArtifacts(),
1868
- readWrappedArtifacts(),
1869
- ]);
1910
+ const wrappedArtifacts = await readWrappedArtifacts();
1870
1911
  const holderState = kcc20StateFromUtxo(holderUtxo, request.owner);
1871
1912
  if (holderState.identifierType !== 0) {
1872
1913
  throw new Error("wrap requires pubkey-owned KCC20 holder UTXO");
@@ -1881,6 +1922,8 @@ export function createKcc20PsktBuilderEngine(options) {
1881
1922
  }
1882
1923
  const kcc20ArtifactInfo = await resolveKcc20ArtifactForState(holderState, requireKaspaAddress(holderUtxo.address), network);
1883
1924
  const kcc20Artifact = kcc20ArtifactInfo.artifact;
1925
+ requireTokenCapability(kcc20ArtifactInfo.profile, "wrap");
1926
+ const wrapperArtifacts = await readWrapperArtifacts(kcc20ArtifactInfo.profile);
1884
1927
  const remaining = holderState.amount - tokenAmount;
1885
1928
  const priceScale = parsePositiveU64(wrapperUtxo.state?.priceScale ??
1886
1929
  wrapperUtxo.state?.tokenDisplayScale ??
@@ -1907,7 +1950,7 @@ export function createKcc20PsktBuilderEngine(options) {
1907
1950
  const { CovenantBinding, Encoding, Hash, RpcClient, ScriptBuilder, Transaction, TransactionOutput, payToAddressScript, payToScriptHashScript, } = kw;
1908
1951
  const wrapperArtifactInfo = selectWrapperArtifactForUtxo({
1909
1952
  kw,
1910
- artifacts: wrapperArtifacts,
1953
+ artifacts: wrapperArtifacts.filter((info) => info.tokenProfileId === kcc20ArtifactInfo.profile.id),
1911
1954
  state: wrapperState,
1912
1955
  utxo: wrapperUtxo,
1913
1956
  network,
@@ -1920,7 +1963,7 @@ export function createKcc20PsktBuilderEngine(options) {
1920
1963
  const wrapperArtifact = wrapperArtifactInfo.artifact;
1921
1964
  const wrappedArtifact = wrappedArtifactInfo.artifact;
1922
1965
  const wrapperScript = wrapperArtifactInfo.script;
1923
- const holderScript = buildNativeKcc20ScriptForState(kcc20Artifact.script, holderState);
1966
+ const holderScript = buildKcc20ScriptForState(kcc20Artifact, holderState);
1924
1967
  const wrapperAddress = requireKaspaAddress(wrapperUtxo.address);
1925
1968
  const holderAddress = requireKaspaAddress(holderUtxo.address);
1926
1969
  assertScriptAddress(kw, holderScript, holderAddress, network, "KCC20 holder UTXO address");
@@ -2133,10 +2176,7 @@ export function createKcc20PsktBuilderEngine(options) {
2133
2176
  DEFAULT_TOKEN_OUTPUT_SOMPI.toString(), "tokenOutputSompi");
2134
2177
  const priorityFee = parseU64(env.KCC20_UNWRAP_PRIORITY_FEE_SOMPI || DEFAULT_PRIORITY_FEE.toString(), "priorityFee");
2135
2178
  const computeBudget = parsePositiveNumber(env.KCC20_UNWRAP_COMPUTE_BUDGET || 700, "computeBudget");
2136
- const [wrapperArtifacts, wrappedArtifacts] = await Promise.all([
2137
- readWrapperArtifacts(),
2138
- readWrappedArtifacts(),
2139
- ]);
2179
+ const wrappedArtifacts = await readWrappedArtifacts();
2140
2180
  const reserveState = kcc20StateFromUtxo(reserveUtxo, request.owner);
2141
2181
  if (reserveState.ownerScheme !== 4 || reserveState.isMintAuthority) {
2142
2182
  throw new Error("KCC20 reserve UTXO must be covenant-owned holder state");
@@ -2146,6 +2186,8 @@ export function createKcc20PsktBuilderEngine(options) {
2146
2186
  }
2147
2187
  const kcc20ArtifactInfo = await resolveKcc20ArtifactForState(reserveState, requireKaspaAddress(reserveUtxo.address), network);
2148
2188
  const kcc20Artifact = kcc20ArtifactInfo.artifact;
2189
+ requireTokenCapability(kcc20ArtifactInfo.profile, "unwrap");
2190
+ const wrapperArtifacts = await readWrapperArtifacts(kcc20ArtifactInfo.profile);
2149
2191
  const wrappedState = kcc20V3WrappedStateFromUtxo(wrappedUtxo, canonicalTokenId, request.owner);
2150
2192
  if (bytesToHex(wrappedState.ownerIdentifier) !== recipientOwner) {
2151
2193
  throw new Error("KCC20 orderbook holder UTXO is not owned by authenticated wallet");
@@ -2189,7 +2231,7 @@ export function createKcc20PsktBuilderEngine(options) {
2189
2231
  const { CovenantBinding, Encoding, Hash, RpcClient, ScriptBuilder, Transaction, TransactionOutput, payToAddressScript, payToScriptHashScript, } = kw;
2190
2232
  const wrapperArtifactInfo = selectWrapperArtifactForUtxo({
2191
2233
  kw,
2192
- artifacts: wrapperArtifacts,
2234
+ artifacts: wrapperArtifacts.filter((info) => info.tokenProfileId === kcc20ArtifactInfo.profile.id),
2193
2235
  state: wrapperState,
2194
2236
  utxo: wrapperUtxo,
2195
2237
  network,
@@ -2211,7 +2253,7 @@ export function createKcc20PsktBuilderEngine(options) {
2211
2253
  const wrapperScript = wrapperArtifactInfo.script;
2212
2254
  const wrappedScript = wrappedArtifactInfo.script;
2213
2255
  const refundAware = wrappedArtifactRefundsHolderDeposits(wrappedArtifactInfo);
2214
- const reserveScript = buildNativeKcc20ScriptForState(kcc20Artifact.script, reserveState);
2256
+ const reserveScript = buildKcc20ScriptForState(kcc20Artifact, reserveState);
2215
2257
  const wrapperAddress = wrapperArtifactInfo.address;
2216
2258
  const wrappedAddress = wrappedArtifactInfo.address;
2217
2259
  const reserveAddress = requireKaspaAddress(reserveUtxo.address);
@@ -5665,6 +5707,7 @@ export function createKcc20PsktBuilderEngine(options) {
5665
5707
  extensionCommitment: mintExtensionCommitment(extension),
5666
5708
  };
5667
5709
  const resolvedArtifact = await resolveKcc20ArtifactForState(inputState, minterAddress, network);
5710
+ requireTokenCapability(resolvedArtifact.profile, "mint");
5668
5711
  const artifact = resolvedArtifact.artifact;
5669
5712
  const recipientState = {
5670
5713
  amount: mintAmount,
@@ -5902,6 +5945,11 @@ export function createKcc20PsktBuilderEngine(options) {
5902
5945
  if (requestedMinterUtxos.length === 0) {
5903
5946
  throw new Error("active KCC20 mint-authority UTXO is required");
5904
5947
  }
5948
+ // The caller submits larger updates as sequential batches. Never truncate
5949
+ // this array: every supplied authority must be represented in the result.
5950
+ if (requestedMinterUtxos.length > 5) {
5951
+ throw new Error("Public mint availability updates support at most 5 authorities per transaction");
5952
+ }
5905
5953
  if (typeof params.active !== "boolean") {
5906
5954
  throw new Error("active must be a boolean");
5907
5955
  }
@@ -5954,6 +6002,7 @@ export function createKcc20PsktBuilderEngine(options) {
5954
6002
  extensionCommitment: mintExtensionCommitment(nextExtension),
5955
6003
  };
5956
6004
  const resolvedArtifact = await resolveKcc20ArtifactForState(inputState, minterAddress, network);
6005
+ requireTokenCapability(resolvedArtifact.profile, "mint");
5957
6006
  dispatchTagFor(resolvedArtifact.artifact, "set_public_mint_active");
5958
6007
  const minterTxid = requireHex32(activeMinterUtxo.txidHex, "minter txid");
5959
6008
  const minterVout = parseVout(activeMinterUtxo.vout, "minter vout");
@@ -6177,6 +6226,10 @@ export function createKcc20PsktBuilderEngine(options) {
6177
6226
  if (resolvedArtifacts.some((candidate) => candidate.version !== resolvedArtifacts[0].version)) {
6178
6227
  throw new Error("consolidation requires holder UTXOs from one artifact");
6179
6228
  }
6229
+ const profile = resolvedArtifacts[0].profile;
6230
+ if (holderUtxos.length > profile.maxTokenInputs) {
6231
+ throw new Error("consolidation exceeds the token profile input bound");
6232
+ }
6180
6233
  const artifact = resolvedArtifacts[0].artifact;
6181
6234
  for (const peerState of states.slice(1)) {
6182
6235
  assertKcc20MergeableStates(primaryState, peerState, owner);
@@ -6480,12 +6533,6 @@ export function createKcc20PsktBuilderEngine(options) {
6480
6533
  if (inputState.isMintAuthority || inputState.amount <= 0n) {
6481
6534
  throw new Error("KCC20 transfer requires a holder UTXO");
6482
6535
  }
6483
- if (inputState.ownerScheme !== 0) {
6484
- throw new Error("KCC20 transfer requires a Schnorr P2PK holder UTXO");
6485
- }
6486
- if (bytesToHex(inputState.ownerIdentifier) !== owner) {
6487
- throw new Error("KCC20 transfer requires wallet to own the active holder UTXO");
6488
- }
6489
6536
  if (transferAmount > inputState.amount) {
6490
6537
  throw new Error("tokenAmount exceeds active holder balance");
6491
6538
  }
@@ -6494,6 +6541,21 @@ export function createKcc20PsktBuilderEngine(options) {
6494
6541
  const tokenAddress = requireKaspaAddress(activeHolderUtxo.address);
6495
6542
  const resolvedArtifact = await resolveKcc20ArtifactForState(inputState, tokenAddress, network);
6496
6543
  const artifact = resolvedArtifact.artifact;
6544
+ if (params.mode && params.mode !== "owner")
6545
+ throw new Error("borrowed receive requires the explicit borrow transaction builder");
6546
+ const authorization = planTokenOwnerWitness({
6547
+ profile: resolvedArtifact.profile,
6548
+ owner: bytesToHex(inputState.ownerIdentifier),
6549
+ ownerScheme: inputState.ownerScheme,
6550
+ publicKey: request.owner?.publicKey ?? request.owner?.kcc20Owner,
6551
+ capabilities: options.walletSigningCapabilities ?? {
6552
+ schnorr: true,
6553
+ ecdsa: false,
6554
+ },
6555
+ role: "leader",
6556
+ });
6557
+ if (!resolvedArtifact.profile.ownerSchemes.includes(recipientOwnerScheme))
6558
+ throw new Error("recipient owner scheme is unsupported by the token profile");
6497
6559
  const recipientState = {
6498
6560
  ...inputState,
6499
6561
  owner: hexToBytes(recipientOwner),
@@ -6525,8 +6587,11 @@ export function createKcc20PsktBuilderEngine(options) {
6525
6587
  throw new Error(`active holder UTXO ${tokenTxid}:${tokenVout} not found`);
6526
6588
  }
6527
6589
  const rpcScript = tokenEntry.scriptPublicKey ??
6528
- tokenEntry.entry?.scriptPublicKey ?? tokenEntry.utxoEntry?.scriptPublicKey;
6529
- if (!rpcScript || kw.addressFromScriptPublicKey(rpcScript, network)?.toString() !== tokenAddress) {
6590
+ tokenEntry.entry?.scriptPublicKey ??
6591
+ tokenEntry.utxoEntry?.scriptPublicKey;
6592
+ if (!rpcScript ||
6593
+ kw.addressFromScriptPublicKey(rpcScript, network)?.toString() !==
6594
+ tokenAddress) {
6530
6595
  throw new Error("active holder RPC script does not match rebuilt covenant state");
6531
6596
  }
6532
6597
  if (activeHolderUtxo.amountSompi != null &&
@@ -6535,7 +6600,7 @@ export function createKcc20PsktBuilderEngine(options) {
6535
6600
  }
6536
6601
  const spendCovenantId = params.activeHolderNativeCovenantId
6537
6602
  ? requireMatchingCovenantId(tokenEntry, covenantId, "active holder")
6538
- : covenantIdFromUtxoEntry(tokenEntry) ?? covenantId;
6603
+ : (covenantIdFromUtxoEntry(tokenEntry) ?? covenantId);
6539
6604
  let outputSompi = 0n;
6540
6605
  const recipientOutputIndex = 0;
6541
6606
  const outputs = [
@@ -6587,9 +6652,9 @@ export function createKcc20PsktBuilderEngine(options) {
6587
6652
  outputs,
6588
6653
  subnetworkId: SUBNETWORK_ID_NATIVE,
6589
6654
  gas: 0n,
6590
- payload: buildTransferPayload({
6655
+ payload: buildAuthenticatedKcc20TransferPayload({
6591
6656
  covenantId: spendCovenantId,
6592
- fromOwner: owner,
6657
+ fromOwnerIdentifier: bytesToHex(inputState.ownerIdentifier),
6593
6658
  recipientOwner,
6594
6659
  recipientOwnerScheme,
6595
6660
  tokenAmount: transferAmount,
@@ -6602,9 +6667,10 @@ export function createKcc20PsktBuilderEngine(options) {
6602
6667
  inputIndex: 0,
6603
6668
  scriptHex: bytesToHex(tokenScript),
6604
6669
  signType: 1,
6670
+ signatureAlgorithm: authorization.signatureAlgorithm,
6605
6671
  signatureScript: {
6606
6672
  mode: "ordered-args",
6607
- args: kcc20TransferOrderedArgs(artifact, changeState ? [recipientState, changeState] : [recipientState]),
6673
+ args: kcc20TransferOrderedArgs(artifact, changeState ? [recipientState, changeState] : [recipientState], authorization.argument),
6608
6674
  },
6609
6675
  },
6610
6676
  ];
@@ -6620,6 +6686,9 @@ export function createKcc20PsktBuilderEngine(options) {
6620
6686
  scripts,
6621
6687
  submitTransactionSupported: true,
6622
6688
  metadata: {
6689
+ tokenProfileId: resolvedArtifact.profile.id,
6690
+ tokenProfileRegistryHash: options.tokenProfileRegistry?.sha256 ??
6691
+ BUNDLED_TOKEN_PROFILE_DIGEST,
6623
6692
  builderKey: "kcc20.transfer",
6624
6693
  operation: "transfer-token",
6625
6694
  action: "transfer",
@@ -7614,34 +7683,6 @@ export function createKcc20PsktBuilderEngine(options) {
7614
7683
  },
7615
7684
  }));
7616
7685
  }
7617
- function buildTransferPayload(fields) {
7618
- return new TextEncoder().encode(JSON.stringify({
7619
- tn12: {
7620
- v: 1,
7621
- tmpl: "KCC20",
7622
- op: "transfer",
7623
- args: [
7624
- { name: "covenantId", type: "byte[32]", value: fields.covenantId },
7625
- { name: "fromOwner", type: "byte[32]", value: fields.fromOwner },
7626
- {
7627
- name: "recipientOwner",
7628
- type: "byte[32]",
7629
- value: fields.recipientOwner,
7630
- },
7631
- {
7632
- name: "recipientOwnerScheme",
7633
- type: "u64",
7634
- value: String(fields.recipientOwnerScheme),
7635
- },
7636
- {
7637
- name: "tokenAmount",
7638
- type: "u64",
7639
- value: fields.tokenAmount.toString(),
7640
- },
7641
- ],
7642
- },
7643
- }));
7644
- }
7645
7686
  function tokenOutput(kw, artifact, state, sompi, covenantId, authorizingInput) {
7646
7687
  return new kw.TransactionOutput(sompi, kw.payToScriptHashScript(buildKcc20ScriptForState(artifact, state)), new kw.CovenantBinding(authorizingInput, new kw.Hash(covenantId)));
7647
7688
  }
@@ -8937,18 +8978,29 @@ export function createKcc20PsktBuilderEngine(options) {
8937
8978
  values.push({ type: "signature" }, i64TemplateArg(active ? 1 : 0), i64TemplateArg(minterOutputIndex), dataTemplateArg(dispatchTagFor(artifact, "set_public_mint_active")));
8938
8979
  return values;
8939
8980
  }
8940
- function kcc20TransferOrderedArgs(artifact, nextStates) {
8981
+ function profileForArtifact(artifact) {
8982
+ const parts = templateParts(artifact);
8983
+ const hash = bytesToHex(templateHash(parts.prefix, parts.suffix));
8984
+ const profile = tokenProfiles.profiles.find((p) => p.templateHash === hash);
8985
+ if (!profile)
8986
+ throw new Error("token artifact has no bundled profile");
8987
+ return profile;
8988
+ }
8989
+ function kcc20TransferOrderedArgs(artifact, nextStates, witness) {
8990
+ const profile = profileForArtifact(artifact);
8991
+ if (nextStates.length > profile.maxTokenOutputs)
8992
+ throw new Error("transfer exceeds profile output bound");
8941
8993
  const fields = encodeKcc20StateRecordArrays(nextStates).map(dataTemplateArg);
8942
8994
  return [
8943
8995
  ...fields,
8944
- { type: "signature", prefixHex: "00" },
8945
- dataTemplateArg(dispatchTagFor(artifact, "transfer")),
8996
+ witness ?? { type: "signature", prefixHex: "00" },
8997
+ dataTemplateArg(dispatchTagFor(artifact, profileForArtifact(artifact).transferEntrypoint)),
8946
8998
  ];
8947
8999
  }
8948
9000
  function kcc20TransferDelegatorOrderedArgs(artifact) {
8949
9001
  return [
8950
9002
  { type: "signature" },
8951
- dataTemplateArg(dispatchTagFor(artifact, "transfer_delegator")),
9003
+ dataTemplateArg(dispatchTagFor(artifact, profileForArtifact(artifact).delegatorEntrypoint)),
8952
9004
  ];
8953
9005
  }
8954
9006
  function kcc20BurnOrderedArgs(artifact, nextStates, burnAmount) {