@buildonspark/spark-sdk 0.2.2 → 0.2.4
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 +15 -0
- package/dist/{chunk-TM6CHQXC.js → chunk-3SEOTO43.js} +1 -1
- package/dist/{chunk-2ENZX6LT.js → chunk-AAZWSPUK.js} +84 -8
- package/dist/{chunk-4JD4HIAN.js → chunk-G4MSZ6DE.js} +299 -1
- package/dist/{chunk-S2AL73MZ.js → chunk-TVUMSHWA.js} +1 -1
- package/dist/{chunk-2TUM3R6C.js → chunk-W4ZRBSWM.js} +2351 -797
- package/dist/{chunk-CDLETEDT.js → chunk-WAQKYSDI.js} +13 -1
- package/dist/{client-CGTRS23n.d.ts → client-BF4cn8F4.d.ts} +15 -3
- package/dist/{client-CcYzmpmj.d.cts → client-KhNkrXz4.d.cts} +15 -3
- package/dist/debug.cjs +2948 -1023
- package/dist/debug.d.cts +19 -6
- package/dist/debug.d.ts +19 -6
- package/dist/debug.js +5 -5
- package/dist/graphql/objects/index.cjs +13 -1
- package/dist/graphql/objects/index.d.cts +2 -2
- package/dist/graphql/objects/index.d.ts +2 -2
- package/dist/graphql/objects/index.js +1 -1
- package/dist/index.cjs +2794 -858
- package/dist/index.d.cts +190 -9
- package/dist/index.d.ts +190 -9
- package/dist/index.js +32 -6
- package/dist/index.node.cjs +2931 -892
- package/dist/index.node.d.cts +10 -188
- package/dist/index.node.d.ts +10 -188
- package/dist/index.node.js +134 -6
- package/dist/native/index.cjs +2794 -858
- package/dist/native/index.d.cts +148 -40
- package/dist/native/index.d.ts +148 -40
- package/dist/native/index.js +2799 -877
- package/dist/proto/lrc20.d.cts +1 -1
- package/dist/proto/lrc20.d.ts +1 -1
- package/dist/proto/lrc20.js +1 -1
- package/dist/proto/spark.cjs +84 -8
- package/dist/proto/spark.d.cts +1 -1
- package/dist/proto/spark.d.ts +1 -1
- package/dist/proto/spark.js +1 -1
- package/dist/proto/spark_token.cjs +301 -0
- package/dist/proto/spark_token.d.cts +35 -2
- package/dist/proto/spark_token.d.ts +35 -2
- package/dist/proto/spark_token.js +8 -2
- package/dist/{sdk-types-DJ2ve9YY.d.cts → sdk-types-CB9HrW5O.d.cts} +1 -1
- package/dist/{sdk-types-DCIVdKUT.d.ts → sdk-types-CkRNraXT.d.ts} +1 -1
- package/dist/{spark-BUOx3U7Q.d.cts → spark-B_7nZx6T.d.cts} +112 -10
- package/dist/{spark-BUOx3U7Q.d.ts → spark-B_7nZx6T.d.ts} +112 -10
- package/dist/{spark-wallet-B_96y9BS.d.ts → spark-wallet-C1Tr_VKI.d.ts} +38 -28
- package/dist/{spark-wallet-CHwKQYJu.d.cts → spark-wallet-DG3x2obf.d.cts} +38 -28
- package/dist/spark-wallet.node-CGxoeCpH.d.ts +13 -0
- package/dist/spark-wallet.node-CN9LoB_O.d.cts +13 -0
- package/dist/tests/test-utils.cjs +1086 -218
- package/dist/tests/test-utils.d.cts +13 -13
- package/dist/tests/test-utils.d.ts +13 -13
- package/dist/tests/test-utils.js +56 -19
- package/dist/types/index.cjs +97 -9
- package/dist/types/index.d.cts +3 -3
- package/dist/types/index.d.ts +3 -3
- package/dist/types/index.js +3 -3
- package/dist/{xchain-address-D5MIHCDL.d.cts → xchain-address-BHu6CpZC.d.ts} +55 -8
- package/dist/{xchain-address-DLbW1iDh.d.ts → xchain-address-HBr6isnc.d.cts} +55 -8
- package/package.json +1 -1
- package/src/graphql/client.ts +8 -0
- package/src/graphql/mutations/CompleteLeavesSwap.ts +9 -1
- package/src/graphql/mutations/RequestSwapLeaves.ts +4 -0
- package/src/graphql/objects/CompleteLeavesSwapInput.ts +34 -34
- package/src/graphql/objects/LeavesSwapRequest.ts +4 -0
- package/src/graphql/objects/RequestLeavesSwapInput.ts +48 -47
- package/src/graphql/objects/SwapLeaf.ts +40 -32
- package/src/graphql/objects/UserLeafInput.ts +24 -0
- package/src/graphql/objects/UserRequest.ts +4 -0
- package/src/index.node.ts +1 -1
- package/src/native/index.ts +4 -5
- package/src/proto/spark.ts +172 -16
- package/src/proto/spark_token.ts +369 -0
- package/src/services/coop-exit.ts +171 -36
- package/src/services/deposit.ts +471 -74
- package/src/services/lightning.ts +18 -5
- package/src/services/signing.ts +162 -50
- package/src/services/token-transactions.ts +6 -2
- package/src/services/transfer.ts +950 -384
- package/src/services/tree-creation.ts +342 -121
- package/src/spark-wallet/spark-wallet.node.ts +71 -66
- package/src/spark-wallet/spark-wallet.ts +459 -166
- package/src/tests/integration/coop-exit.test.ts +3 -8
- package/src/tests/integration/deposit.test.ts +3 -3
- package/src/tests/integration/lightning.test.ts +521 -466
- package/src/tests/integration/swap.test.ts +559 -307
- package/src/tests/integration/transfer.test.ts +625 -623
- package/src/tests/integration/wallet.test.ts +2 -2
- package/src/tests/integration/watchtower.test.ts +211 -0
- package/src/tests/test-utils.ts +63 -14
- package/src/tests/utils/test-faucet.ts +4 -2
- package/src/utils/adaptor-signature.ts +15 -5
- package/src/utils/fetch.ts +75 -0
- package/src/utils/mempool.ts +9 -4
- package/src/utils/transaction.ts +388 -26
|
@@ -88,14 +88,16 @@ import {
|
|
|
88
88
|
NetworkType,
|
|
89
89
|
} from "../utils/network.js";
|
|
90
90
|
import { sumAvailableTokens } from "../utils/token-transactions.js";
|
|
91
|
-
import {
|
|
91
|
+
import {
|
|
92
|
+
doesLeafNeedRefresh,
|
|
93
|
+
getCurrentTimelock,
|
|
94
|
+
} from "../utils/transaction.js";
|
|
92
95
|
|
|
93
96
|
import { LRCWallet } from "@buildonspark/lrc20-sdk";
|
|
94
97
|
import { sha256 } from "@noble/hashes/sha2";
|
|
95
98
|
import { EventEmitter } from "eventemitter3";
|
|
96
99
|
import { isReactNative } from "../constants.js";
|
|
97
100
|
import { Network as NetworkProto, networkToJSON } from "../proto/spark.js";
|
|
98
|
-
import { TokenTransactionWithStatus } from "../proto/spark_token.js";
|
|
99
101
|
import {
|
|
100
102
|
decodeInvoice,
|
|
101
103
|
getNetworkFromInvoice,
|
|
@@ -136,6 +138,11 @@ import type {
|
|
|
136
138
|
TransferParams,
|
|
137
139
|
UserTokenMetadata,
|
|
138
140
|
} from "./types.js";
|
|
141
|
+
import {
|
|
142
|
+
TokenTransactionWithStatus,
|
|
143
|
+
TokenMetadata,
|
|
144
|
+
} from "../proto/spark_token.js";
|
|
145
|
+
import { getFetch } from "../utils/fetch.js";
|
|
139
146
|
|
|
140
147
|
/**
|
|
141
148
|
* The SparkWallet class is the primary interface for interacting with the Spark network.
|
|
@@ -439,37 +446,43 @@ export class SparkWallet extends EventEmitter {
|
|
|
439
446
|
}
|
|
440
447
|
|
|
441
448
|
public async getLeaves(isBalanceCheck: boolean = false): Promise<TreeNode[]> {
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
449
|
+
const operatorToLeaves = new Map<string, QueryNodesResponse>();
|
|
450
|
+
const ownerIdentityPubkey = await this.config.signer.getIdentityPublicKey();
|
|
451
|
+
|
|
452
|
+
let signingOperators = Object.entries(this.config.getSigningOperators());
|
|
453
|
+
if (isBalanceCheck) {
|
|
454
|
+
// If we're just checking the balance, we can just query the coordinator.
|
|
455
|
+
signingOperators = signingOperators.filter(
|
|
456
|
+
([id, _]) => id === this.config.getCoordinatorIdentifier(),
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
await Promise.all(
|
|
460
|
+
signingOperators.map(async ([id, operator]) => {
|
|
461
|
+
const leaves = await this.queryNodes(
|
|
462
|
+
{
|
|
463
|
+
source: {
|
|
464
|
+
$case: "ownerIdentityPubkey",
|
|
465
|
+
ownerIdentityPubkey,
|
|
466
|
+
},
|
|
467
|
+
includeParents: false,
|
|
468
|
+
network: NetworkToProto[this.config.getNetwork()],
|
|
469
|
+
},
|
|
470
|
+
operator.address,
|
|
471
|
+
);
|
|
472
|
+
operatorToLeaves.set(id, leaves);
|
|
473
|
+
}),
|
|
474
|
+
);
|
|
450
475
|
|
|
476
|
+
const leaves = operatorToLeaves.get(
|
|
477
|
+
this.config.getCoordinatorIdentifier(),
|
|
478
|
+
)!;
|
|
451
479
|
const leavesToIgnore: Set<string> = new Set();
|
|
452
|
-
// Query the leaf states from other operators.
|
|
453
|
-
// We'll ignore the leaves that are out of sync for now.
|
|
454
|
-
// Still include the leaves that are out of sync for balance check.
|
|
455
480
|
if (!isBalanceCheck) {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
481
|
+
// Query the leaf states from other operators.
|
|
482
|
+
// We'll ignore the leaves that are out of sync for now.
|
|
483
|
+
// Still include the leaves that are out of sync for balance check.
|
|
484
|
+
for (const [id, operatorLeaves] of operatorToLeaves) {
|
|
459
485
|
if (id !== this.config.getCoordinatorIdentifier()) {
|
|
460
|
-
const operatorLeaves = await this.queryNodes(
|
|
461
|
-
{
|
|
462
|
-
source: {
|
|
463
|
-
$case: "ownerIdentityPubkey",
|
|
464
|
-
ownerIdentityPubkey:
|
|
465
|
-
await this.config.signer.getIdentityPublicKey(),
|
|
466
|
-
},
|
|
467
|
-
includeParents: false,
|
|
468
|
-
network: NetworkToProto[this.config.getNetwork()],
|
|
469
|
-
},
|
|
470
|
-
operator.address,
|
|
471
|
-
);
|
|
472
|
-
|
|
473
486
|
// Loop over leaves returned by coordinator.
|
|
474
487
|
// If the leaf is not present in the operator's leaves, we'll ignore it.
|
|
475
488
|
// If the leaf is present, we'll check if the leaf is in sync with the operator's leaf.
|
|
@@ -499,17 +512,30 @@ export class SparkWallet extends EventEmitter {
|
|
|
499
512
|
}
|
|
500
513
|
}
|
|
501
514
|
|
|
502
|
-
const
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
verifyingKey: Uint8Array,
|
|
506
|
-
) => {
|
|
507
|
-
return equalBytes(addPublicKeys(pubkey1, pubkey2), verifyingKey);
|
|
508
|
-
};
|
|
515
|
+
const availableLeaves = Object.entries(leaves.nodes).filter(
|
|
516
|
+
([_, node]) => node.status === "AVAILABLE",
|
|
517
|
+
);
|
|
509
518
|
|
|
510
|
-
for (const [id, leaf] of
|
|
519
|
+
for (const [id, leaf] of availableLeaves) {
|
|
511
520
|
if (
|
|
512
|
-
|
|
521
|
+
leaf.parentNodeId &&
|
|
522
|
+
leaf.status === "AVAILABLE" &&
|
|
523
|
+
this.verifyKey(
|
|
524
|
+
await this.config.signer.getPublicKeyFromDerivation({
|
|
525
|
+
type: KeyDerivationType.LEAF,
|
|
526
|
+
path: leaf.parentNodeId,
|
|
527
|
+
}),
|
|
528
|
+
leaf.signingKeyshare?.publicKey ?? new Uint8Array(),
|
|
529
|
+
leaf.verifyingPublicKey,
|
|
530
|
+
)
|
|
531
|
+
) {
|
|
532
|
+
this.transferLeavesToSelf([leaf], {
|
|
533
|
+
type: KeyDerivationType.LEAF,
|
|
534
|
+
path: leaf.parentNodeId,
|
|
535
|
+
});
|
|
536
|
+
leavesToIgnore.add(id);
|
|
537
|
+
} else if (
|
|
538
|
+
!this.verifyKey(
|
|
513
539
|
await this.config.signer.getPublicKeyFromDerivation({
|
|
514
540
|
type: KeyDerivationType.LEAF,
|
|
515
541
|
path: leaf.id,
|
|
@@ -522,14 +548,33 @@ export class SparkWallet extends EventEmitter {
|
|
|
522
548
|
}
|
|
523
549
|
}
|
|
524
550
|
|
|
525
|
-
return
|
|
526
|
-
.filter(
|
|
527
|
-
([_, node]) =>
|
|
528
|
-
node.status === "AVAILABLE" && !leavesToIgnore.has(node.id),
|
|
529
|
-
)
|
|
551
|
+
return availableLeaves
|
|
552
|
+
.filter(([_, node]) => !leavesToIgnore.has(node.id))
|
|
530
553
|
.map(([_, node]) => node);
|
|
531
554
|
}
|
|
532
555
|
|
|
556
|
+
private async checkExtendLeaves(leaves: TreeNode[]): Promise<void> {
|
|
557
|
+
await this.withLeaves(async () => {
|
|
558
|
+
for (const leaf of leaves) {
|
|
559
|
+
if (!leaf.parentNodeId && leaf.status === "AVAILABLE") {
|
|
560
|
+
const res = await this.transferService.extendTimelock(leaf);
|
|
561
|
+
await this.transferLeavesToSelf(res.nodes, {
|
|
562
|
+
type: KeyDerivationType.LEAF,
|
|
563
|
+
path: leaf.id,
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
private verifyKey(
|
|
571
|
+
pubkey1: Uint8Array,
|
|
572
|
+
pubkey2: Uint8Array,
|
|
573
|
+
verifyingKey: Uint8Array,
|
|
574
|
+
): boolean {
|
|
575
|
+
return equalBytes(addPublicKeys(pubkey1, pubkey2), verifyingKey);
|
|
576
|
+
}
|
|
577
|
+
|
|
533
578
|
private async selectLeaves(
|
|
534
579
|
targetAmounts: number[],
|
|
535
580
|
): Promise<Map<number, TreeNode[]>> {
|
|
@@ -705,6 +750,8 @@ export class SparkWallet extends EventEmitter {
|
|
|
705
750
|
leaves = await this.checkExtendTimeLockNodes(leaves);
|
|
706
751
|
|
|
707
752
|
this.leaves = leaves;
|
|
753
|
+
this.checkExtendLeaves(leaves);
|
|
754
|
+
|
|
708
755
|
this.optimizeLeaves().catch((e) => {
|
|
709
756
|
console.error("Failed to optimize leaves", e);
|
|
710
757
|
});
|
|
@@ -1000,26 +1047,80 @@ export class SparkWallet extends EventEmitter {
|
|
|
1000
1047
|
})),
|
|
1001
1048
|
);
|
|
1002
1049
|
|
|
1003
|
-
const {
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1050
|
+
const {
|
|
1051
|
+
transfer,
|
|
1052
|
+
signatureMap,
|
|
1053
|
+
directSignatureMap,
|
|
1054
|
+
directFromCpfpSignatureMap,
|
|
1055
|
+
} = await this.transferService.startSwapSignRefund(
|
|
1056
|
+
leafKeyTweaks,
|
|
1057
|
+
hexToBytes(this.config.getSspIdentityPublicKey()),
|
|
1058
|
+
new Date(Date.now() + 2 * 60 * 1000),
|
|
1059
|
+
);
|
|
1060
|
+
|
|
1009
1061
|
try {
|
|
1010
1062
|
if (!transfer.leaves[0]?.leaf) {
|
|
1063
|
+
console.error("[processSwapBatch] First leaf is missing");
|
|
1011
1064
|
throw new Error("Failed to get leaf");
|
|
1012
1065
|
}
|
|
1013
1066
|
|
|
1014
|
-
const
|
|
1015
|
-
if (!
|
|
1016
|
-
|
|
1067
|
+
const cpfpRefundSignature = signatureMap.get(transfer.leaves[0].leaf.id);
|
|
1068
|
+
if (!cpfpRefundSignature) {
|
|
1069
|
+
console.error(
|
|
1070
|
+
"[processSwapBatch] Missing CPFP refund signature for first leaf",
|
|
1071
|
+
);
|
|
1072
|
+
throw new Error("Failed to get CPFP refund signature");
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
const directRefundSignature = directSignatureMap.get(
|
|
1076
|
+
transfer.leaves[0].leaf.id,
|
|
1077
|
+
);
|
|
1078
|
+
if (!directRefundSignature) {
|
|
1079
|
+
console.error(
|
|
1080
|
+
"[processSwapBatch] Missing direct refund signature for first leaf",
|
|
1081
|
+
);
|
|
1082
|
+
throw new Error("Failed to get direct refund signature");
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
const directFromCpfpRefundSignature = directFromCpfpSignatureMap.get(
|
|
1086
|
+
transfer.leaves[0].leaf.id,
|
|
1087
|
+
);
|
|
1088
|
+
if (!directFromCpfpRefundSignature) {
|
|
1089
|
+
console.error(
|
|
1090
|
+
"[processSwapBatch] Missing direct from CPFP refund signature for first leaf",
|
|
1091
|
+
);
|
|
1092
|
+
throw new Error("Failed to get direct from CPFP refund signature");
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
const {
|
|
1096
|
+
adaptorPrivateKey: cpfpAdaptorPrivateKey,
|
|
1097
|
+
adaptorSignature: cpfpAdaptorSignature,
|
|
1098
|
+
} = generateAdaptorFromSignature(cpfpRefundSignature);
|
|
1099
|
+
|
|
1100
|
+
let directAdaptorPrivateKey: Uint8Array = new Uint8Array();
|
|
1101
|
+
let directAdaptorSignature: Uint8Array = new Uint8Array();
|
|
1102
|
+
let directFromCpfpAdaptorPrivateKey: Uint8Array = new Uint8Array();
|
|
1103
|
+
let directFromCpfpAdaptorSignature: Uint8Array = new Uint8Array();
|
|
1104
|
+
|
|
1105
|
+
if (directRefundSignature.length > 0) {
|
|
1106
|
+
const { adaptorPrivateKey, adaptorSignature } =
|
|
1107
|
+
generateAdaptorFromSignature(directRefundSignature);
|
|
1108
|
+
|
|
1109
|
+
directAdaptorPrivateKey = adaptorPrivateKey;
|
|
1110
|
+
directAdaptorSignature = adaptorSignature;
|
|
1017
1111
|
}
|
|
1018
1112
|
|
|
1019
|
-
|
|
1020
|
-
|
|
1113
|
+
if (directFromCpfpRefundSignature.length > 0) {
|
|
1114
|
+
const { adaptorPrivateKey, adaptorSignature } =
|
|
1115
|
+
generateAdaptorFromSignature(directFromCpfpRefundSignature);
|
|
1116
|
+
directFromCpfpAdaptorPrivateKey = adaptorPrivateKey;
|
|
1117
|
+
directFromCpfpAdaptorSignature = adaptorSignature;
|
|
1118
|
+
}
|
|
1021
1119
|
|
|
1022
1120
|
if (!transfer.leaves[0].leaf) {
|
|
1121
|
+
console.error(
|
|
1122
|
+
"[processSwapBatch] First leaf missing when preparing user leaves",
|
|
1123
|
+
);
|
|
1023
1124
|
throw new Error("Failed to get leaf");
|
|
1024
1125
|
}
|
|
1025
1126
|
|
|
@@ -1029,42 +1130,128 @@ export class SparkWallet extends EventEmitter {
|
|
|
1029
1130
|
raw_unsigned_refund_transaction: bytesToHex(
|
|
1030
1131
|
transfer.leaves[0].intermediateRefundTx,
|
|
1031
1132
|
),
|
|
1032
|
-
|
|
1133
|
+
direct_raw_unsigned_refund_transaction: bytesToHex(
|
|
1134
|
+
transfer.leaves[0].intermediateDirectRefundTx,
|
|
1135
|
+
),
|
|
1136
|
+
direct_from_cpfp_raw_unsigned_refund_transaction: bytesToHex(
|
|
1137
|
+
transfer.leaves[0].intermediateDirectFromCpfpRefundTx,
|
|
1138
|
+
),
|
|
1139
|
+
adaptor_added_signature: bytesToHex(cpfpAdaptorSignature),
|
|
1140
|
+
direct_adaptor_added_signature: bytesToHex(directAdaptorSignature),
|
|
1141
|
+
direct_from_cpfp_adaptor_added_signature: bytesToHex(
|
|
1142
|
+
directFromCpfpAdaptorSignature,
|
|
1143
|
+
),
|
|
1033
1144
|
});
|
|
1034
1145
|
|
|
1035
1146
|
for (let i = 1; i < transfer.leaves.length; i++) {
|
|
1036
1147
|
const leaf = transfer.leaves[i];
|
|
1037
1148
|
if (!leaf?.leaf) {
|
|
1149
|
+
console.error(`[processSwapBatch] Leaf ${i + 1} is missing`);
|
|
1038
1150
|
throw new Error("Failed to get leaf");
|
|
1039
1151
|
}
|
|
1040
1152
|
|
|
1041
|
-
const
|
|
1042
|
-
if (!
|
|
1043
|
-
|
|
1153
|
+
const cpfpRefundSignature = signatureMap.get(leaf.leaf.id);
|
|
1154
|
+
if (!cpfpRefundSignature) {
|
|
1155
|
+
console.error(
|
|
1156
|
+
`[processSwapBatch] Missing CPFP refund signature for leaf ${i + 1}`,
|
|
1157
|
+
);
|
|
1158
|
+
throw new Error("Failed to get CPFP refund signature");
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
const directRefundSignature = directSignatureMap.get(leaf.leaf.id);
|
|
1162
|
+
if (!directRefundSignature) {
|
|
1163
|
+
console.error(
|
|
1164
|
+
`[processSwapBatch] Missing direct refund signature for leaf ${i + 1}`,
|
|
1165
|
+
);
|
|
1166
|
+
throw new Error("Failed to get direct refund signature");
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
const directFromCpfpRefundSignature = directFromCpfpSignatureMap.get(
|
|
1170
|
+
leaf.leaf.id,
|
|
1171
|
+
);
|
|
1172
|
+
if (!directFromCpfpRefundSignature) {
|
|
1173
|
+
console.error(
|
|
1174
|
+
`[processSwapBatch] Missing direct from CPFP refund signature for leaf ${i + 1}`,
|
|
1175
|
+
);
|
|
1176
|
+
throw new Error("Failed to get direct from CPFP refund signature");
|
|
1044
1177
|
}
|
|
1045
1178
|
|
|
1046
|
-
const
|
|
1047
|
-
|
|
1048
|
-
|
|
1179
|
+
const cpfpSignature = generateSignatureFromExistingAdaptor(
|
|
1180
|
+
cpfpRefundSignature,
|
|
1181
|
+
cpfpAdaptorPrivateKey,
|
|
1049
1182
|
);
|
|
1050
1183
|
|
|
1184
|
+
let directSignature: Uint8Array = new Uint8Array();
|
|
1185
|
+
if (directRefundSignature.length > 0) {
|
|
1186
|
+
directSignature = generateSignatureFromExistingAdaptor(
|
|
1187
|
+
directRefundSignature,
|
|
1188
|
+
directAdaptorPrivateKey,
|
|
1189
|
+
);
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
let directFromCpfpSignature: Uint8Array = new Uint8Array();
|
|
1193
|
+
if (directFromCpfpRefundSignature.length > 0) {
|
|
1194
|
+
directFromCpfpSignature = generateSignatureFromExistingAdaptor(
|
|
1195
|
+
directFromCpfpRefundSignature,
|
|
1196
|
+
directFromCpfpAdaptorPrivateKey,
|
|
1197
|
+
);
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1051
1200
|
userLeaves.push({
|
|
1052
1201
|
leaf_id: leaf.leaf.id,
|
|
1053
1202
|
raw_unsigned_refund_transaction: bytesToHex(
|
|
1054
1203
|
leaf.intermediateRefundTx,
|
|
1055
1204
|
),
|
|
1056
|
-
|
|
1205
|
+
direct_raw_unsigned_refund_transaction: bytesToHex(
|
|
1206
|
+
leaf.intermediateDirectRefundTx,
|
|
1207
|
+
),
|
|
1208
|
+
direct_from_cpfp_raw_unsigned_refund_transaction: bytesToHex(
|
|
1209
|
+
leaf.intermediateDirectFromCpfpRefundTx,
|
|
1210
|
+
),
|
|
1211
|
+
adaptor_added_signature: bytesToHex(cpfpSignature),
|
|
1212
|
+
direct_adaptor_added_signature: bytesToHex(directSignature),
|
|
1213
|
+
direct_from_cpfp_adaptor_added_signature: bytesToHex(
|
|
1214
|
+
directFromCpfpSignature,
|
|
1215
|
+
),
|
|
1057
1216
|
});
|
|
1058
1217
|
}
|
|
1059
1218
|
|
|
1060
1219
|
const sspClient = this.getSspClient();
|
|
1061
|
-
const
|
|
1062
|
-
secp256k1.getPublicKey(
|
|
1220
|
+
const cpfpAdaptorPubkey = bytesToHex(
|
|
1221
|
+
secp256k1.getPublicKey(cpfpAdaptorPrivateKey),
|
|
1063
1222
|
);
|
|
1223
|
+
if (!cpfpAdaptorPubkey) {
|
|
1224
|
+
throw new Error("Failed to generate CPFP adaptor pubkey");
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
let directAdaptorPubkey: string | undefined;
|
|
1228
|
+
if (directAdaptorPrivateKey.length > 0) {
|
|
1229
|
+
directAdaptorPubkey = bytesToHex(
|
|
1230
|
+
secp256k1.getPublicKey(directAdaptorPrivateKey),
|
|
1231
|
+
);
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
let directFromCpfpAdaptorPubkey: string | undefined;
|
|
1235
|
+
if (directFromCpfpAdaptorPrivateKey.length > 0) {
|
|
1236
|
+
directFromCpfpAdaptorPubkey = bytesToHex(
|
|
1237
|
+
secp256k1.getPublicKey(directFromCpfpAdaptorPrivateKey),
|
|
1238
|
+
);
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1064
1241
|
let request: LeavesSwapRequest | null | undefined = null;
|
|
1242
|
+
const targetAmountSats =
|
|
1243
|
+
targetAmounts?.reduce((acc, amount) => acc + amount, 0) ||
|
|
1244
|
+
leavesBatch.reduce((acc, leaf) => acc + leaf.value, 0);
|
|
1245
|
+
const totalAmountSats = leavesBatch.reduce(
|
|
1246
|
+
(acc, leaf) => acc + leaf.value,
|
|
1247
|
+
0,
|
|
1248
|
+
);
|
|
1249
|
+
|
|
1065
1250
|
request = await sspClient.requestLeaveSwap({
|
|
1066
1251
|
userLeaves,
|
|
1067
|
-
adaptorPubkey,
|
|
1252
|
+
adaptorPubkey: cpfpAdaptorPubkey,
|
|
1253
|
+
directAdaptorPubkey: directAdaptorPubkey,
|
|
1254
|
+
directFromCpfpAdaptorPubkey: directFromCpfpAdaptorPubkey,
|
|
1068
1255
|
targetAmountSats:
|
|
1069
1256
|
targetAmounts?.reduce((acc, amount) => acc + amount, 0) ||
|
|
1070
1257
|
leavesBatch.reduce((acc, leaf) => acc + leaf.value, 0),
|
|
@@ -1076,6 +1263,7 @@ export class SparkWallet extends EventEmitter {
|
|
|
1076
1263
|
});
|
|
1077
1264
|
|
|
1078
1265
|
if (!request) {
|
|
1266
|
+
console.error("[processSwapBatch] Leave swap request returned null");
|
|
1079
1267
|
throw new Error("Failed to request leaves swap. No response returned.");
|
|
1080
1268
|
}
|
|
1081
1269
|
|
|
@@ -1091,37 +1279,127 @@ export class SparkWallet extends EventEmitter {
|
|
|
1091
1279
|
});
|
|
1092
1280
|
|
|
1093
1281
|
if (Object.values(nodes.nodes).length !== request.swapLeaves.length) {
|
|
1282
|
+
console.error("[processSwapBatch] Node count mismatch:", {
|
|
1283
|
+
actual: Object.values(nodes.nodes).length,
|
|
1284
|
+
expected: request.swapLeaves.length,
|
|
1285
|
+
});
|
|
1094
1286
|
throw new Error("Expected same number of nodes as swapLeaves");
|
|
1095
1287
|
}
|
|
1096
1288
|
|
|
1097
1289
|
for (const [nodeId, node] of Object.entries(nodes.nodes)) {
|
|
1098
1290
|
if (!node.nodeTx) {
|
|
1291
|
+
console.error(`[processSwapBatch] Node tx missing for ${nodeId}`);
|
|
1099
1292
|
throw new Error(`Node tx not found for leaf ${nodeId}`);
|
|
1100
1293
|
}
|
|
1101
1294
|
|
|
1102
1295
|
if (!node.verifyingPublicKey) {
|
|
1296
|
+
console.error(
|
|
1297
|
+
`[processSwapBatch] Verifying public key missing for ${nodeId}`,
|
|
1298
|
+
);
|
|
1103
1299
|
throw new Error(`Node public key not found for leaf ${nodeId}`);
|
|
1104
1300
|
}
|
|
1105
1301
|
|
|
1106
1302
|
const leaf = request.swapLeaves.find((leaf) => leaf.leafId === nodeId);
|
|
1107
1303
|
if (!leaf) {
|
|
1304
|
+
console.error(`[processSwapBatch] Leaf not found for node ${nodeId}`);
|
|
1108
1305
|
throw new Error(`Leaf not found for node ${nodeId}`);
|
|
1109
1306
|
}
|
|
1110
|
-
|
|
1111
|
-
const
|
|
1112
|
-
const
|
|
1113
|
-
const
|
|
1114
|
-
const
|
|
1307
|
+
// Apply CPFP adaptor signature
|
|
1308
|
+
const cpfpNodeTx = getTxFromRawTxBytes(node.nodeTx);
|
|
1309
|
+
const cpfpRefundTxBytes = hexToBytes(leaf.rawUnsignedRefundTransaction);
|
|
1310
|
+
const cpfpRefundTx = getTxFromRawTxBytes(cpfpRefundTxBytes);
|
|
1311
|
+
const cpfpSighash = getSigHashFromTx(
|
|
1312
|
+
cpfpRefundTx,
|
|
1313
|
+
0,
|
|
1314
|
+
cpfpNodeTx.getOutput(0),
|
|
1315
|
+
);
|
|
1115
1316
|
|
|
1116
1317
|
const nodePublicKey = node.verifyingPublicKey;
|
|
1117
|
-
|
|
1118
1318
|
const taprootKey = computeTaprootKeyNoScript(nodePublicKey.slice(1));
|
|
1119
|
-
const
|
|
1319
|
+
const cpfpAdaptorSignatureBytes = hexToBytes(
|
|
1320
|
+
leaf.adaptorSignedSignature,
|
|
1321
|
+
);
|
|
1322
|
+
applyAdaptorToSignature(
|
|
1323
|
+
taprootKey.slice(1),
|
|
1324
|
+
cpfpSighash,
|
|
1325
|
+
cpfpAdaptorSignatureBytes,
|
|
1326
|
+
cpfpAdaptorPrivateKey,
|
|
1327
|
+
);
|
|
1328
|
+
|
|
1329
|
+
// Apply direct adaptor signature
|
|
1330
|
+
|
|
1331
|
+
if (!leaf.directRawUnsignedRefundTransaction) {
|
|
1332
|
+
throw new Error(
|
|
1333
|
+
`Direct raw unsigned refund transaction missing for node ${nodeId}`,
|
|
1334
|
+
);
|
|
1335
|
+
}
|
|
1336
|
+
if (!leaf.directAdaptorSignedSignature) {
|
|
1337
|
+
throw new Error(
|
|
1338
|
+
`Direct adaptor signed signature missing for node ${nodeId}`,
|
|
1339
|
+
);
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
const directNodeTx = getTxFromRawTxBytes(node.directTx);
|
|
1343
|
+
|
|
1344
|
+
const directRefundTxBytes = hexToBytes(
|
|
1345
|
+
leaf.directRawUnsignedRefundTransaction,
|
|
1346
|
+
);
|
|
1347
|
+
|
|
1348
|
+
const directRefundTx = getTxFromRawTxBytes(directRefundTxBytes);
|
|
1349
|
+
|
|
1350
|
+
const directSighash = getSigHashFromTx(
|
|
1351
|
+
directRefundTx,
|
|
1352
|
+
0,
|
|
1353
|
+
directNodeTx.getOutput(0),
|
|
1354
|
+
);
|
|
1355
|
+
|
|
1356
|
+
if (!leaf.directFromCpfpAdaptorSignedSignature) {
|
|
1357
|
+
throw new Error(
|
|
1358
|
+
`Direct adaptor signed signature missing for node ${nodeId}`,
|
|
1359
|
+
);
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
const directAdaptorSignatureBytes = hexToBytes(
|
|
1363
|
+
leaf.directAdaptorSignedSignature,
|
|
1364
|
+
);
|
|
1365
|
+
|
|
1366
|
+
applyAdaptorToSignature(
|
|
1367
|
+
taprootKey.slice(1),
|
|
1368
|
+
directSighash,
|
|
1369
|
+
directAdaptorSignatureBytes,
|
|
1370
|
+
directAdaptorPrivateKey,
|
|
1371
|
+
);
|
|
1372
|
+
|
|
1373
|
+
if (!leaf.directRawUnsignedRefundTransaction) {
|
|
1374
|
+
throw new Error(
|
|
1375
|
+
`Direct raw unsigned refund transaction missing for node ${nodeId}`,
|
|
1376
|
+
);
|
|
1377
|
+
}
|
|
1378
|
+
if (!leaf.directFromCpfpRawUnsignedRefundTransaction) {
|
|
1379
|
+
throw new Error(
|
|
1380
|
+
`Direct raw unsigned refund transaction missing for node ${nodeId}`,
|
|
1381
|
+
);
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
const directFromCpfpRefundTxBytes = hexToBytes(
|
|
1385
|
+
leaf.directFromCpfpRawUnsignedRefundTransaction,
|
|
1386
|
+
);
|
|
1387
|
+
const directFromCpfpRefundTx = getTxFromRawTxBytes(
|
|
1388
|
+
directFromCpfpRefundTxBytes,
|
|
1389
|
+
);
|
|
1390
|
+
const directFromCpfpSighash = getSigHashFromTx(
|
|
1391
|
+
directFromCpfpRefundTx,
|
|
1392
|
+
0,
|
|
1393
|
+
cpfpNodeTx.getOutput(0),
|
|
1394
|
+
);
|
|
1395
|
+
const directFromCpfpAdaptorSignatureBytes = hexToBytes(
|
|
1396
|
+
leaf.directFromCpfpAdaptorSignedSignature,
|
|
1397
|
+
);
|
|
1120
1398
|
applyAdaptorToSignature(
|
|
1121
1399
|
taprootKey.slice(1),
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1400
|
+
directFromCpfpSighash,
|
|
1401
|
+
directFromCpfpAdaptorSignatureBytes,
|
|
1402
|
+
directFromCpfpAdaptorPrivateKey,
|
|
1125
1403
|
);
|
|
1126
1404
|
}
|
|
1127
1405
|
|
|
@@ -1129,15 +1407,24 @@ export class SparkWallet extends EventEmitter {
|
|
|
1129
1407
|
transfer,
|
|
1130
1408
|
leafKeyTweaks,
|
|
1131
1409
|
signatureMap,
|
|
1410
|
+
directSignatureMap,
|
|
1411
|
+
directFromCpfpSignatureMap,
|
|
1132
1412
|
);
|
|
1133
|
-
|
|
1134
1413
|
const completeResponse = await sspClient.completeLeaveSwap({
|
|
1135
|
-
adaptorSecretKey: bytesToHex(
|
|
1414
|
+
adaptorSecretKey: bytesToHex(cpfpAdaptorPrivateKey),
|
|
1415
|
+
directAdaptorSecretKey: bytesToHex(directAdaptorPrivateKey),
|
|
1416
|
+
directFromCpfpAdaptorSecretKey: bytesToHex(
|
|
1417
|
+
directFromCpfpAdaptorPrivateKey,
|
|
1418
|
+
),
|
|
1136
1419
|
userOutboundTransferExternalId: transfer.id,
|
|
1137
1420
|
leavesSwapRequestId: request.id,
|
|
1138
1421
|
});
|
|
1139
1422
|
|
|
1140
1423
|
if (!completeResponse || !completeResponse.inboundTransfer?.sparkId) {
|
|
1424
|
+
console.error(
|
|
1425
|
+
"[processSwapBatch] Invalid complete response:",
|
|
1426
|
+
completeResponse,
|
|
1427
|
+
);
|
|
1141
1428
|
throw new Error("Failed to complete leaves swap");
|
|
1142
1429
|
}
|
|
1143
1430
|
|
|
@@ -1146,6 +1433,7 @@ export class SparkWallet extends EventEmitter {
|
|
|
1146
1433
|
);
|
|
1147
1434
|
|
|
1148
1435
|
if (!incomingTransfer) {
|
|
1436
|
+
console.error("[processSwapBatch] No incoming transfer found");
|
|
1149
1437
|
throw new Error("Failed to get incoming transfer");
|
|
1150
1438
|
}
|
|
1151
1439
|
|
|
@@ -1156,6 +1444,11 @@ export class SparkWallet extends EventEmitter {
|
|
|
1156
1444
|
optimize: false,
|
|
1157
1445
|
});
|
|
1158
1446
|
} catch (e) {
|
|
1447
|
+
console.error("[processSwapBatch] Error details:", {
|
|
1448
|
+
error: e,
|
|
1449
|
+
message: (e as Error).message,
|
|
1450
|
+
stack: (e as Error).stack,
|
|
1451
|
+
});
|
|
1159
1452
|
await this.cancelAllSenderInitiatedTransfers();
|
|
1160
1453
|
throw new Error(`Failed to request leaves swap: ${e}`);
|
|
1161
1454
|
}
|
|
@@ -1769,8 +2062,9 @@ export class SparkWallet extends EventEmitter {
|
|
|
1769
2062
|
});
|
|
1770
2063
|
}
|
|
1771
2064
|
|
|
2065
|
+
const { fetch, Headers } = getFetch();
|
|
1772
2066
|
const baseUrl = this.config.getElectrsUrl();
|
|
1773
|
-
const headers
|
|
2067
|
+
const headers = new Headers();
|
|
1774
2068
|
|
|
1775
2069
|
let txHex: string | undefined;
|
|
1776
2070
|
|
|
@@ -1783,7 +2077,7 @@ export class SparkWallet extends EventEmitter {
|
|
|
1783
2077
|
const auth = btoa(
|
|
1784
2078
|
`${ELECTRS_CREDENTIALS.username}:${ELECTRS_CREDENTIALS.password}`,
|
|
1785
2079
|
);
|
|
1786
|
-
headers
|
|
2080
|
+
headers.set("Authorization", `Basic ${auth}`);
|
|
1787
2081
|
}
|
|
1788
2082
|
|
|
1789
2083
|
const response = await fetch(`${baseUrl}/tx/${txid}/hex`, {
|
|
@@ -1940,8 +2234,9 @@ export class SparkWallet extends EventEmitter {
|
|
|
1940
2234
|
}
|
|
1941
2235
|
|
|
1942
2236
|
const nodes = await mutex.runExclusive(async () => {
|
|
2237
|
+
const { fetch, Headers } = getFetch();
|
|
1943
2238
|
const baseUrl = this.config.getElectrsUrl();
|
|
1944
|
-
const headers
|
|
2239
|
+
const headers = new Headers();
|
|
1945
2240
|
|
|
1946
2241
|
let txHex: string | undefined;
|
|
1947
2242
|
|
|
@@ -1954,7 +2249,7 @@ export class SparkWallet extends EventEmitter {
|
|
|
1954
2249
|
const auth = btoa(
|
|
1955
2250
|
`${ELECTRS_CREDENTIALS.username}:${ELECTRS_CREDENTIALS.password}`,
|
|
1956
2251
|
);
|
|
1957
|
-
headers
|
|
2252
|
+
headers.set("Authorization", `Basic ${auth}`);
|
|
1958
2253
|
}
|
|
1959
2254
|
|
|
1960
2255
|
const response = await fetch(`${baseUrl}/tx/${txid}/hex`, {
|
|
@@ -2251,10 +2546,16 @@ export class SparkWallet extends EventEmitter {
|
|
|
2251
2546
|
|
|
2252
2547
|
for (const node of nodes) {
|
|
2253
2548
|
const nodeTx = getTxFromRawTxBytes(node.nodeTx);
|
|
2254
|
-
const
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2549
|
+
const sequence = nodeTx.getInput(0).sequence;
|
|
2550
|
+
if (!sequence) {
|
|
2551
|
+
throw new ValidationError("Invalid node transaction", {
|
|
2552
|
+
field: "sequence",
|
|
2553
|
+
value: nodeTx.getInput(0),
|
|
2554
|
+
expected: "Non-null sequence",
|
|
2555
|
+
});
|
|
2556
|
+
}
|
|
2557
|
+
const needsRefresh = doesLeafNeedRefresh(sequence, true);
|
|
2558
|
+
if (needsRefresh) {
|
|
2258
2559
|
nodesToExtend.push(node);
|
|
2259
2560
|
nodeIds.push(node.id);
|
|
2260
2561
|
} else {
|
|
@@ -2299,11 +2600,16 @@ export class SparkWallet extends EventEmitter {
|
|
|
2299
2600
|
|
|
2300
2601
|
for (const node of nodes) {
|
|
2301
2602
|
const refundTx = getTxFromRawTxBytes(node.refundTx);
|
|
2302
|
-
const
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2603
|
+
const sequence = refundTx.getInput(0).sequence;
|
|
2604
|
+
if (!sequence) {
|
|
2605
|
+
throw new ValidationError("Invalid refund transaction", {
|
|
2606
|
+
field: "sequence",
|
|
2607
|
+
value: refundTx.getInput(0),
|
|
2608
|
+
expected: "Non-null sequence",
|
|
2609
|
+
});
|
|
2610
|
+
}
|
|
2611
|
+
const needsRefresh = doesLeafNeedRefresh(sequence);
|
|
2612
|
+
if (needsRefresh) {
|
|
2307
2613
|
nodesToRefresh.push(node);
|
|
2308
2614
|
nodeIds.push(node.id);
|
|
2309
2615
|
} else {
|
|
@@ -2343,7 +2649,7 @@ export class SparkWallet extends EventEmitter {
|
|
|
2343
2649
|
}
|
|
2344
2650
|
|
|
2345
2651
|
const { nodes } = await this.transferService.refreshTimelockNodes(
|
|
2346
|
-
|
|
2652
|
+
node,
|
|
2347
2653
|
parentNode,
|
|
2348
2654
|
);
|
|
2349
2655
|
|
|
@@ -2408,6 +2714,9 @@ export class SparkWallet extends EventEmitter {
|
|
|
2408
2714
|
leaf: {
|
|
2409
2715
|
...leaf.leaf,
|
|
2410
2716
|
refundTx: leaf.intermediateRefundTx,
|
|
2717
|
+
directRefundTx: leaf.intermediateDirectRefundTx,
|
|
2718
|
+
directFromCpfpRefundTx:
|
|
2719
|
+
leaf.intermediateDirectFromCpfpRefundTx,
|
|
2411
2720
|
},
|
|
2412
2721
|
keyDerivation: {
|
|
2413
2722
|
type: KeyDerivationType.ECIES,
|
|
@@ -2503,7 +2812,9 @@ export class SparkWallet extends EventEmitter {
|
|
|
2503
2812
|
transfer.status !==
|
|
2504
2813
|
TransferStatus.TRANSFER_STATUS_RECEIVER_REFUND_SIGNED &&
|
|
2505
2814
|
transfer.status !==
|
|
2506
|
-
TransferStatus.TRANSFER_STATUS_RECEIVER_KEY_TWEAK_APPLIED
|
|
2815
|
+
TransferStatus.TRANSFER_STATUS_RECEIVER_KEY_TWEAK_APPLIED &&
|
|
2816
|
+
transfer.status !==
|
|
2817
|
+
TransferStatus.TRANSFER_STATUS_RECEIVER_KEY_TWEAK_LOCKED
|
|
2507
2818
|
) {
|
|
2508
2819
|
continue;
|
|
2509
2820
|
}
|
|
@@ -2857,6 +3168,8 @@ export class SparkWallet extends EventEmitter {
|
|
|
2857
3168
|
swapResponse.transfer,
|
|
2858
3169
|
leavesToSend,
|
|
2859
3170
|
new Map(),
|
|
3171
|
+
new Map(),
|
|
3172
|
+
new Map(),
|
|
2860
3173
|
);
|
|
2861
3174
|
|
|
2862
3175
|
const sspResponse = await sspClient.requestLightningSend({
|
|
@@ -3405,12 +3718,16 @@ export class SparkWallet extends EventEmitter {
|
|
|
3405
3718
|
tokenTransactionHashes,
|
|
3406
3719
|
tokenIdentifiers,
|
|
3407
3720
|
outputIds,
|
|
3721
|
+
pageSize = 100,
|
|
3722
|
+
offset = 0,
|
|
3408
3723
|
}: {
|
|
3409
3724
|
ownerPublicKeys?: string[];
|
|
3410
3725
|
issuerPublicKeys?: string[];
|
|
3411
3726
|
tokenTransactionHashes?: string[];
|
|
3412
3727
|
tokenIdentifiers?: string[];
|
|
3413
3728
|
outputIds?: string[];
|
|
3729
|
+
pageSize?: number;
|
|
3730
|
+
offset?: number;
|
|
3414
3731
|
}): Promise<TokenTransactionWithStatus[]> {
|
|
3415
3732
|
return this.tokenTransactionService.queryTokenTransactions({
|
|
3416
3733
|
ownerPublicKeys,
|
|
@@ -3418,6 +3735,8 @@ export class SparkWallet extends EventEmitter {
|
|
|
3418
3735
|
tokenTransactionHashes,
|
|
3419
3736
|
tokenIdentifiers,
|
|
3420
3737
|
outputIds,
|
|
3738
|
+
pageSize,
|
|
3739
|
+
offset,
|
|
3421
3740
|
}) as Promise<TokenTransactionWithStatus[]>;
|
|
3422
3741
|
}
|
|
3423
3742
|
|
|
@@ -3814,101 +4133,75 @@ export class SparkWallet extends EventEmitter {
|
|
|
3814
4133
|
includeParents: true,
|
|
3815
4134
|
});
|
|
3816
4135
|
|
|
3817
|
-
|
|
3818
|
-
if (!
|
|
4136
|
+
let leaf = response.nodes[nodeId];
|
|
4137
|
+
if (!leaf) {
|
|
3819
4138
|
throw new ValidationError("Node not found", {
|
|
3820
4139
|
field: "nodeId",
|
|
3821
4140
|
value: nodeId,
|
|
3822
4141
|
});
|
|
3823
4142
|
}
|
|
3824
4143
|
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
4144
|
+
let parentNode;
|
|
4145
|
+
let hasParentNode = false;
|
|
4146
|
+
if (!leaf.parentNodeId) {
|
|
4147
|
+
// skip node timelock check
|
|
4148
|
+
} else {
|
|
4149
|
+
hasParentNode = true;
|
|
4150
|
+
parentNode = response.nodes[leaf.parentNodeId];
|
|
4151
|
+
if (!parentNode) {
|
|
4152
|
+
throw new ValidationError("Parent node not found", {
|
|
4153
|
+
field: "parentNodeId",
|
|
4154
|
+
value: leaf.parentNodeId,
|
|
4155
|
+
});
|
|
4156
|
+
}
|
|
3830
4157
|
}
|
|
3831
4158
|
|
|
3832
|
-
const
|
|
3833
|
-
|
|
3834
|
-
throw new ValidationError("Parent node not found", {
|
|
3835
|
-
field: "parentNodeId",
|
|
3836
|
-
value: node.parentNodeId,
|
|
3837
|
-
});
|
|
3838
|
-
}
|
|
4159
|
+
const nodeTx = getTxFromRawTxBytes(leaf.nodeTx);
|
|
4160
|
+
const refundTx = getTxFromRawTxBytes(leaf.refundTx);
|
|
3839
4161
|
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
4162
|
+
if (hasParentNode) {
|
|
4163
|
+
const nodeTimelock = getCurrentTimelock(nodeTx.getInput(0).sequence);
|
|
4164
|
+
if (nodeTimelock > 100) {
|
|
4165
|
+
const expiredNodeTxLeaf =
|
|
4166
|
+
await this.transferService.testonly_expireTimeLockNodeTx(
|
|
4167
|
+
leaf,
|
|
4168
|
+
parentNode,
|
|
4169
|
+
);
|
|
3845
4170
|
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
4171
|
+
if (!expiredNodeTxLeaf.nodes[0]) {
|
|
4172
|
+
throw new ValidationError("No expired node tx leaf", {
|
|
4173
|
+
field: "expiredNodeTxLeaf",
|
|
4174
|
+
value: expiredNodeTxLeaf,
|
|
4175
|
+
});
|
|
4176
|
+
}
|
|
4177
|
+
leaf = expiredNodeTxLeaf.nodes[0];
|
|
3852
4178
|
}
|
|
3853
4179
|
}
|
|
3854
|
-
|
|
3855
|
-
throw new NetworkError(
|
|
3856
|
-
"Failed to refresh timelock",
|
|
3857
|
-
{
|
|
3858
|
-
method: "refresh_timelock",
|
|
3859
|
-
},
|
|
3860
|
-
error as Error,
|
|
3861
|
-
);
|
|
3862
|
-
}
|
|
3863
|
-
}
|
|
3864
|
-
|
|
3865
|
-
/**
|
|
3866
|
-
* Refresh the timelock of a specific node's refund transaction only.
|
|
3867
|
-
*
|
|
3868
|
-
* @param {string} nodeId - The ID of the node whose refund transaction to refresh
|
|
3869
|
-
* @returns {Promise<void>} Promise that resolves when the refund timelock is refreshed
|
|
3870
|
-
*/
|
|
3871
|
-
public async testOnly_expireTimelockRefundTx(nodeId: string): Promise<void> {
|
|
3872
|
-
const sparkClient = await this.connectionManager.createSparkClient(
|
|
3873
|
-
this.config.getCoordinatorAddress(),
|
|
3874
|
-
);
|
|
4180
|
+
const refundTimelock = getCurrentTimelock(refundTx.getInput(0).sequence);
|
|
3875
4181
|
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
source: {
|
|
3880
|
-
$case: "nodeIds",
|
|
3881
|
-
nodeIds: {
|
|
3882
|
-
nodeIds: [nodeId],
|
|
3883
|
-
},
|
|
3884
|
-
},
|
|
3885
|
-
includeParents: false,
|
|
3886
|
-
});
|
|
4182
|
+
if (refundTimelock > 100) {
|
|
4183
|
+
const expiredTxLeaf =
|
|
4184
|
+
await this.transferService.testonly_expireTimeLockRefundtx(leaf);
|
|
3887
4185
|
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
}
|
|
4186
|
+
if (!expiredTxLeaf.nodes[0]) {
|
|
4187
|
+
throw new ValidationError("No expired tx leaf", {
|
|
4188
|
+
field: "expiredTxLeaf",
|
|
4189
|
+
value: expiredTxLeaf,
|
|
4190
|
+
});
|
|
4191
|
+
}
|
|
4192
|
+
leaf = expiredTxLeaf.nodes[0];
|
|
3894
4193
|
}
|
|
3895
4194
|
|
|
3896
|
-
// Call the transfer service to refresh the refund timelock
|
|
3897
|
-
const result = await this.transferService.refreshTimelockRefundTx(node);
|
|
3898
|
-
|
|
3899
4195
|
// Update the local leaves if this node is in our wallet
|
|
3900
|
-
const leafIndex = this.leaves.findIndex((leaf) => leaf.id ===
|
|
3901
|
-
if (leafIndex !== -1
|
|
3902
|
-
|
|
3903
|
-
if (newNode) {
|
|
3904
|
-
this.leaves[leafIndex] = newNode;
|
|
3905
|
-
}
|
|
4196
|
+
const leafIndex = this.leaves.findIndex((leaf) => leaf.id === leaf.id);
|
|
4197
|
+
if (leafIndex !== -1) {
|
|
4198
|
+
this.leaves[leafIndex] = leaf;
|
|
3906
4199
|
}
|
|
3907
4200
|
} catch (error) {
|
|
3908
4201
|
throw new NetworkError(
|
|
3909
|
-
"Failed to refresh
|
|
4202
|
+
"Failed to refresh timelock",
|
|
3910
4203
|
{
|
|
3911
|
-
method: "
|
|
4204
|
+
method: "refresh_timelock",
|
|
3912
4205
|
},
|
|
3913
4206
|
error as Error,
|
|
3914
4207
|
);
|