@buildonspark/spark-sdk 0.3.8 → 0.4.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/CHANGELOG.md +18 -0
- package/android/build.gradle +1 -1
- package/android/src/main/java/uniffi/uniffi/spark_frost/spark_frost.kt +1361 -1367
- package/android/src/main/jniLibs/arm64-v8a/libuniffi_spark_frost.so +0 -0
- package/android/src/main/jniLibs/armeabi-v7a/libuniffi_spark_frost.so +0 -0
- package/android/src/main/jniLibs/x86/libuniffi_spark_frost.so +0 -0
- package/android/src/main/jniLibs/x86_64/libuniffi_spark_frost.so +0 -0
- package/dist/bare/index.cjs +1342 -1346
- package/dist/bare/index.d.cts +114 -68
- package/dist/bare/index.d.ts +114 -68
- package/dist/bare/index.js +1266 -1279
- package/dist/{chunk-FXIESWE6.js → chunk-27ILUWDJ.js} +1 -1
- package/dist/{chunk-5ASXVNTM.js → chunk-4YFT7DAE.js} +1 -1
- package/dist/{chunk-FP2CRVQH.js → chunk-G3LHXHF3.js} +1045 -1308
- package/dist/{chunk-XI6FCNYG.js → chunk-JLF6WJ7K.js} +1 -1
- package/dist/{chunk-3LPEQGVJ.js → chunk-LOXWCMZL.js} +1 -1
- package/dist/{chunk-5HU5W56H.js → chunk-WICAF6BS.js} +2 -2
- package/dist/{chunk-VFN34EOX.js → chunk-YEBEN7XD.js} +258 -0
- package/dist/{client-pNpGP15j.d.cts → client-BIqiUNy4.d.cts} +1 -1
- package/dist/{client-By-N7oJS.d.ts → client-BaQf-5gD.d.ts} +1 -1
- package/dist/debug.cjs +1330 -1336
- package/dist/debug.d.cts +20 -16
- package/dist/debug.d.ts +20 -16
- package/dist/debug.js +5 -5
- package/dist/graphql/objects/index.d.cts +3 -3
- package/dist/graphql/objects/index.d.ts +3 -3
- package/dist/index.cjs +1363 -1365
- package/dist/index.d.cts +7 -7
- package/dist/index.d.ts +7 -7
- package/dist/index.js +32 -24
- package/dist/index.node.cjs +1363 -1365
- package/dist/index.node.d.cts +7 -7
- package/dist/index.node.d.ts +7 -7
- package/dist/index.node.js +31 -23
- package/dist/{logging-DMFVY384.d.ts → logging-BNGm6dBp.d.ts} +42 -37
- package/dist/{logging-DxLp34Xm.d.cts → logging-D3IfXfHG.d.cts} +42 -37
- package/dist/native/index.react-native.cjs +1505 -1366
- package/dist/native/index.react-native.d.cts +112 -58
- package/dist/native/index.react-native.d.ts +112 -58
- package/dist/native/index.react-native.js +1415 -1289
- package/dist/proto/spark.cjs +258 -0
- package/dist/proto/spark.d.cts +1 -1
- package/dist/proto/spark.d.ts +1 -1
- package/dist/proto/spark.js +5 -1
- package/dist/proto/spark_token.d.cts +1 -1
- package/dist/proto/spark_token.d.ts +1 -1
- package/dist/proto/spark_token.js +2 -2
- package/dist/{spark-By6yHsrk.d.cts → spark-DOpheE8_.d.cts} +39 -2
- package/dist/{spark-By6yHsrk.d.ts → spark-DOpheE8_.d.ts} +39 -2
- package/dist/{spark-wallet.browser-C1dQknVj.d.ts → spark-wallet.browser-B2rGwjuM.d.ts} +2 -2
- package/dist/{spark-wallet.browser-CNMo3IvO.d.cts → spark-wallet.browser-Ck9No4Ks.d.cts} +2 -2
- package/dist/{spark-wallet.node-Og6__NMh.d.ts → spark-wallet.node-BqmKsGPs.d.ts} +2 -2
- package/dist/{spark-wallet.node-BZJhJZKq.d.cts → spark-wallet.node-C2TIkyt4.d.cts} +2 -2
- package/dist/tests/test-utils.cjs +1304 -1236
- package/dist/tests/test-utils.d.cts +18 -4
- package/dist/tests/test-utils.d.ts +18 -4
- package/dist/tests/test-utils.js +7 -7
- package/dist/{token-transactions-CLR3rnYi.d.cts → token-transactions-Db8mkjnU.d.cts} +2 -2
- package/dist/{token-transactions-C7yefB2S.d.ts → token-transactions-DoMcrxXQ.d.ts} +2 -2
- package/dist/types/index.cjs +256 -0
- package/dist/types/index.d.cts +2 -2
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.js +2 -2
- package/dist/{wallet-config-BoyMVa6n.d.ts → wallet-config-Bg3kWltL.d.ts} +42 -35
- package/dist/{wallet-config-xom-9UFF.d.cts → wallet-config-CuZKNo9S.d.cts} +42 -35
- package/ios/spark_frostFFI.xcframework/ios-arm64/SparkFrost +0 -0
- package/ios/spark_frostFFI.xcframework/ios-arm64/spark_frostFFI.framework/spark_frostFFI +0 -0
- package/ios/spark_frostFFI.xcframework/ios-arm64_x86_64-simulator/SparkFrost +0 -0
- package/ios/spark_frostFFI.xcframework/ios-arm64_x86_64-simulator/spark_frostFFI.framework/spark_frostFFI +0 -0
- package/ios/spark_frostFFI.xcframework/macos-arm64_x86_64/spark_frostFFI.framework/spark_frostFFI +0 -0
- package/package.json +1 -1
- package/src/index.react-native.ts +8 -2
- package/src/proto/spark.ts +348 -4
- package/src/services/config.ts +5 -0
- package/src/services/coop-exit.ts +26 -107
- package/src/services/deposit.ts +12 -48
- package/src/services/signing.ts +62 -49
- package/src/services/transfer.ts +437 -722
- package/src/services/wallet-config.ts +10 -0
- package/src/services/xhr-transport.ts +13 -3
- package/src/signer/signer.react-native.ts +73 -1
- package/src/spark-wallet/proto-descriptors.ts +1 -1
- package/src/spark-wallet/spark-wallet.ts +162 -315
- package/src/spark-wallet/types.ts +2 -2
- package/src/spark_descriptors.pb +0 -0
- package/src/tests/integration/lightning.test.ts +1 -27
- package/src/tests/integration/static_deposit.test.ts +6 -9
- package/src/tests/integration/unilateral-exit.test.ts +117 -0
- package/src/tests/optimize.test.ts +31 -1
- package/src/tests/utils/signing.ts +33 -0
- package/src/tests/utils/test-faucet.ts +61 -0
- package/src/utils/optimize.ts +42 -0
- package/src/utils/transaction.ts +250 -249
- package/src/utils/unilateral-exit.ts +1 -40
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isObject, mapCurrencyAmount } from "@lightsparkdev/core";
|
|
2
2
|
import { secp256k1 } from "@noble/curves/secp256k1";
|
|
3
3
|
import {
|
|
4
4
|
bytesToHex,
|
|
@@ -46,7 +46,6 @@ import {
|
|
|
46
46
|
SigningJob,
|
|
47
47
|
SubscribeToEventsResponse,
|
|
48
48
|
Transfer,
|
|
49
|
-
TransferEvent,
|
|
50
49
|
TransferStatus,
|
|
51
50
|
TransferType,
|
|
52
51
|
TreeNode,
|
|
@@ -87,10 +86,7 @@ import {
|
|
|
87
86
|
NetworkType,
|
|
88
87
|
} from "../utils/network.js";
|
|
89
88
|
import { sumAvailableTokens } from "../utils/token-transactions.js";
|
|
90
|
-
import {
|
|
91
|
-
doesLeafNeedRefresh,
|
|
92
|
-
getCurrentTimelock,
|
|
93
|
-
} from "../utils/transaction.js";
|
|
89
|
+
import { doesTxnNeedRenewed, isZeroTimelock } from "../utils/transaction.js";
|
|
94
90
|
|
|
95
91
|
import { sha256 } from "@noble/hashes/sha2";
|
|
96
92
|
import { trace, Tracer } from "@opentelemetry/api";
|
|
@@ -128,7 +124,6 @@ import {
|
|
|
128
124
|
import { chunkArray } from "../utils/chunkArray.js";
|
|
129
125
|
import { getFetch } from "../utils/fetch.js";
|
|
130
126
|
import { addPublicKeys } from "../utils/keys.js";
|
|
131
|
-
import { maximizeUnilateralExit } from "../utils/optimize.js";
|
|
132
127
|
import { RetryContext, withRetry } from "../utils/retry.js";
|
|
133
128
|
import {
|
|
134
129
|
Bech32mTokenIdentifier,
|
|
@@ -156,6 +151,7 @@ import type {
|
|
|
156
151
|
UserTokenMetadata,
|
|
157
152
|
} from "./types.js";
|
|
158
153
|
import { SparkWalletEvent } from "./types.js";
|
|
154
|
+
import { optimize, shouldOptimize } from "../utils/optimize.js";
|
|
159
155
|
|
|
160
156
|
/**
|
|
161
157
|
* The SparkWallet class is the primary interface for interacting with the Spark network.
|
|
@@ -303,16 +299,15 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
|
|
|
303
299
|
await this.claimTransfer({
|
|
304
300
|
transfer: event.transfer.transfer,
|
|
305
301
|
emit: true,
|
|
306
|
-
optimize: true,
|
|
307
302
|
});
|
|
308
303
|
}
|
|
309
304
|
} else if (isDepositStreamEvent(event)) {
|
|
310
305
|
const deposit = event.deposit.deposit;
|
|
311
|
-
|
|
312
|
-
await this.
|
|
313
|
-
|
|
314
|
-
path: deposit.id,
|
|
306
|
+
|
|
307
|
+
await this.withLeaves(async () => {
|
|
308
|
+
this.leaves.push(deposit);
|
|
315
309
|
});
|
|
310
|
+
|
|
316
311
|
this.emit(
|
|
317
312
|
SparkWalletEvent.DepositConfirmed,
|
|
318
313
|
deposit.id,
|
|
@@ -531,20 +526,6 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
|
|
|
531
526
|
.map(([_, node]) => node);
|
|
532
527
|
}
|
|
533
528
|
|
|
534
|
-
private async checkExtendLeaves(leaves: TreeNode[]): Promise<void> {
|
|
535
|
-
await this.withLeaves(async () => {
|
|
536
|
-
for (const leaf of leaves) {
|
|
537
|
-
if (!leaf.parentNodeId && leaf.status === "AVAILABLE") {
|
|
538
|
-
const res = await this.transferService.extendTimelock(leaf);
|
|
539
|
-
await this.transferLeavesToSelf(res.nodes, {
|
|
540
|
-
type: KeyDerivationType.LEAF,
|
|
541
|
-
path: leaf.id,
|
|
542
|
-
});
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
});
|
|
546
|
-
}
|
|
547
|
-
|
|
548
529
|
private verifyKey(
|
|
549
530
|
pubkey1: Uint8Array,
|
|
550
531
|
pubkey2: Uint8Array,
|
|
@@ -687,76 +668,101 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
|
|
|
687
668
|
return nodes;
|
|
688
669
|
}
|
|
689
670
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
671
|
+
public async *optimizeLeaves(
|
|
672
|
+
multiplicity: number | undefined = undefined,
|
|
673
|
+
): AsyncGenerator<
|
|
674
|
+
{
|
|
675
|
+
step: number;
|
|
676
|
+
total: number;
|
|
677
|
+
controller: AbortController;
|
|
678
|
+
},
|
|
679
|
+
void,
|
|
680
|
+
void
|
|
681
|
+
> {
|
|
682
|
+
const multiplicityValue =
|
|
683
|
+
multiplicity ?? this.config.getOptimizationOptions().multiplicity ?? 0;
|
|
684
|
+
if (multiplicityValue < 0) {
|
|
685
|
+
throw new ValidationError("Multiplicity cannot be negative");
|
|
686
|
+
} else if (multiplicityValue > 5) {
|
|
687
|
+
throw new ValidationError("Multiplicity cannot be greater than 5");
|
|
695
688
|
}
|
|
696
689
|
|
|
697
|
-
|
|
690
|
+
if (
|
|
691
|
+
this.optimizationInProgress ||
|
|
692
|
+
!shouldOptimize(
|
|
693
|
+
this.leaves.map((leaf) => leaf.value),
|
|
694
|
+
multiplicityValue,
|
|
695
|
+
)
|
|
696
|
+
) {
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
698
699
|
|
|
699
|
-
|
|
700
|
-
|
|
700
|
+
const controller = new AbortController();
|
|
701
|
+
const release = await this.leavesMutex.acquire();
|
|
702
|
+
try {
|
|
703
|
+
this.optimizationInProgress = true;
|
|
701
704
|
|
|
702
|
-
|
|
703
|
-
const
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
705
|
+
this.leaves = await this.getLeaves();
|
|
706
|
+
const swaps = optimize(
|
|
707
|
+
this.leaves.map((leaf) => leaf.value),
|
|
708
|
+
multiplicityValue,
|
|
709
|
+
);
|
|
710
|
+
if (swaps.length === 0) {
|
|
711
|
+
return;
|
|
707
712
|
}
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
return this.leaves.length > optimalLeavesLength * 5;
|
|
711
|
-
}
|
|
712
713
|
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
714
|
+
yield {
|
|
715
|
+
step: 0,
|
|
716
|
+
total: swaps.length,
|
|
717
|
+
controller,
|
|
718
|
+
};
|
|
717
719
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
);
|
|
720
|
+
// Build a map from the denomination to the indices
|
|
721
|
+
const valueToNodes = new Map<number, TreeNode[]>();
|
|
722
|
+
this.leaves.forEach((leaf) => {
|
|
723
|
+
if (!valueToNodes.has(leaf.value)) {
|
|
724
|
+
valueToNodes.set(leaf.value, []);
|
|
725
|
+
}
|
|
726
|
+
valueToNodes.get(leaf.value)!.push(leaf);
|
|
727
|
+
});
|
|
725
728
|
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
}
|
|
732
|
-
valueToNodes.get(leaf.value)!.push(leaf);
|
|
733
|
-
});
|
|
729
|
+
// Select the leaves to send for each swap.
|
|
730
|
+
for (const swap of swaps) {
|
|
731
|
+
if (controller.signal.aborted) {
|
|
732
|
+
break;
|
|
733
|
+
}
|
|
734
734
|
|
|
735
|
-
|
|
736
|
-
for (const
|
|
737
|
-
const
|
|
738
|
-
|
|
739
|
-
const
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
`No unused leaf with value ${leafValue} found in leaves`,
|
|
746
|
-
);
|
|
747
|
-
}
|
|
735
|
+
const leavesToSend: TreeNode[] = [];
|
|
736
|
+
for (const leafValue of swap.inLeaves) {
|
|
737
|
+
const nodes = valueToNodes.get(leafValue);
|
|
738
|
+
if (nodes && nodes.length > 0) {
|
|
739
|
+
const node = nodes.shift()!;
|
|
740
|
+
leavesToSend.push(node);
|
|
741
|
+
} else {
|
|
742
|
+
throw new InternalValidationError(
|
|
743
|
+
`No unused leaf with value ${leafValue} found in leaves`,
|
|
744
|
+
);
|
|
748
745
|
}
|
|
749
|
-
await this.requestLeavesSwap({
|
|
750
|
-
leaves: leavesToSend,
|
|
751
|
-
targetAmounts: swap.outLeaves,
|
|
752
|
-
});
|
|
753
746
|
}
|
|
754
747
|
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
748
|
+
// TODO: Parallelize this.
|
|
749
|
+
await this.requestLeavesSwap({
|
|
750
|
+
leaves: leavesToSend,
|
|
751
|
+
targetAmounts: swap.outLeaves,
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
yield {
|
|
755
|
+
step: swaps.indexOf(swap) + 1,
|
|
756
|
+
total: swaps.length,
|
|
757
|
+
controller,
|
|
758
|
+
};
|
|
758
759
|
}
|
|
759
|
-
|
|
760
|
+
|
|
761
|
+
this.leaves = await this.getLeaves();
|
|
762
|
+
} finally {
|
|
763
|
+
this.optimizationInProgress = false;
|
|
764
|
+
release();
|
|
765
|
+
}
|
|
760
766
|
}
|
|
761
767
|
|
|
762
768
|
private async syncWallet() {
|
|
@@ -764,15 +770,15 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
|
|
|
764
770
|
|
|
765
771
|
let leaves = await this.getLeaves();
|
|
766
772
|
|
|
767
|
-
leaves = await this.
|
|
768
|
-
leaves = await this.checkExtendTimeLockNodes(leaves);
|
|
773
|
+
leaves = await this.checkRenewLeaves(leaves);
|
|
769
774
|
|
|
770
775
|
this.leaves = leaves;
|
|
771
|
-
this.checkExtendLeaves(leaves);
|
|
772
776
|
|
|
773
|
-
this.
|
|
774
|
-
|
|
775
|
-
|
|
777
|
+
if (this.config.getOptimizationOptions().auto) {
|
|
778
|
+
for await (const _ of this.optimizeLeaves()) {
|
|
779
|
+
// run all optimizer steps, do nothing with them
|
|
780
|
+
}
|
|
781
|
+
}
|
|
776
782
|
}
|
|
777
783
|
|
|
778
784
|
private async withLeaves<T>(operation: () => Promise<T>): Promise<T> {
|
|
@@ -1473,7 +1479,6 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
|
|
|
1473
1479
|
return await this.claimTransfer({
|
|
1474
1480
|
transfer: incomingTransfer,
|
|
1475
1481
|
emit: false,
|
|
1476
|
-
optimize: false,
|
|
1477
1482
|
});
|
|
1478
1483
|
} catch (e) {
|
|
1479
1484
|
console.error("[processSwapBatch] Error details:", {
|
|
@@ -2333,29 +2338,7 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
|
|
|
2333
2338
|
depositTx,
|
|
2334
2339
|
vout,
|
|
2335
2340
|
});
|
|
2336
|
-
|
|
2337
|
-
const resultingNodes: TreeNode[] = [];
|
|
2338
|
-
for (const node of res.nodes) {
|
|
2339
|
-
if (node.status === "AVAILABLE") {
|
|
2340
|
-
const { nodes } = await this.transferService.extendTimelock(node);
|
|
2341
|
-
|
|
2342
|
-
for (const n of nodes) {
|
|
2343
|
-
if (n.status === "AVAILABLE") {
|
|
2344
|
-
const transfer = await this.transferLeavesToSelf([n], {
|
|
2345
|
-
type: KeyDerivationType.LEAF,
|
|
2346
|
-
path: node.id,
|
|
2347
|
-
});
|
|
2348
|
-
resultingNodes.push(...transfer);
|
|
2349
|
-
} else {
|
|
2350
|
-
resultingNodes.push(n);
|
|
2351
|
-
}
|
|
2352
|
-
}
|
|
2353
|
-
} else {
|
|
2354
|
-
resultingNodes.push(node);
|
|
2355
|
-
}
|
|
2356
|
-
}
|
|
2357
|
-
|
|
2358
|
-
return resultingNodes;
|
|
2341
|
+
return res.nodes;
|
|
2359
2342
|
}
|
|
2360
2343
|
|
|
2361
2344
|
/**
|
|
@@ -2528,6 +2511,10 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
|
|
|
2528
2511
|
vout,
|
|
2529
2512
|
});
|
|
2530
2513
|
|
|
2514
|
+
await this.withLeaves(async () => {
|
|
2515
|
+
this.leaves.push(...nodes);
|
|
2516
|
+
});
|
|
2517
|
+
|
|
2531
2518
|
return nodes;
|
|
2532
2519
|
});
|
|
2533
2520
|
|
|
@@ -2637,6 +2624,7 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
|
|
|
2637
2624
|
: await this.claimTransfer({ transfer: pendingTransfer });
|
|
2638
2625
|
|
|
2639
2626
|
const leavesToRemove = new Set(leaves.map((leaf) => leaf.id));
|
|
2627
|
+
|
|
2640
2628
|
this.leaves = [
|
|
2641
2629
|
...this.leaves.filter((leaf) => !leavesToRemove.has(leaf.id)),
|
|
2642
2630
|
...resultNodes,
|
|
@@ -2735,8 +2723,7 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
|
|
|
2735
2723
|
`TreeNode group at index ${groupIndex} not found for amount ${amount} after selection`,
|
|
2736
2724
|
);
|
|
2737
2725
|
}
|
|
2738
|
-
|
|
2739
|
-
available = await this.checkExtendTimeLockNodes(available);
|
|
2726
|
+
const available = await this.checkRenewLeaves(group);
|
|
2740
2727
|
|
|
2741
2728
|
if (available.length < group.length) {
|
|
2742
2729
|
throw new Error(
|
|
@@ -2787,7 +2774,7 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
|
|
|
2787
2774
|
transfer.id,
|
|
2788
2775
|
);
|
|
2789
2776
|
if (pending) {
|
|
2790
|
-
await this.claimTransfer({ transfer: pending
|
|
2777
|
+
await this.claimTransfer({ transfer: pending });
|
|
2791
2778
|
}
|
|
2792
2779
|
}
|
|
2793
2780
|
return {
|
|
@@ -2842,87 +2829,55 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
|
|
|
2842
2829
|
};
|
|
2843
2830
|
}
|
|
2844
2831
|
|
|
2845
|
-
private async
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
const
|
|
2832
|
+
private async checkRenewLeaves(nodes: TreeNode[]): Promise<TreeNode[]> {
|
|
2833
|
+
const nodesToRenewNode: TreeNode[] = [];
|
|
2834
|
+
const nodesToRenewRefund: TreeNode[] = [];
|
|
2835
|
+
const nodesToRenewZeroTimelock: TreeNode[] = [];
|
|
2836
|
+
|
|
2849
2837
|
const nodeIds: string[] = [];
|
|
2850
2838
|
const validNodes: TreeNode[] = [];
|
|
2851
2839
|
|
|
2852
2840
|
for (const node of nodes) {
|
|
2853
2841
|
const nodeTx = getTxFromRawTxBytes(node.nodeTx);
|
|
2854
|
-
const
|
|
2855
|
-
|
|
2842
|
+
const refundTx = getTxFromRawTxBytes(node.refundTx);
|
|
2843
|
+
|
|
2844
|
+
const nodeSequence = nodeTx.getInput(0).sequence;
|
|
2845
|
+
const refundSequence = refundTx.getInput(0).sequence;
|
|
2846
|
+
|
|
2847
|
+
if (nodeSequence === undefined) {
|
|
2856
2848
|
throw new ValidationError("Invalid node transaction", {
|
|
2857
2849
|
field: "sequence",
|
|
2858
2850
|
value: nodeTx.getInput(0),
|
|
2859
2851
|
expected: "Non-null sequence",
|
|
2860
2852
|
});
|
|
2861
2853
|
}
|
|
2862
|
-
|
|
2863
|
-
if (needsRefresh) {
|
|
2864
|
-
nodesToExtend.push(node);
|
|
2865
|
-
nodeIds.push(node.id);
|
|
2866
|
-
} else {
|
|
2867
|
-
validNodes.push(node);
|
|
2868
|
-
}
|
|
2869
|
-
}
|
|
2870
|
-
|
|
2871
|
-
if (nodesToExtend.length === 0) {
|
|
2872
|
-
return validNodes;
|
|
2873
|
-
}
|
|
2874
|
-
|
|
2875
|
-
const nodesToAdd: TreeNode[] = [];
|
|
2876
|
-
for (const node of nodesToExtend) {
|
|
2877
|
-
const { nodes } = await this.transferService.extendTimelock(node);
|
|
2878
|
-
this.leaves = this.leaves.filter((leaf) => leaf.id !== node.id);
|
|
2879
|
-
const newNodes = await this.transferLeavesToSelf(nodes, {
|
|
2880
|
-
type: KeyDerivationType.LEAF,
|
|
2881
|
-
path: node.id,
|
|
2882
|
-
});
|
|
2883
|
-
nodesToAdd.push(...newNodes);
|
|
2884
|
-
}
|
|
2885
|
-
|
|
2886
|
-
this.updateLeaves(nodeIds, nodesToAdd);
|
|
2887
|
-
validNodes.push(...nodesToAdd);
|
|
2888
|
-
|
|
2889
|
-
return validNodes;
|
|
2890
|
-
}
|
|
2891
|
-
|
|
2892
|
-
/**
|
|
2893
|
-
* Internal method to refresh timelock nodes.
|
|
2894
|
-
*
|
|
2895
|
-
* @param {string} nodeId - The optional ID of the node to refresh. If not provided, all nodes will be checked.
|
|
2896
|
-
* @returns {Promise<void>}
|
|
2897
|
-
* @private
|
|
2898
|
-
*/
|
|
2899
|
-
private async checkRefreshTimelockNodes(
|
|
2900
|
-
nodes: TreeNode[],
|
|
2901
|
-
): Promise<TreeNode[]> {
|
|
2902
|
-
const nodesToRefresh: TreeNode[] = [];
|
|
2903
|
-
const nodeIds: string[] = [];
|
|
2904
|
-
const validNodes: TreeNode[] = [];
|
|
2905
|
-
|
|
2906
|
-
for (const node of nodes) {
|
|
2907
|
-
const refundTx = getTxFromRawTxBytes(node.refundTx);
|
|
2908
|
-
const sequence = refundTx.getInput(0).sequence;
|
|
2909
|
-
if (!sequence) {
|
|
2854
|
+
if (!refundSequence) {
|
|
2910
2855
|
throw new ValidationError("Invalid refund transaction", {
|
|
2911
2856
|
field: "sequence",
|
|
2912
2857
|
value: refundTx.getInput(0),
|
|
2913
2858
|
expected: "Non-null sequence",
|
|
2914
2859
|
});
|
|
2915
2860
|
}
|
|
2916
|
-
|
|
2917
|
-
if (
|
|
2918
|
-
|
|
2861
|
+
|
|
2862
|
+
if (doesTxnNeedRenewed(refundSequence)) {
|
|
2863
|
+
if (isZeroTimelock(nodeSequence)) {
|
|
2864
|
+
nodesToRenewZeroTimelock.push(node);
|
|
2865
|
+
} else if (doesTxnNeedRenewed(nodeSequence)) {
|
|
2866
|
+
nodesToRenewNode.push(node);
|
|
2867
|
+
} else {
|
|
2868
|
+
nodesToRenewRefund.push(node);
|
|
2869
|
+
}
|
|
2919
2870
|
nodeIds.push(node.id);
|
|
2920
2871
|
} else {
|
|
2921
2872
|
validNodes.push(node);
|
|
2922
2873
|
}
|
|
2923
2874
|
}
|
|
2924
2875
|
|
|
2925
|
-
if (
|
|
2876
|
+
if (
|
|
2877
|
+
nodesToRenewNode.length === 0 &&
|
|
2878
|
+
nodesToRenewRefund.length === 0 &&
|
|
2879
|
+
nodesToRenewZeroTimelock.length === 0
|
|
2880
|
+
) {
|
|
2926
2881
|
return validNodes;
|
|
2927
2882
|
}
|
|
2928
2883
|
|
|
@@ -2943,7 +2898,7 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
|
|
|
2943
2898
|
}
|
|
2944
2899
|
|
|
2945
2900
|
const nodesToAdd: TreeNode[] = [];
|
|
2946
|
-
for (const node of
|
|
2901
|
+
for (const node of nodesToRenewNode) {
|
|
2947
2902
|
if (!node.parentNodeId) {
|
|
2948
2903
|
throw new Error(`node ${node.id} has no parent`);
|
|
2949
2904
|
}
|
|
@@ -2953,20 +2908,29 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
|
|
|
2953
2908
|
throw new Error(`parent node ${node.parentNodeId} not found`);
|
|
2954
2909
|
}
|
|
2955
2910
|
|
|
2956
|
-
const
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
);
|
|
2911
|
+
const newNode = await this.transferService.renewNodeTxn(node, parentNode);
|
|
2912
|
+
nodesToAdd.push(newNode);
|
|
2913
|
+
}
|
|
2960
2914
|
|
|
2961
|
-
|
|
2962
|
-
|
|
2915
|
+
for (const node of nodesToRenewRefund) {
|
|
2916
|
+
if (!node.parentNodeId) {
|
|
2917
|
+
throw new Error(`node ${node.id} has no parent`);
|
|
2963
2918
|
}
|
|
2964
2919
|
|
|
2965
|
-
const
|
|
2966
|
-
if (!
|
|
2967
|
-
throw new Error(
|
|
2920
|
+
const parentNode = nodesMap.get(node.parentNodeId);
|
|
2921
|
+
if (!parentNode) {
|
|
2922
|
+
throw new Error(`parent node ${node.parentNodeId} not found`);
|
|
2968
2923
|
}
|
|
2969
2924
|
|
|
2925
|
+
const newNode = await this.transferService.renewRefundTxn(
|
|
2926
|
+
node,
|
|
2927
|
+
parentNode,
|
|
2928
|
+
);
|
|
2929
|
+
nodesToAdd.push(newNode);
|
|
2930
|
+
}
|
|
2931
|
+
|
|
2932
|
+
for (const node of nodesToRenewZeroTimelock) {
|
|
2933
|
+
const newNode = await this.transferService.renewZeroTimelockNodeTxn(node);
|
|
2970
2934
|
nodesToAdd.push(newNode);
|
|
2971
2935
|
}
|
|
2972
2936
|
|
|
@@ -3020,17 +2984,20 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
|
|
|
3020
2984
|
result: TreeNode[],
|
|
3021
2985
|
transfer: Transfer,
|
|
3022
2986
|
emit?: boolean,
|
|
3023
|
-
optimize?: boolean,
|
|
3024
2987
|
): Promise<TreeNode[]> {
|
|
3025
|
-
result = await this.
|
|
3026
|
-
result = await this.checkExtendTimeLockNodes(result);
|
|
2988
|
+
result = await this.checkRenewLeaves(result);
|
|
3027
2989
|
|
|
3028
2990
|
const existingIds = new Set(this.leaves.map((leaf) => leaf.id));
|
|
3029
2991
|
const uniqueResults = result.filter((node) => !existingIds.has(node.id));
|
|
3030
2992
|
this.leaves.push(...uniqueResults);
|
|
3031
2993
|
|
|
3032
|
-
if (
|
|
3033
|
-
|
|
2994
|
+
if (
|
|
2995
|
+
this.config.getOptimizationOptions().auto &&
|
|
2996
|
+
transfer.type !== TransferType.COUNTER_SWAP
|
|
2997
|
+
) {
|
|
2998
|
+
for await (const _ of this.optimizeLeaves()) {
|
|
2999
|
+
// run all optimizer steps, do nothing with them
|
|
3000
|
+
}
|
|
3034
3001
|
}
|
|
3035
3002
|
|
|
3036
3003
|
if (emit) {
|
|
@@ -3053,11 +3020,9 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
|
|
|
3053
3020
|
private async claimTransfer({
|
|
3054
3021
|
transfer,
|
|
3055
3022
|
emit,
|
|
3056
|
-
optimize,
|
|
3057
3023
|
}: {
|
|
3058
3024
|
transfer: Transfer;
|
|
3059
3025
|
emit?: boolean;
|
|
3060
|
-
optimize?: boolean;
|
|
3061
3026
|
}) {
|
|
3062
3027
|
const onError = async (
|
|
3063
3028
|
context: RetryContext<TreeNode[], Transfer>,
|
|
@@ -3115,12 +3080,7 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
|
|
|
3115
3080
|
return [];
|
|
3116
3081
|
}
|
|
3117
3082
|
|
|
3118
|
-
return await this.processClaimedTransferResults(
|
|
3119
|
-
result,
|
|
3120
|
-
transfer,
|
|
3121
|
-
emit,
|
|
3122
|
-
optimize,
|
|
3123
|
-
);
|
|
3083
|
+
return await this.processClaimedTransferResults(result, transfer, emit);
|
|
3124
3084
|
} catch (error) {
|
|
3125
3085
|
console.warn(
|
|
3126
3086
|
`Failed to claim transfer after all retries. Please try reinitializing your wallet in a few minutes. Transfer ID: ${transfer.id}`,
|
|
@@ -3169,7 +3129,7 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
|
|
|
3169
3129
|
continue;
|
|
3170
3130
|
}
|
|
3171
3131
|
promises.push(
|
|
3172
|
-
this.claimTransfer({ transfer, emit
|
|
3132
|
+
this.claimTransfer({ transfer, emit })
|
|
3173
3133
|
.then(() => transfer.id)
|
|
3174
3134
|
.catch((error) => {
|
|
3175
3135
|
console.warn(`Failed to claim transfer ${transfer.id}:`, error);
|
|
@@ -3509,8 +3469,7 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
|
|
|
3509
3469
|
selectedLeaves,
|
|
3510
3470
|
`no leaves for ${totalAmount}`,
|
|
3511
3471
|
);
|
|
3512
|
-
leaves = await this.
|
|
3513
|
-
leaves = await this.checkExtendTimeLockNodes(leaves);
|
|
3472
|
+
leaves = await this.checkRenewLeaves(leaves);
|
|
3514
3473
|
|
|
3515
3474
|
const leavesToSend: LeafKeyTweak[] = await Promise.all(
|
|
3516
3475
|
leaves.map(async (leaf) => ({
|
|
@@ -3914,10 +3873,8 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
|
|
|
3914
3873
|
leavesToSendToSE = leavesForFee;
|
|
3915
3874
|
}
|
|
3916
3875
|
|
|
3917
|
-
leavesToSendToSsp = await this.
|
|
3918
|
-
|
|
3919
|
-
leavesToSendToSE = await this.checkRefreshTimelockNodes(leavesToSendToSE);
|
|
3920
|
-
leavesToSendToSE = await this.checkExtendTimeLockNodes(leavesToSendToSE);
|
|
3876
|
+
leavesToSendToSsp = await this.checkRenewLeaves(leavesToSendToSsp);
|
|
3877
|
+
leavesToSendToSE = await this.checkRenewLeaves(leavesToSendToSE);
|
|
3921
3878
|
|
|
3922
3879
|
const leafKeyTweaks: LeafKeyTweak[] = await Promise.all(
|
|
3923
3880
|
[...leavesToSendToSE, ...leavesToSendToSsp].map(async (leaf) => ({
|
|
@@ -4024,8 +3981,7 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
|
|
|
4024
3981
|
`no leaves for ${amountSats}`,
|
|
4025
3982
|
);
|
|
4026
3983
|
|
|
4027
|
-
leaves = await this.
|
|
4028
|
-
leaves = await this.checkExtendTimeLockNodes(leaves);
|
|
3984
|
+
leaves = await this.checkRenewLeaves(leaves);
|
|
4029
3985
|
|
|
4030
3986
|
const feeEstimate = await sspClient.getCoopExitFeeQuote({
|
|
4031
3987
|
leafExternalIds: leaves.map((leaf) => leaf.id),
|
|
@@ -4661,16 +4617,6 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
|
|
|
4661
4617
|
);
|
|
4662
4618
|
}
|
|
4663
4619
|
|
|
4664
|
-
if (!nodeInput.sequence) {
|
|
4665
|
-
throw new ValidationError(
|
|
4666
|
-
`Node transaction has no sequence for ${isRootNode ? "root" : "non-root"} node`,
|
|
4667
|
-
{
|
|
4668
|
-
field: "sequence",
|
|
4669
|
-
value: nodeInput.sequence,
|
|
4670
|
-
},
|
|
4671
|
-
);
|
|
4672
|
-
}
|
|
4673
|
-
|
|
4674
4620
|
const refundInput = refundTx.getInput(0);
|
|
4675
4621
|
if (!refundInput) {
|
|
4676
4622
|
throw new ValidationError(
|
|
@@ -4711,104 +4657,6 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
|
|
|
4711
4657
|
}
|
|
4712
4658
|
}
|
|
4713
4659
|
|
|
4714
|
-
/**
|
|
4715
|
-
* Refresh the timelock of a specific node.
|
|
4716
|
-
*
|
|
4717
|
-
* @param {string} nodeId - The ID of the node to refresh
|
|
4718
|
-
* @returns {Promise<void>} Promise that resolves when the timelock is refreshed
|
|
4719
|
-
*/
|
|
4720
|
-
public async testOnly_expireTimelock(nodeId: string): Promise<void> {
|
|
4721
|
-
const sparkClient = await this.connectionManager.createSparkClient(
|
|
4722
|
-
this.config.getCoordinatorAddress(),
|
|
4723
|
-
);
|
|
4724
|
-
|
|
4725
|
-
try {
|
|
4726
|
-
// First, get the node and its parent
|
|
4727
|
-
const response = await sparkClient.query_nodes({
|
|
4728
|
-
source: {
|
|
4729
|
-
$case: "nodeIds",
|
|
4730
|
-
nodeIds: {
|
|
4731
|
-
nodeIds: [nodeId],
|
|
4732
|
-
},
|
|
4733
|
-
},
|
|
4734
|
-
includeParents: true,
|
|
4735
|
-
});
|
|
4736
|
-
|
|
4737
|
-
let leaf = response.nodes[nodeId];
|
|
4738
|
-
if (!leaf) {
|
|
4739
|
-
throw new ValidationError("Node not found", {
|
|
4740
|
-
field: "nodeId",
|
|
4741
|
-
value: nodeId,
|
|
4742
|
-
});
|
|
4743
|
-
}
|
|
4744
|
-
|
|
4745
|
-
let parentNode;
|
|
4746
|
-
let hasParentNode = false;
|
|
4747
|
-
if (!leaf.parentNodeId) {
|
|
4748
|
-
// skip node timelock check
|
|
4749
|
-
} else {
|
|
4750
|
-
hasParentNode = true;
|
|
4751
|
-
parentNode = response.nodes[leaf.parentNodeId];
|
|
4752
|
-
if (!parentNode) {
|
|
4753
|
-
throw new ValidationError("Parent node not found", {
|
|
4754
|
-
field: "parentNodeId",
|
|
4755
|
-
value: leaf.parentNodeId,
|
|
4756
|
-
});
|
|
4757
|
-
}
|
|
4758
|
-
}
|
|
4759
|
-
|
|
4760
|
-
const nodeTx = getTxFromRawTxBytes(leaf.nodeTx);
|
|
4761
|
-
const refundTx = getTxFromRawTxBytes(leaf.refundTx);
|
|
4762
|
-
|
|
4763
|
-
if (hasParentNode) {
|
|
4764
|
-
const nodeTimelock = getCurrentTimelock(nodeTx.getInput(0).sequence);
|
|
4765
|
-
if (nodeTimelock > 100) {
|
|
4766
|
-
const expiredNodeTxLeaf =
|
|
4767
|
-
await this.transferService.testonly_expireTimeLockNodeTx(
|
|
4768
|
-
leaf,
|
|
4769
|
-
parentNode,
|
|
4770
|
-
);
|
|
4771
|
-
|
|
4772
|
-
if (!expiredNodeTxLeaf.nodes[0]) {
|
|
4773
|
-
throw new ValidationError("No expired node tx leaf", {
|
|
4774
|
-
field: "expiredNodeTxLeaf",
|
|
4775
|
-
value: expiredNodeTxLeaf,
|
|
4776
|
-
});
|
|
4777
|
-
}
|
|
4778
|
-
leaf = expiredNodeTxLeaf.nodes[0];
|
|
4779
|
-
}
|
|
4780
|
-
}
|
|
4781
|
-
const refundTimelock = getCurrentTimelock(refundTx.getInput(0).sequence);
|
|
4782
|
-
|
|
4783
|
-
if (refundTimelock > 100) {
|
|
4784
|
-
const expiredTxLeaf =
|
|
4785
|
-
await this.transferService.testonly_expireTimeLockRefundtx(leaf);
|
|
4786
|
-
|
|
4787
|
-
if (!expiredTxLeaf.nodes[0]) {
|
|
4788
|
-
throw new ValidationError("No expired tx leaf", {
|
|
4789
|
-
field: "expiredTxLeaf",
|
|
4790
|
-
value: expiredTxLeaf,
|
|
4791
|
-
});
|
|
4792
|
-
}
|
|
4793
|
-
leaf = expiredTxLeaf.nodes[0];
|
|
4794
|
-
}
|
|
4795
|
-
|
|
4796
|
-
// Update the local leaves if this node is in our wallet
|
|
4797
|
-
const leafIndex = this.leaves.findIndex((leaf) => leaf.id === leaf.id);
|
|
4798
|
-
if (leafIndex !== -1) {
|
|
4799
|
-
this.leaves[leafIndex] = leaf;
|
|
4800
|
-
}
|
|
4801
|
-
} catch (error) {
|
|
4802
|
-
throw new NetworkError(
|
|
4803
|
-
"Failed to refresh timelock",
|
|
4804
|
-
{
|
|
4805
|
-
method: "refresh_timelock",
|
|
4806
|
-
},
|
|
4807
|
-
error as Error,
|
|
4808
|
-
);
|
|
4809
|
-
}
|
|
4810
|
-
}
|
|
4811
|
-
|
|
4812
4660
|
private cleanup() {
|
|
4813
4661
|
if (this.claimTransfersInterval) {
|
|
4814
4662
|
clearInterval(this.claimTransfersInterval);
|
|
@@ -5012,7 +4860,6 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
|
|
|
5012
4860
|
"getLightningSendRequest",
|
|
5013
4861
|
"getCoopExitRequest",
|
|
5014
4862
|
"checkTimelock",
|
|
5015
|
-
"testOnly_expireTimelock",
|
|
5016
4863
|
] as const;
|
|
5017
4864
|
|
|
5018
4865
|
methods.forEach((m) => this.wrapPublicSparkWalletMethodWithOtelSpan(m));
|