@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.
- package/CHANGELOG.md +6 -0
- package/README.md +2 -2
- package/RELEASING.md +19 -14
- package/artifacts/ArgentKCC20.reference.json +12428 -0
- package/artifacts/ArgentKCC20Wrapper.placeholder.json +12455 -0
- package/artifacts/KCC20.placeholder.json +1447 -1973
- package/artifacts/KCC20FeeTicket.placeholder.json +816 -1447
- package/artifacts/KCC20Orderbook.placeholder.json +1 -2
- package/artifacts/KCC20SnapshotBootstrap.placeholder.json +101 -100
- package/artifacts/KCC20SnapshotController.placeholder.json +101 -100
- package/artifacts/KCC20Vesting.placeholder.json +427 -429
- package/artifacts/KCC20Wrapper.placeholder.json +733 -734
- package/artifacts/kcc20-snapshot-bootstrap/KCC20SnapshotBootstrap.placeholder.json +101 -100
- package/artifacts/kcc20-snapshot-controller/KCC20SnapshotController.placeholder.json +101 -100
- package/artifacts/snapshot-standards.lock.json +6 -3
- package/artifacts/token-profiles.json +109 -0
- package/artifacts/token-profiles.sha256 +1 -0
- package/dist/action-source-resolver.d.ts.map +1 -1
- package/dist/action-source-resolver.js +11 -2
- package/dist/action-source-resolver.js.map +1 -1
- package/dist/artifacts.d.ts +10 -10
- package/dist/artifacts.d.ts.map +1 -1
- package/dist/artifacts.js +10 -10
- package/dist/artifacts.js.map +1 -1
- package/dist/cjs/action-source-resolver.js +11 -2
- package/dist/cjs/action-source-resolver.js.map +1 -1
- package/dist/cjs/artifacts.d.ts +10 -10
- package/dist/cjs/artifacts.js +10 -10
- package/dist/cjs/artifacts.js.map +1 -1
- package/dist/cjs/engine.d.ts +10 -0
- package/dist/cjs/engine.js +129 -77
- package/dist/cjs/engine.js.map +1 -1
- package/dist/cjs/index.d.ts +5 -0
- package/dist/cjs/index.js +5 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/profile-transfer.d.ts +60 -0
- package/dist/cjs/profile-transfer.js +248 -0
- package/dist/cjs/profile-transfer.js.map +1 -0
- package/dist/cjs/snapshot-signerless-transactions.js +2 -5
- package/dist/cjs/snapshot-signerless-transactions.js.map +1 -1
- package/dist/cjs/snapshot-signerless.js +5 -3
- package/dist/cjs/snapshot-signerless.js.map +1 -1
- package/dist/cjs/token-authorization.d.ts +42 -0
- package/dist/cjs/token-authorization.js +93 -0
- package/dist/cjs/token-authorization.js.map +1 -0
- package/dist/cjs/token-profiles.d.ts +82 -0
- package/dist/cjs/token-profiles.generated.d.ts +3 -0
- package/dist/cjs/token-profiles.generated.js +114 -0
- package/dist/cjs/token-profiles.generated.js.map +1 -0
- package/dist/cjs/token-profiles.js +257 -0
- package/dist/cjs/token-profiles.js.map +1 -0
- package/dist/cjs/transfer-payload.d.ts +9 -0
- package/dist/cjs/transfer-payload.js +37 -0
- package/dist/cjs/transfer-payload.js.map +1 -0
- package/dist/cjs/wrapper-operation.d.ts +1 -0
- package/dist/cjs/wrapper-operation.js +1 -0
- package/dist/cjs/wrapper-operation.js.map +1 -1
- package/dist/engine.d.ts +10 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +130 -78
- package/dist/engine.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/profile-transfer.d.ts +61 -0
- package/dist/profile-transfer.d.ts.map +1 -0
- package/dist/profile-transfer.js +245 -0
- package/dist/profile-transfer.js.map +1 -0
- package/dist/snapshot-signerless-transactions.d.ts.map +1 -1
- package/dist/snapshot-signerless-transactions.js +2 -5
- package/dist/snapshot-signerless-transactions.js.map +1 -1
- package/dist/snapshot-signerless.d.ts.map +1 -1
- package/dist/snapshot-signerless.js +5 -3
- package/dist/snapshot-signerless.js.map +1 -1
- package/dist/token-authorization.d.ts +43 -0
- package/dist/token-authorization.d.ts.map +1 -0
- package/dist/token-authorization.js +88 -0
- package/dist/token-authorization.js.map +1 -0
- package/dist/token-profiles.d.ts +83 -0
- package/dist/token-profiles.d.ts.map +1 -0
- package/dist/token-profiles.generated.d.ts +4 -0
- package/dist/token-profiles.generated.d.ts.map +1 -0
- package/dist/token-profiles.generated.js +111 -0
- package/dist/token-profiles.generated.js.map +1 -0
- package/dist/token-profiles.js +245 -0
- package/dist/token-profiles.js.map +1 -0
- package/dist/transfer-payload.d.ts +10 -0
- package/dist/transfer-payload.d.ts.map +1 -0
- package/dist/transfer-payload.js +34 -0
- package/dist/transfer-payload.js.map +1 -0
- package/dist/wrapper-operation.d.ts +1 -0
- package/dist/wrapper-operation.d.ts.map +1 -1
- package/dist/wrapper-operation.js +1 -0
- package/dist/wrapper-operation.js.map +1 -1
- package/docs/COMPATIBILITY.json +24 -33
- package/docs/LAST_CALL.md +50 -0
- package/examples/README.md +3 -0
- package/examples/backend/cli.ts +2 -1
- package/examples/frontend/main.ts +2 -1
- package/examples/shared/build.ts +3 -0
- package/examples/shared/offline-profile.ts +27 -0
- package/examples/shared/recipe-fixtures.ts +3 -2
- package/examples/test/browser.ts +2 -2
- package/examples/test/build.test.ts +21 -8
- package/examples/test/sources.test.ts +3 -2
- package/package.json +2 -2
- package/src/action-source-resolver.ts +22 -4
- package/src/artifacts.ts +12 -10
- package/src/engine.ts +215 -105
- package/src/index.ts +7 -0
- package/src/profile-transfer.ts +425 -0
- package/src/snapshot-signerless-transactions.ts +7 -16
- package/src/snapshot-signerless.ts +5 -3
- package/src/token-authorization.ts +135 -0
- package/src/token-profiles.generated.ts +112 -0
- package/src/token-profiles.ts +456 -0
- package/src/transfer-payload.ts +45 -0
- 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
|
-
|
|
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
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
|
|
255
|
-
|
|
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
|
-
|
|
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
|
-
|
|
313
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
9905
|
-
tokenEntry.
|
|
9906
|
-
|
|
9907
|
-
|
|
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 (
|
|
9910
|
-
|
|
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:
|
|
10108
|
+
payload: buildAuthenticatedKcc20TransferPayload({
|
|
9995
10109
|
covenantId: spendCovenantId,
|
|
9996
|
-
|
|
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
|
-
|
|
13050
|
-
|
|
13051
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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";
|