@hardkas/tx-builder 0.7.3-alpha → 0.7.4-alpha
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/dist/index.js +105 -24
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -23,7 +23,9 @@ function estimateTransactionMass(input) {
|
|
|
23
23
|
outputs += KASPA_MASS_CONSTANTS.OUTPUT_P2PK;
|
|
24
24
|
} else {
|
|
25
25
|
outputs += KASPA_MASS_CONSTANTS.SCRIPT_FALLBACK;
|
|
26
|
-
warnings.push(
|
|
26
|
+
warnings.push(
|
|
27
|
+
`P2SH/Other script detected for address: ${out.address}. Mass is estimated using placeholder script-size assumptions.`
|
|
28
|
+
);
|
|
27
29
|
}
|
|
28
30
|
}
|
|
29
31
|
if (input.hasChange) {
|
|
@@ -69,16 +71,31 @@ function verifyTxPlanSemantics(plan, context = {}) {
|
|
|
69
71
|
};
|
|
70
72
|
const ePlan = plan;
|
|
71
73
|
if (ePlan.mode === "simulated" && ePlan.networkId !== "simnet") {
|
|
72
|
-
addIssue(
|
|
74
|
+
addIssue(
|
|
75
|
+
"ENV_CONSISTENCY_FAILURE",
|
|
76
|
+
"error",
|
|
77
|
+
`Environment mismatch: simulated plan must target 'simnet', but targets '${ePlan.networkId}'`
|
|
78
|
+
);
|
|
73
79
|
}
|
|
74
80
|
if (!plan.inputs.every((i) => i.address.includes(":"))) {
|
|
75
|
-
addIssue(
|
|
81
|
+
addIssue(
|
|
82
|
+
"INVALID_ADDRESS_FORMAT",
|
|
83
|
+
"error",
|
|
84
|
+
"One or more input addresses are missing prefix (e.g. kaspa:)"
|
|
85
|
+
);
|
|
76
86
|
}
|
|
77
87
|
if (!plan.outputs.every((o) => o.address.includes(":"))) {
|
|
78
|
-
addIssue(
|
|
88
|
+
addIssue(
|
|
89
|
+
"INVALID_ADDRESS_FORMAT",
|
|
90
|
+
"error",
|
|
91
|
+
"One or more output addresses are missing prefix (e.g. kaspa:)"
|
|
92
|
+
);
|
|
79
93
|
}
|
|
80
94
|
const inputTotalSompi = plan.inputs.reduce((sum, i) => sum + BigInt(i.amountSompi), 0n);
|
|
81
|
-
const outputTotalSompi = plan.outputs.reduce(
|
|
95
|
+
const outputTotalSompi = plan.outputs.reduce(
|
|
96
|
+
(sum, o) => sum + BigInt(o.amountSompi),
|
|
97
|
+
0n
|
|
98
|
+
);
|
|
82
99
|
const changeAmountSompi = plan.change ? BigInt(plan.change.amountSompi) : 0n;
|
|
83
100
|
const planFeeSompi = BigInt(plan.estimatedFeeSompi);
|
|
84
101
|
const recomputedFeeSompi = inputTotalSompi - outputTotalSompi - changeAmountSompi;
|
|
@@ -86,10 +103,18 @@ function verifyTxPlanSemantics(plan, context = {}) {
|
|
|
86
103
|
addIssue("ZERO_INPUTS", "critical", "Transaction has zero or negative total inputs.");
|
|
87
104
|
}
|
|
88
105
|
if (outputTotalSompi <= 0n) {
|
|
89
|
-
addIssue(
|
|
106
|
+
addIssue(
|
|
107
|
+
"ZERO_OUTPUTS",
|
|
108
|
+
"error",
|
|
109
|
+
"Transaction has zero or negative total outputs (excluding change)."
|
|
110
|
+
);
|
|
90
111
|
}
|
|
91
112
|
if (recomputedFeeSompi < 0n) {
|
|
92
|
-
addIssue(
|
|
113
|
+
addIssue(
|
|
114
|
+
"NEGATIVE_FEE",
|
|
115
|
+
"critical",
|
|
116
|
+
`Negative fee detected: inputs (${inputTotalSompi}) < outputs + change (${outputTotalSompi + changeAmountSompi})`
|
|
117
|
+
);
|
|
93
118
|
}
|
|
94
119
|
const massResult = estimateTransactionMass({
|
|
95
120
|
inputCount: plan.inputs.length,
|
|
@@ -98,27 +123,55 @@ function verifyTxPlanSemantics(plan, context = {}) {
|
|
|
98
123
|
});
|
|
99
124
|
const recomputedMass = massResult.mass;
|
|
100
125
|
if (recomputedMass !== BigInt(plan.estimatedMass)) {
|
|
101
|
-
addIssue(
|
|
126
|
+
addIssue(
|
|
127
|
+
"MASS_MISMATCH",
|
|
128
|
+
"critical",
|
|
129
|
+
`Mass mismatch: plan says ${plan.estimatedMass}, recomputed ${recomputedMass}`
|
|
130
|
+
);
|
|
102
131
|
}
|
|
103
132
|
if (planFeeSompi !== recomputedFeeSompi) {
|
|
104
|
-
addIssue(
|
|
133
|
+
addIssue(
|
|
134
|
+
"FEE_MISMATCH",
|
|
135
|
+
"critical",
|
|
136
|
+
`Fee mismatch: estimatedFeeSompi (${planFeeSompi}) does not match input-output delta (${recomputedFeeSompi})`
|
|
137
|
+
);
|
|
105
138
|
}
|
|
106
139
|
plan.outputs.forEach((o, i) => {
|
|
107
140
|
if (BigInt(o.amountSompi) <= 0n) {
|
|
108
|
-
addIssue(
|
|
141
|
+
addIssue(
|
|
142
|
+
"INVALID_OUTPUT_AMOUNT",
|
|
143
|
+
"error",
|
|
144
|
+
`Output ${i} has non-positive amount: ${o.amountSompi}`,
|
|
145
|
+
`outputs[${i}]`
|
|
146
|
+
);
|
|
109
147
|
}
|
|
110
148
|
if (BigInt(o.amountSompi) < 600n) {
|
|
111
|
-
addIssue(
|
|
149
|
+
addIssue(
|
|
150
|
+
"DUST_OUTPUT",
|
|
151
|
+
"warning",
|
|
152
|
+
`Output ${i} might be dust: ${o.amountSompi} sompi`,
|
|
153
|
+
`outputs[${i}]`
|
|
154
|
+
);
|
|
112
155
|
}
|
|
113
156
|
});
|
|
114
157
|
if (plan.change && BigInt(plan.change.amountSompi) < 600n) {
|
|
115
|
-
addIssue(
|
|
158
|
+
addIssue(
|
|
159
|
+
"DUST_CHANGE",
|
|
160
|
+
"warning",
|
|
161
|
+
`Change output might be dust: ${plan.change.amountSompi} sompi`,
|
|
162
|
+
"change"
|
|
163
|
+
);
|
|
116
164
|
}
|
|
117
165
|
const seenInputs = /* @__PURE__ */ new Set();
|
|
118
166
|
plan.inputs.forEach((input, i) => {
|
|
119
167
|
const id = `${input.outpoint.transactionId}:${input.outpoint.index}`;
|
|
120
168
|
if (seenInputs.has(id)) {
|
|
121
|
-
addIssue(
|
|
169
|
+
addIssue(
|
|
170
|
+
"DUPLICATE_INPUT",
|
|
171
|
+
"critical",
|
|
172
|
+
`Duplicate input detected: ${id}`,
|
|
173
|
+
`inputs[${i}]`
|
|
174
|
+
);
|
|
122
175
|
}
|
|
123
176
|
seenInputs.add(id);
|
|
124
177
|
});
|
|
@@ -128,15 +181,30 @@ function verifyTxPlanSemantics(plan, context = {}) {
|
|
|
128
181
|
(u) => u.outpoint.transactionId === input.outpoint.transactionId && u.outpoint.index === input.outpoint.index
|
|
129
182
|
);
|
|
130
183
|
if (!match) {
|
|
131
|
-
addIssue(
|
|
184
|
+
addIssue(
|
|
185
|
+
"UNKNOWN_INPUT",
|
|
186
|
+
"error",
|
|
187
|
+
`Input ${i} not found in provided UTXO context`,
|
|
188
|
+
`inputs[${i}]`
|
|
189
|
+
);
|
|
132
190
|
} else if (BigInt(match.amountSompi) !== BigInt(input.amountSompi)) {
|
|
133
|
-
addIssue(
|
|
191
|
+
addIssue(
|
|
192
|
+
"INPUT_AMOUNT_MISMATCH",
|
|
193
|
+
"critical",
|
|
194
|
+
`Input ${i} amount mismatch: plan says ${input.amountSompi}, context says ${match.amountSompi}`,
|
|
195
|
+
`inputs[${i}]`
|
|
196
|
+
);
|
|
134
197
|
}
|
|
135
198
|
});
|
|
136
199
|
}
|
|
137
200
|
if (context.expectedChangeAddress && plan.change) {
|
|
138
201
|
if (plan.change.address !== context.expectedChangeAddress) {
|
|
139
|
-
addIssue(
|
|
202
|
+
addIssue(
|
|
203
|
+
"CHANGE_ADDRESS_MISMATCH",
|
|
204
|
+
"error",
|
|
205
|
+
`Change address mismatch: expected ${context.expectedChangeAddress}, got ${plan.change.address}`,
|
|
206
|
+
"change.address"
|
|
207
|
+
);
|
|
140
208
|
}
|
|
141
209
|
}
|
|
142
210
|
return {
|
|
@@ -157,16 +225,32 @@ function verifySignedTxSemantics(signed, plan) {
|
|
|
157
225
|
if (plan) {
|
|
158
226
|
const ePlan = plan;
|
|
159
227
|
if (signed.sourcePlanId !== ePlan.planId && signed.sourcePlanId !== ePlan.contentHash) {
|
|
160
|
-
addIssue(
|
|
228
|
+
addIssue(
|
|
229
|
+
"PLAN_ID_MISMATCH",
|
|
230
|
+
"critical",
|
|
231
|
+
`Security violation: sourcePlanId mismatch. Signed sourcePlanId is ${signed.sourcePlanId}, but plan is ${ePlan.planId}`
|
|
232
|
+
);
|
|
161
233
|
}
|
|
162
234
|
if (ePlan.amountSompi && BigInt(signed.amountSompi) !== BigInt(ePlan.amountSompi)) {
|
|
163
|
-
addIssue(
|
|
235
|
+
addIssue(
|
|
236
|
+
"IMMUTABLE_FIELD_MUTATION",
|
|
237
|
+
"critical",
|
|
238
|
+
`Security violation: amountSompi changed from ${ePlan.amountSompi} to ${signed.amountSompi} after signing`
|
|
239
|
+
);
|
|
164
240
|
}
|
|
165
241
|
if (signed.networkId !== ePlan.networkId) {
|
|
166
|
-
addIssue(
|
|
242
|
+
addIssue(
|
|
243
|
+
"NETWORK_MISMATCH",
|
|
244
|
+
"critical",
|
|
245
|
+
`Security violation: networkId changed from ${ePlan.networkId} to ${signed.networkId} after signing`
|
|
246
|
+
);
|
|
167
247
|
}
|
|
168
248
|
} else {
|
|
169
|
-
addIssue(
|
|
249
|
+
addIssue(
|
|
250
|
+
"PLAN_UNAVAILABLE_FOR_LINEAGE_CHECK",
|
|
251
|
+
"warning",
|
|
252
|
+
"Source plan is not available in the workspace; skipping lineage check"
|
|
253
|
+
);
|
|
170
254
|
}
|
|
171
255
|
if (!signed.signedTransaction?.payload) {
|
|
172
256
|
addIssue("MISSING_PAYLOAD", "error", "Signed transaction is missing its raw payload");
|
|
@@ -205,10 +289,7 @@ function buildPaymentPlan(request) {
|
|
|
205
289
|
if (a.address > b.address) return 1;
|
|
206
290
|
return 0;
|
|
207
291
|
});
|
|
208
|
-
const target = sortedOutputs.reduce(
|
|
209
|
-
(sum, output) => sum + output.amountSompi,
|
|
210
|
-
0n
|
|
211
|
-
);
|
|
292
|
+
const target = sortedOutputs.reduce((sum, output) => sum + output.amountSompi, 0n);
|
|
212
293
|
if (target <= 0n) {
|
|
213
294
|
throw new Error("Transaction amount must be positive.");
|
|
214
295
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hardkas/tx-builder",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.4-alpha",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
".": "./dist/index.js"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@hardkas/core": "0.7.
|
|
11
|
+
"@hardkas/core": "0.7.4-alpha"
|
|
12
12
|
},
|
|
13
13
|
"devDependencies": {
|
|
14
14
|
"tsup": "^8.3.5",
|