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