@hardkas/tx-builder 0.8.20-alpha → 0.9.1-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.d.ts +44 -1
- package/dist/index.js +134 -0
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -108,6 +108,49 @@ declare function verifyTxReceiptSemantics(receipt: any): {
|
|
|
108
108
|
issues: SemanticVerificationIssue[];
|
|
109
109
|
};
|
|
110
110
|
|
|
111
|
+
interface UtxoProvider {
|
|
112
|
+
getUtxos(address: string): Promise<Utxo[]>;
|
|
113
|
+
getVirtualDaaScore?(): Promise<bigint>;
|
|
114
|
+
}
|
|
115
|
+
interface TxPlanServiceOptions {
|
|
116
|
+
maxInputsPerTx?: number;
|
|
117
|
+
warnInputs?: number;
|
|
118
|
+
marginFeePerInput?: bigint;
|
|
119
|
+
coinbaseMaturity?: bigint;
|
|
120
|
+
}
|
|
121
|
+
interface PlanTransactionRequest {
|
|
122
|
+
fromAddress: string;
|
|
123
|
+
toAddress: string;
|
|
124
|
+
amountSompi: bigint;
|
|
125
|
+
feeRate?: bigint;
|
|
126
|
+
}
|
|
127
|
+
interface ConsolidationRequest {
|
|
128
|
+
fromAddress: string;
|
|
129
|
+
selectedUtxos: Utxo[];
|
|
130
|
+
toAddress: string;
|
|
131
|
+
feeRate?: bigint;
|
|
132
|
+
}
|
|
133
|
+
interface TxPlanResult {
|
|
134
|
+
plan: TxPlan;
|
|
135
|
+
utxoSelection: {
|
|
136
|
+
totalUtxosSeen: number;
|
|
137
|
+
selectedUtxos: number;
|
|
138
|
+
selectionStrategy: "largest-first" | "consolidation-smallest-first";
|
|
139
|
+
purpose?: "wallet-consolidation";
|
|
140
|
+
warnings?: string[];
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
declare class TxPlanService {
|
|
144
|
+
private provider;
|
|
145
|
+
readonly maxInputsPerTx: number;
|
|
146
|
+
readonly warnInputs: number;
|
|
147
|
+
readonly marginFeePerInput: bigint;
|
|
148
|
+
readonly coinbaseMaturity: bigint;
|
|
149
|
+
constructor(provider: UtxoProvider, options?: TxPlanServiceOptions);
|
|
150
|
+
planTransaction(request: PlanTransactionRequest): Promise<TxPlanResult>;
|
|
151
|
+
planConsolidation(request: ConsolidationRequest): Promise<TxPlanResult>;
|
|
152
|
+
}
|
|
153
|
+
|
|
111
154
|
type Sompi = bigint;
|
|
112
155
|
|
|
113
156
|
interface Outpoint {
|
|
@@ -155,4 +198,4 @@ declare function createMockUtxo(input: {
|
|
|
155
198
|
readonly index?: number;
|
|
156
199
|
}): Utxo;
|
|
157
200
|
|
|
158
|
-
export { DUST_THRESHOLD_SOMPI, KASPA_MASS_CONSTANTS, type MassBreakdown, type MassEstimateResult, type Outpoint, type SemanticVerificationIssue, type SemanticVerificationResult, type SemanticVerificationSeverity, type SemanticVerifyContext, type Sompi, type TxBuildRequest, type TxOutput, type TxPlan, type Utxo, buildPaymentPlan, createMockUtxo, estimateFeeFromMass, estimateMass, estimateTransactionMass, verifySignedTxSemantics, verifyTxPlanSemantics, verifyTxReceiptSemantics };
|
|
201
|
+
export { type ConsolidationRequest, DUST_THRESHOLD_SOMPI, KASPA_MASS_CONSTANTS, type MassBreakdown, type MassEstimateResult, type Outpoint, type PlanTransactionRequest, type SemanticVerificationIssue, type SemanticVerificationResult, type SemanticVerificationSeverity, type SemanticVerifyContext, type Sompi, type TxBuildRequest, type TxOutput, type TxPlan, type TxPlanResult, TxPlanService, type TxPlanServiceOptions, type Utxo, type UtxoProvider, buildPaymentPlan, createMockUtxo, estimateFeeFromMass, estimateMass, estimateTransactionMass, verifySignedTxSemantics, verifyTxPlanSemantics, verifyTxReceiptSemantics };
|
package/dist/index.js
CHANGED
|
@@ -278,6 +278,139 @@ function verifyTxReceiptSemantics(receipt) {
|
|
|
278
278
|
};
|
|
279
279
|
}
|
|
280
280
|
|
|
281
|
+
// src/service.ts
|
|
282
|
+
var TxPlanService = class {
|
|
283
|
+
constructor(provider, options = {}) {
|
|
284
|
+
this.provider = provider;
|
|
285
|
+
this.maxInputsPerTx = options.maxInputsPerTx ?? 512;
|
|
286
|
+
this.warnInputs = options.warnInputs ?? 128;
|
|
287
|
+
this.marginFeePerInput = options.marginFeePerInput ?? 1500n;
|
|
288
|
+
this.coinbaseMaturity = options.coinbaseMaturity ?? 1000n;
|
|
289
|
+
}
|
|
290
|
+
provider;
|
|
291
|
+
maxInputsPerTx;
|
|
292
|
+
warnInputs;
|
|
293
|
+
marginFeePerInput;
|
|
294
|
+
coinbaseMaturity;
|
|
295
|
+
async planTransaction(request) {
|
|
296
|
+
const rpcUtxos = await this.provider.getUtxos(request.fromAddress);
|
|
297
|
+
let virtualDaaScore;
|
|
298
|
+
if (this.provider.getVirtualDaaScore) {
|
|
299
|
+
try {
|
|
300
|
+
virtualDaaScore = await this.provider.getVirtualDaaScore();
|
|
301
|
+
} catch {
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
const matureUtxos = virtualDaaScore !== void 0 ? rpcUtxos.filter((u) => {
|
|
305
|
+
if (!u.isCoinbase) return true;
|
|
306
|
+
const score = u.blockDaaScore;
|
|
307
|
+
if (score === void 0) return true;
|
|
308
|
+
return virtualDaaScore - score >= this.coinbaseMaturity;
|
|
309
|
+
}) : rpcUtxos;
|
|
310
|
+
const allFetchedUtxos = matureUtxos;
|
|
311
|
+
const sortedUtxos = [...allFetchedUtxos].sort((a, b) => {
|
|
312
|
+
if (a.amountSompi > b.amountSompi) return -1;
|
|
313
|
+
if (a.amountSompi < b.amountSompi) return 1;
|
|
314
|
+
return 0;
|
|
315
|
+
});
|
|
316
|
+
const feeRate = request.feeRate ?? 1n;
|
|
317
|
+
const marginFee = this.marginFeePerInput * feeRate;
|
|
318
|
+
let selectedAmount = 0n;
|
|
319
|
+
let selectedInputsCount = 0;
|
|
320
|
+
const builderUtxos = [];
|
|
321
|
+
const HARD_LIMIT = 1e3;
|
|
322
|
+
for (const utxo of sortedUtxos) {
|
|
323
|
+
builderUtxos.push(utxo);
|
|
324
|
+
selectedAmount += utxo.amountSompi;
|
|
325
|
+
selectedInputsCount++;
|
|
326
|
+
const requiredTotal = request.amountSompi + BigInt(selectedInputsCount) * marginFee;
|
|
327
|
+
if (selectedAmount >= requiredTotal) {
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
if (selectedInputsCount >= HARD_LIMIT) {
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (selectedAmount < request.amountSompi) {
|
|
335
|
+
throw new Error(
|
|
336
|
+
`Insufficient funds: needed ${request.amountSompi} sompi but only found ${selectedAmount} sompi across ${selectedInputsCount} UTXOs.`
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
if (selectedInputsCount > this.maxInputsPerTx) {
|
|
340
|
+
const err = new Error(
|
|
341
|
+
`TOO_MANY_INPUTS_FOR_SINGLE_TX: Transaction requires ${selectedInputsCount} inputs to cover the amount, which exceeds the safe limit of ${this.maxInputsPerTx} inputs.
|
|
342
|
+
Hint: Run 'hardkas accounts consolidate' to merge dust UTXOs.`
|
|
343
|
+
);
|
|
344
|
+
err.code = "TOO_MANY_INPUTS_FOR_SINGLE_TX";
|
|
345
|
+
throw err;
|
|
346
|
+
}
|
|
347
|
+
const warnings = [];
|
|
348
|
+
if (selectedInputsCount >= this.warnInputs) {
|
|
349
|
+
warnings.push(
|
|
350
|
+
`Transaction requires ${selectedInputsCount} inputs. Consider running 'hardkas accounts consolidate'.`
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
const builderPlan = buildPaymentPlan({
|
|
354
|
+
fromAddress: request.fromAddress,
|
|
355
|
+
availableUtxos: builderUtxos,
|
|
356
|
+
outputs: [
|
|
357
|
+
{
|
|
358
|
+
address: request.toAddress,
|
|
359
|
+
amountSompi: request.amountSompi
|
|
360
|
+
}
|
|
361
|
+
],
|
|
362
|
+
feeRateSompiPerMass: feeRate
|
|
363
|
+
});
|
|
364
|
+
return {
|
|
365
|
+
plan: builderPlan,
|
|
366
|
+
utxoSelection: {
|
|
367
|
+
totalUtxosSeen: allFetchedUtxos.length,
|
|
368
|
+
selectedUtxos: selectedInputsCount,
|
|
369
|
+
selectionStrategy: "largest-first",
|
|
370
|
+
warnings
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
async planConsolidation(request) {
|
|
375
|
+
let totalAmount = 0n;
|
|
376
|
+
const builderUtxos = request.selectedUtxos.map((u) => {
|
|
377
|
+
const amount = BigInt(u.amountSompi);
|
|
378
|
+
totalAmount += amount;
|
|
379
|
+
return u;
|
|
380
|
+
});
|
|
381
|
+
const feeRate = request.feeRate ?? 1n;
|
|
382
|
+
const massPerInput = 1500n;
|
|
383
|
+
const estimatedMass = BigInt(request.selectedUtxos.length) * massPerInput + 500n;
|
|
384
|
+
const expectedFee = estimatedMass * feeRate;
|
|
385
|
+
if (totalAmount <= expectedFee) {
|
|
386
|
+
throw new Error(
|
|
387
|
+
`Consolidation failed: Total selected UTXO amount (${totalAmount}) is less than or equal to the estimated fee (${expectedFee}).`
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
const outputAmount = totalAmount - expectedFee;
|
|
391
|
+
const builderPlan = buildPaymentPlan({
|
|
392
|
+
fromAddress: request.fromAddress,
|
|
393
|
+
availableUtxos: builderUtxos,
|
|
394
|
+
outputs: [
|
|
395
|
+
{
|
|
396
|
+
address: request.toAddress,
|
|
397
|
+
amountSompi: outputAmount
|
|
398
|
+
}
|
|
399
|
+
],
|
|
400
|
+
feeRateSompiPerMass: feeRate
|
|
401
|
+
});
|
|
402
|
+
return {
|
|
403
|
+
plan: builderPlan,
|
|
404
|
+
utxoSelection: {
|
|
405
|
+
totalUtxosSeen: request.selectedUtxos.length,
|
|
406
|
+
selectedUtxos: request.selectedUtxos.length,
|
|
407
|
+
selectionStrategy: "consolidation-smallest-first",
|
|
408
|
+
purpose: "wallet-consolidation"
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
|
|
281
414
|
// src/index.ts
|
|
282
415
|
function buildPaymentPlan(request) {
|
|
283
416
|
if (request.outputs.length === 0) {
|
|
@@ -379,6 +512,7 @@ function createMockUtxo(input) {
|
|
|
379
512
|
export {
|
|
380
513
|
DUST_THRESHOLD_SOMPI,
|
|
381
514
|
KASPA_MASS_CONSTANTS,
|
|
515
|
+
TxPlanService,
|
|
382
516
|
buildPaymentPlan,
|
|
383
517
|
createMockUtxo,
|
|
384
518
|
estimateFeeFromMass,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hardkas/tx-builder",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1-alpha",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@hardkas/core": "0.
|
|
15
|
+
"@hardkas/core": "0.9.1-alpha"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"tsup": "^8.3.5",
|