@buildonspark/spark-sdk 0.1.43 → 0.1.45
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 +16 -0
- package/dist/{RequestLightningSendInput-D7fZdT4A.d.ts → RequestLightningSendInput-DEPd_fPO.d.ts} +43 -4
- package/dist/{RequestLightningSendInput-Na1mHdWg.d.cts → RequestLightningSendInput-Du0z7Om7.d.cts} +43 -4
- package/dist/address/index.cjs +2 -2
- package/dist/address/index.d.cts +2 -2
- package/dist/address/index.d.ts +2 -2
- package/dist/address/index.js +2 -2
- package/dist/{chunk-IRW5TWMH.js → chunk-5FUB65LX.js} +7 -9
- package/dist/{chunk-BUTZWYBW.js → chunk-6264CGDM.js} +4 -4
- package/dist/{chunk-VFJQNBFX.js → chunk-7V6N75CC.js} +5 -2
- package/dist/{chunk-M6A4KFIG.js → chunk-BGGEVUJK.js} +1505 -445
- package/dist/{chunk-DQYKQJRZ.js → chunk-C2S227QR.js} +675 -52
- package/dist/{chunk-GYQR4B4P.js → chunk-GZ5IPPJ2.js} +2 -2
- package/dist/{chunk-6AFUC5M2.js → chunk-HWJWKEIU.js} +8 -2
- package/dist/{chunk-TOSP3INR.js → chunk-I54FARY2.js} +4 -2
- package/dist/{chunk-WWOTVNPP.js → chunk-J2IE4Z7Y.js} +544 -431
- package/dist/{chunk-O4RYNJNB.js → chunk-KMUMFYFX.js} +3 -3
- package/dist/chunk-LHRD2WT6.js +2374 -0
- package/dist/{chunk-ABZA6R5S.js → chunk-NTFKFRQ2.js} +1 -1
- package/dist/{chunk-MIVX3GHD.js → chunk-OBFKIEMP.js} +1 -1
- package/dist/{chunk-HRQRRDSS.js → chunk-PQN3C2MF.js} +15 -15
- package/dist/{chunk-DOA6QXYQ.js → chunk-R5PXJZQS.js} +3 -1
- package/dist/{chunk-TIUBYNN5.js → chunk-YUPMXTCJ.js} +4 -4
- package/dist/graphql/objects/index.d.cts +6 -43
- package/dist/graphql/objects/index.d.ts +6 -43
- package/dist/graphql/objects/index.js +1 -1
- package/dist/index-B2AwKW5J.d.cts +214 -0
- package/dist/index-CJDi1HWc.d.ts +214 -0
- package/dist/index.cjs +4150 -1026
- package/dist/index.d.cts +764 -19
- package/dist/index.d.ts +764 -19
- package/dist/index.js +17 -21
- package/dist/index.node.cjs +4153 -1033
- package/dist/index.node.d.cts +10 -8
- package/dist/index.node.d.ts +10 -8
- package/dist/index.node.js +20 -28
- package/dist/native/index.cjs +4166 -1042
- package/dist/native/index.d.cts +369 -108
- package/dist/native/index.d.ts +369 -108
- package/dist/native/index.js +4138 -1015
- package/dist/{network-xkBSpaTn.d.ts → network-BTJl-Sul.d.ts} +1 -1
- package/dist/{network-D5lKssVl.d.cts → network-CqgsdUF2.d.cts} +1 -1
- package/dist/proto/lrc20.cjs +222 -19
- package/dist/proto/lrc20.d.cts +1 -1
- package/dist/proto/lrc20.d.ts +1 -1
- package/dist/proto/lrc20.js +2 -2
- package/dist/proto/spark.cjs +1502 -442
- package/dist/proto/spark.d.cts +1 -1
- package/dist/proto/spark.d.ts +1 -1
- package/dist/proto/spark.js +5 -5
- package/dist/proto/spark_token.cjs +1515 -56
- package/dist/proto/spark_token.d.cts +153 -15
- package/dist/proto/spark_token.d.ts +153 -15
- package/dist/proto/spark_token.js +40 -4
- package/dist/{sdk-types-B-q9py_P.d.cts → sdk-types-B0SwjolI.d.cts} +1 -1
- package/dist/{sdk-types-BPoPgzda.d.ts → sdk-types-Cc4l4kb1.d.ts} +1 -1
- package/dist/services/config.cjs +7 -3
- package/dist/services/config.d.cts +5 -4
- package/dist/services/config.d.ts +5 -4
- package/dist/services/config.js +6 -6
- package/dist/services/connection.cjs +2938 -646
- package/dist/services/connection.d.cts +5 -4
- package/dist/services/connection.d.ts +5 -4
- package/dist/services/connection.js +4 -4
- package/dist/services/index.cjs +6381 -3461
- package/dist/services/index.d.cts +7 -6
- package/dist/services/index.d.ts +7 -6
- package/dist/services/index.js +15 -13
- package/dist/services/lrc-connection.cjs +227 -21
- package/dist/services/lrc-connection.d.cts +5 -4
- package/dist/services/lrc-connection.d.ts +5 -4
- package/dist/services/lrc-connection.js +4 -4
- package/dist/services/token-transactions.cjs +868 -244
- package/dist/services/token-transactions.d.cts +25 -7
- package/dist/services/token-transactions.d.ts +25 -7
- package/dist/services/token-transactions.js +5 -4
- package/dist/services/wallet-config.cjs +4 -1
- package/dist/services/wallet-config.d.cts +7 -5
- package/dist/services/wallet-config.d.ts +7 -5
- package/dist/services/wallet-config.js +3 -1
- package/dist/signer/signer.cjs +5 -2
- package/dist/signer/signer.d.cts +3 -2
- package/dist/signer/signer.d.ts +3 -2
- package/dist/signer/signer.js +2 -2
- package/dist/{signer-wqesWifN.d.ts → signer-BocS_J6B.d.ts} +2 -6
- package/dist/{signer-IO3oMRNj.d.cts → signer-DKS0AJkw.d.cts} +2 -6
- package/dist/{spark-CDm4gqS6.d.cts → spark-dM7EYXYQ.d.cts} +138 -42
- package/dist/{spark-CDm4gqS6.d.ts → spark-dM7EYXYQ.d.ts} +138 -42
- package/dist/spark_bindings/native/index.cjs +183 -0
- package/dist/spark_bindings/native/index.d.cts +14 -0
- package/dist/spark_bindings/native/index.d.ts +14 -0
- package/dist/spark_bindings/native/index.js +141 -0
- package/dist/spark_bindings/wasm/index.cjs +1093 -0
- package/dist/spark_bindings/wasm/index.d.cts +47 -0
- package/dist/spark_bindings/wasm/index.d.ts +47 -0
- package/dist/{chunk-K4C4W5FC.js → spark_bindings/wasm/index.js} +7 -6
- package/dist/types/index.cjs +1503 -443
- package/dist/types/index.d.cts +6 -5
- package/dist/types/index.d.ts +6 -5
- package/dist/types/index.js +3 -3
- package/dist/types-C-Rp0Oo7.d.cts +46 -0
- package/dist/types-C-Rp0Oo7.d.ts +46 -0
- package/dist/utils/index.cjs +358 -36
- package/dist/utils/index.d.cts +14 -134
- package/dist/utils/index.d.ts +14 -134
- package/dist/utils/index.js +8 -8
- package/package.json +21 -1
- package/src/constants.ts +5 -1
- package/src/graphql/client.ts +28 -0
- package/src/graphql/mutations/RequestCoopExit.ts +6 -0
- package/src/graphql/mutations/RequestSwapLeaves.ts +2 -0
- package/src/graphql/queries/GetCoopExitFeeQuote.ts +20 -0
- package/src/index.node.ts +0 -1
- package/src/index.ts +0 -1
- package/src/native/index.ts +1 -2
- package/src/proto/common.ts +5 -5
- package/src/proto/google/protobuf/descriptor.ts +34 -34
- package/src/proto/google/protobuf/duration.ts +2 -2
- package/src/proto/google/protobuf/empty.ts +2 -2
- package/src/proto/google/protobuf/timestamp.ts +2 -2
- package/src/proto/mock.ts +4 -4
- package/src/proto/spark.ts +1924 -525
- package/src/proto/spark_authn.ts +7 -7
- package/src/proto/spark_token.ts +1668 -105
- package/src/proto/validate/validate.ts +24 -24
- package/src/services/bolt11-spark.ts +62 -187
- package/src/services/coop-exit.ts +3 -0
- package/src/services/lrc20.ts +1 -1
- package/src/services/token-transactions.ts +209 -9
- package/src/services/transfer.ts +22 -3
- package/src/services/tree-creation.ts +13 -0
- package/src/services/wallet-config.ts +2 -1
- package/src/spark-wallet/spark-wallet.node.ts +3 -7
- package/src/spark-wallet/spark-wallet.ts +376 -232
- package/src/spark-wallet/types.ts +39 -3
- package/src/tests/bolt11-spark.test.ts +7 -15
- package/src/tests/integration/deposit.test.ts +16 -0
- package/src/tests/integration/ssp/coop-exit.test.ts +85 -21
- package/src/tests/integration/ssp/swap.test.ts +47 -0
- package/src/tests/integration/swap.test.ts +453 -433
- package/src/tests/integration/transfer.test.ts +261 -248
- package/src/tests/token-identifier.test.ts +54 -0
- package/src/tests/tokens.test.ts +218 -22
- package/src/utils/token-hashing.ts +346 -52
- package/src/utils/token-identifier.ts +88 -0
- package/src/utils/token-transaction-validation.ts +350 -5
- package/src/utils/token-transactions.ts +12 -8
- package/src/utils/transaction.ts +2 -8
- package/dist/chunk-VA7MV4MZ.js +0 -1073
- package/dist/index-7RYRH5wc.d.ts +0 -815
- package/dist/index-BJOc8Ur-.d.cts +0 -815
- package/dist/wasm-7OWFHDMS.js +0 -21
- package/src/logger.ts +0 -3
|
@@ -23,7 +23,7 @@ import SspClient from "../graphql/client.js";
|
|
|
23
23
|
import {
|
|
24
24
|
BitcoinNetwork,
|
|
25
25
|
ClaimStaticDepositOutput,
|
|
26
|
-
|
|
26
|
+
CoopExitFeeQuote,
|
|
27
27
|
CoopExitRequest,
|
|
28
28
|
ExitSpeed,
|
|
29
29
|
LeavesSwapFeeEstimateOutput,
|
|
@@ -31,8 +31,8 @@ import {
|
|
|
31
31
|
LightningReceiveRequest,
|
|
32
32
|
LightningSendFeeEstimateInput,
|
|
33
33
|
LightningSendRequest,
|
|
34
|
+
RequestCoopExitInput,
|
|
34
35
|
StaticDepositQuoteOutput,
|
|
35
|
-
SwapLeaf,
|
|
36
36
|
UserLeafInput,
|
|
37
37
|
} from "../graphql/objects/index.js";
|
|
38
38
|
import GraphQLTransferObj from "../graphql/objects/Transfer.js";
|
|
@@ -43,7 +43,6 @@ import {
|
|
|
43
43
|
QueryNodesResponse,
|
|
44
44
|
SigningJob,
|
|
45
45
|
SubscribeToEventsResponse,
|
|
46
|
-
TokenTransactionWithStatus,
|
|
47
46
|
Transfer,
|
|
48
47
|
TransferStatus,
|
|
49
48
|
TransferType,
|
|
@@ -102,7 +101,7 @@ import {
|
|
|
102
101
|
SparkAddressFormat,
|
|
103
102
|
} from "../address/index.js";
|
|
104
103
|
import { isReactNative } from "../constants.js";
|
|
105
|
-
import { networkToJSON } from "../proto/spark.js";
|
|
104
|
+
import { Network as NetworkProto, networkToJSON } from "../proto/spark.js";
|
|
106
105
|
import {
|
|
107
106
|
decodeInvoice,
|
|
108
107
|
getNetworkFromInvoice,
|
|
@@ -125,9 +124,12 @@ import type {
|
|
|
125
124
|
InitWalletResponse,
|
|
126
125
|
PayLightningInvoiceParams,
|
|
127
126
|
SparkWalletProps,
|
|
128
|
-
|
|
127
|
+
TokenMetadata,
|
|
129
128
|
TransferParams,
|
|
129
|
+
TokenBalanceMap,
|
|
130
130
|
} from "./types.js";
|
|
131
|
+
import { encodeHumanReadableTokenIdentifier } from "../utils/token-identifier.js";
|
|
132
|
+
import { TokenTransactionWithStatus } from "../proto/spark_token.js";
|
|
131
133
|
|
|
132
134
|
/**
|
|
133
135
|
* The SparkWallet class is the primary interface for interacting with the Spark network.
|
|
@@ -164,6 +166,7 @@ export class SparkWallet extends EventEmitter {
|
|
|
164
166
|
private streamController: AbortController | null = null;
|
|
165
167
|
|
|
166
168
|
protected leaves: TreeNode[] = [];
|
|
169
|
+
|
|
167
170
|
protected tokenOutputs: Map<string, OutputWithPreviousTransactionData[]> =
|
|
168
171
|
new Map();
|
|
169
172
|
|
|
@@ -486,8 +489,7 @@ export class SparkWallet extends EventEmitter {
|
|
|
486
489
|
leaf.signingKeyshare.publicKey,
|
|
487
490
|
operatorLeaf.signingKeyshare.publicKey,
|
|
488
491
|
) ||
|
|
489
|
-
!equalBytes(leaf.nodeTx, operatorLeaf.nodeTx)
|
|
490
|
-
!equalBytes(leaf.refundTx, operatorLeaf.refundTx)
|
|
492
|
+
!equalBytes(leaf.nodeTx, operatorLeaf.nodeTx)
|
|
491
493
|
) {
|
|
492
494
|
leavesToIgnore.add(nodeId);
|
|
493
495
|
}
|
|
@@ -524,14 +526,40 @@ export class SparkWallet extends EventEmitter {
|
|
|
524
526
|
.map(([_, node]) => node);
|
|
525
527
|
}
|
|
526
528
|
|
|
527
|
-
private async selectLeaves(
|
|
528
|
-
|
|
529
|
+
private async selectLeaves(
|
|
530
|
+
targetAmounts: number[],
|
|
531
|
+
): Promise<Map<number, TreeNode[]>> {
|
|
532
|
+
if (targetAmounts.length === 0) {
|
|
533
|
+
throw new ValidationError("Target amounts must be non-empty", {
|
|
534
|
+
field: "targetAmounts",
|
|
535
|
+
value: targetAmounts,
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (targetAmounts.some((amount) => amount <= 0)) {
|
|
529
540
|
throw new ValidationError("Target amount must be positive", {
|
|
530
|
-
field: "
|
|
531
|
-
value:
|
|
541
|
+
field: "targetAmounts",
|
|
542
|
+
value: targetAmounts,
|
|
532
543
|
});
|
|
533
544
|
}
|
|
534
545
|
|
|
546
|
+
const totalTargetAmount = targetAmounts.reduce(
|
|
547
|
+
(acc, amount) => acc + amount,
|
|
548
|
+
0,
|
|
549
|
+
);
|
|
550
|
+
const totalBalance = this.getInternalBalance();
|
|
551
|
+
|
|
552
|
+
if (totalTargetAmount > totalBalance) {
|
|
553
|
+
throw new ValidationError(
|
|
554
|
+
"Total target amount exceeds available balance",
|
|
555
|
+
{
|
|
556
|
+
field: "targetAmounts",
|
|
557
|
+
value: totalTargetAmount,
|
|
558
|
+
expected: `less than or equal to ${totalBalance}`,
|
|
559
|
+
},
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
|
|
535
563
|
const leaves = await this.getLeaves();
|
|
536
564
|
if (leaves.length === 0) {
|
|
537
565
|
throw new ValidationError("No owned leaves found", {
|
|
@@ -541,37 +569,63 @@ export class SparkWallet extends EventEmitter {
|
|
|
541
569
|
|
|
542
570
|
leaves.sort((a, b) => b.value - a.value);
|
|
543
571
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
}
|
|
572
|
+
const selectLeavesForTargets = (
|
|
573
|
+
targetAmounts: number[],
|
|
574
|
+
leaves: TreeNode[],
|
|
575
|
+
) => {
|
|
576
|
+
const usedLeaves = new Set<string>();
|
|
577
|
+
const results: Map<number, TreeNode[]> = new Map();
|
|
578
|
+
let totalAmount = 0;
|
|
552
579
|
|
|
553
|
-
|
|
554
|
-
|
|
580
|
+
for (const targetAmount of targetAmounts) {
|
|
581
|
+
const nodes: TreeNode[] = [];
|
|
582
|
+
let amount = 0;
|
|
555
583
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
584
|
+
for (const leaf of leaves) {
|
|
585
|
+
if (usedLeaves.has(leaf.id)) {
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
if (targetAmount - amount >= leaf.value) {
|
|
590
|
+
amount += leaf.value;
|
|
591
|
+
nodes.push(leaf);
|
|
592
|
+
usedLeaves.add(leaf.id);
|
|
593
|
+
}
|
|
564
594
|
}
|
|
595
|
+
|
|
596
|
+
totalAmount += amount;
|
|
597
|
+
results.set(targetAmount, nodes);
|
|
565
598
|
}
|
|
599
|
+
|
|
600
|
+
return {
|
|
601
|
+
results,
|
|
602
|
+
foundSelections: totalAmount === totalTargetAmount,
|
|
603
|
+
};
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
let { results, foundSelections } = selectLeavesForTargets(
|
|
607
|
+
targetAmounts,
|
|
608
|
+
leaves,
|
|
609
|
+
);
|
|
610
|
+
|
|
611
|
+
if (!foundSelections) {
|
|
612
|
+
const newLeaves = await this.requestLeavesSwap({ targetAmounts });
|
|
613
|
+
|
|
614
|
+
newLeaves.sort((a, b) => b.value - a.value);
|
|
615
|
+
|
|
616
|
+
({ results, foundSelections } = selectLeavesForTargets(
|
|
617
|
+
targetAmounts,
|
|
618
|
+
newLeaves,
|
|
619
|
+
));
|
|
566
620
|
}
|
|
567
621
|
|
|
568
|
-
if (
|
|
622
|
+
if (!foundSelections) {
|
|
569
623
|
throw new Error(
|
|
570
|
-
`Failed to select leaves for target amount ${
|
|
624
|
+
`Failed to select leaves for target amount ${totalTargetAmount}`,
|
|
571
625
|
);
|
|
572
626
|
}
|
|
573
627
|
|
|
574
|
-
return
|
|
628
|
+
return results;
|
|
575
629
|
}
|
|
576
630
|
|
|
577
631
|
private async selectLeavesForSwap(targetAmount: number) {
|
|
@@ -853,32 +907,57 @@ export class SparkWallet extends EventEmitter {
|
|
|
853
907
|
* @private
|
|
854
908
|
*/
|
|
855
909
|
private async requestLeavesSwap({
|
|
856
|
-
|
|
910
|
+
targetAmounts,
|
|
857
911
|
leaves,
|
|
858
912
|
}: {
|
|
859
|
-
|
|
913
|
+
targetAmounts?: number[];
|
|
860
914
|
leaves?: TreeNode[];
|
|
861
|
-
}) {
|
|
862
|
-
if (
|
|
863
|
-
throw new Error("targetAmount must be positive");
|
|
915
|
+
}): Promise<TreeNode[]> {
|
|
916
|
+
if (targetAmounts && targetAmounts.some((amount) => amount <= 0)) {
|
|
917
|
+
throw new Error("specified targetAmount must be positive");
|
|
864
918
|
}
|
|
865
919
|
|
|
866
|
-
if (
|
|
920
|
+
if (
|
|
921
|
+
targetAmounts &&
|
|
922
|
+
targetAmounts.some((amount) => !Number.isSafeInteger(amount))
|
|
923
|
+
) {
|
|
867
924
|
throw new ValidationError("targetAmount must be less than 2^53", {
|
|
868
|
-
field: "
|
|
869
|
-
value:
|
|
925
|
+
field: "targetAmounts",
|
|
926
|
+
value: targetAmounts,
|
|
870
927
|
expected: "smaller or equal to " + Number.MAX_SAFE_INTEGER,
|
|
871
928
|
});
|
|
872
929
|
}
|
|
873
930
|
|
|
874
931
|
let leavesToSwap: TreeNode[];
|
|
875
|
-
|
|
876
|
-
|
|
932
|
+
const totalTargetAmount = targetAmounts?.reduce(
|
|
933
|
+
(acc, amount) => acc + amount,
|
|
934
|
+
0,
|
|
935
|
+
);
|
|
936
|
+
|
|
937
|
+
if (totalTargetAmount) {
|
|
938
|
+
const totalBalance = this.getInternalBalance();
|
|
939
|
+
|
|
940
|
+
if (totalTargetAmount > totalBalance) {
|
|
941
|
+
throw new ValidationError(
|
|
942
|
+
"Total target amount exceeds available balance",
|
|
943
|
+
{
|
|
944
|
+
field: "targetAmounts",
|
|
945
|
+
value: totalTargetAmount,
|
|
946
|
+
expected: `less than or equal to ${totalBalance}`,
|
|
947
|
+
},
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
if (totalTargetAmount && leaves && leaves.length > 0) {
|
|
953
|
+
if (
|
|
954
|
+
totalTargetAmount < leaves.reduce((acc, leaf) => acc + leaf.value, 0)
|
|
955
|
+
) {
|
|
877
956
|
throw new Error("targetAmount is less than the sum of leaves");
|
|
878
957
|
}
|
|
879
958
|
leavesToSwap = leaves;
|
|
880
|
-
} else if (
|
|
881
|
-
leavesToSwap = await this.selectLeavesForSwap(
|
|
959
|
+
} else if (totalTargetAmount) {
|
|
960
|
+
leavesToSwap = await this.selectLeavesForSwap(totalTargetAmount);
|
|
882
961
|
} else if (leaves && leaves.length > 0) {
|
|
883
962
|
leavesToSwap = leaves;
|
|
884
963
|
} else {
|
|
@@ -889,10 +968,10 @@ export class SparkWallet extends EventEmitter {
|
|
|
889
968
|
|
|
890
969
|
const batches = chunkArray(leavesToSwap, 100);
|
|
891
970
|
|
|
892
|
-
const results:
|
|
971
|
+
const results: TreeNode[] = [];
|
|
893
972
|
for (const batch of batches) {
|
|
894
|
-
const result = await this.processSwapBatch(batch,
|
|
895
|
-
results.push(...result
|
|
973
|
+
const result = await this.processSwapBatch(batch, targetAmounts);
|
|
974
|
+
results.push(...result);
|
|
896
975
|
}
|
|
897
976
|
|
|
898
977
|
return results;
|
|
@@ -903,8 +982,8 @@ export class SparkWallet extends EventEmitter {
|
|
|
903
982
|
*/
|
|
904
983
|
private async processSwapBatch(
|
|
905
984
|
leavesBatch: TreeNode[],
|
|
906
|
-
|
|
907
|
-
): Promise<
|
|
985
|
+
targetAmounts?: number[],
|
|
986
|
+
): Promise<TreeNode[]> {
|
|
908
987
|
const leafKeyTweaks = await Promise.all(
|
|
909
988
|
leavesBatch.map(async (leaf) => ({
|
|
910
989
|
leaf,
|
|
@@ -981,9 +1060,10 @@ export class SparkWallet extends EventEmitter {
|
|
|
981
1060
|
userLeaves,
|
|
982
1061
|
adaptorPubkey,
|
|
983
1062
|
targetAmountSats:
|
|
984
|
-
|
|
1063
|
+
targetAmounts?.reduce((acc, amount) => acc + amount, 0) ||
|
|
985
1064
|
leavesBatch.reduce((acc, leaf) => acc + leaf.value, 0),
|
|
986
1065
|
totalAmountSats: leavesBatch.reduce((acc, leaf) => acc + leaf.value, 0),
|
|
1066
|
+
targetAmountSatsList: targetAmounts,
|
|
987
1067
|
// TODO: Request fee from SSP
|
|
988
1068
|
feeSats: 0,
|
|
989
1069
|
idempotencyKey: uuidv7(),
|
|
@@ -1051,13 +1131,24 @@ export class SparkWallet extends EventEmitter {
|
|
|
1051
1131
|
leavesSwapRequestId: request.id,
|
|
1052
1132
|
});
|
|
1053
1133
|
|
|
1054
|
-
if (!completeResponse) {
|
|
1134
|
+
if (!completeResponse || !completeResponse.inboundTransfer?.sparkId) {
|
|
1055
1135
|
throw new Error("Failed to complete leaves swap");
|
|
1056
1136
|
}
|
|
1057
1137
|
|
|
1058
|
-
await this.
|
|
1138
|
+
const incomingTransfer = await this.transferService.queryTransfer(
|
|
1139
|
+
completeResponse.inboundTransfer.sparkId,
|
|
1140
|
+
);
|
|
1059
1141
|
|
|
1060
|
-
|
|
1142
|
+
if (!incomingTransfer) {
|
|
1143
|
+
throw new Error("Failed to get incoming transfer");
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
return await this.claimTransfer({
|
|
1147
|
+
transfer: incomingTransfer,
|
|
1148
|
+
emit: false,
|
|
1149
|
+
retryCount: 0,
|
|
1150
|
+
optimize: false,
|
|
1151
|
+
});
|
|
1061
1152
|
} catch (e) {
|
|
1062
1153
|
await this.cancelAllSenderInitiatedTransfers();
|
|
1063
1154
|
throw new Error(`Failed to request leaves swap: ${e}`);
|
|
@@ -1093,32 +1184,6 @@ export class SparkWallet extends EventEmitter {
|
|
|
1093
1184
|
};
|
|
1094
1185
|
}
|
|
1095
1186
|
|
|
1096
|
-
/**
|
|
1097
|
-
* Gets the held token info for the wallet.
|
|
1098
|
-
*
|
|
1099
|
-
* @deprecated The information is returned in getBalance
|
|
1100
|
-
*/
|
|
1101
|
-
public async getTokenInfo(): Promise<TokenInfo[]> {
|
|
1102
|
-
console.warn("getTokenInfo is deprecated. Use getBalance instead.");
|
|
1103
|
-
|
|
1104
|
-
await this.syncTokenOutputs();
|
|
1105
|
-
|
|
1106
|
-
const lrc20Client = await this.lrc20ConnectionManager.createLrc20Client();
|
|
1107
|
-
const { balance, tokenBalances } = await this.getBalance();
|
|
1108
|
-
|
|
1109
|
-
const tokenInfo = await lrc20Client.getTokenPubkeyInfo({
|
|
1110
|
-
publicKeys: Array.from(tokenBalances.keys()).map(hexToBytes),
|
|
1111
|
-
});
|
|
1112
|
-
|
|
1113
|
-
return tokenInfo.tokenPubkeyInfos.map((info) => ({
|
|
1114
|
-
tokenPublicKey: bytesToHex(info.announcement!.publicKey!.publicKey),
|
|
1115
|
-
tokenName: info.announcement!.name,
|
|
1116
|
-
tokenSymbol: info.announcement!.symbol,
|
|
1117
|
-
tokenDecimals: Number(bytesToNumberBE(info.announcement!.decimal)),
|
|
1118
|
-
maxSupply: bytesToNumberBE(info.announcement!.maxSupply),
|
|
1119
|
-
}));
|
|
1120
|
-
}
|
|
1121
|
-
|
|
1122
1187
|
/**
|
|
1123
1188
|
* Gets the current balance of the wallet.
|
|
1124
1189
|
* You can use the forceRefetch option to synchronize your wallet and claim any
|
|
@@ -1126,24 +1191,21 @@ export class SparkWallet extends EventEmitter {
|
|
|
1126
1191
|
*
|
|
1127
1192
|
* @returns {Promise<Object>} Object containing:
|
|
1128
1193
|
* - balance: The wallet's current balance in satoshis
|
|
1129
|
-
* - tokenBalances: Map of token
|
|
1194
|
+
* - tokenBalances: Map of the human readable token identifier to token balances and token info
|
|
1130
1195
|
*/
|
|
1131
1196
|
public async getBalance(): Promise<{
|
|
1132
1197
|
balance: bigint;
|
|
1133
|
-
tokenBalances:
|
|
1198
|
+
tokenBalances: TokenBalanceMap;
|
|
1134
1199
|
}> {
|
|
1135
1200
|
const leaves = await this.getLeaves(true);
|
|
1136
1201
|
await this.syncTokenOutputs();
|
|
1137
1202
|
|
|
1138
|
-
let tokenBalances:
|
|
1203
|
+
let tokenBalances: TokenBalanceMap;
|
|
1139
1204
|
|
|
1140
1205
|
if (this.tokenOutputs.size !== 0) {
|
|
1141
1206
|
tokenBalances = await this.getTokenBalance();
|
|
1142
1207
|
} else {
|
|
1143
|
-
tokenBalances = new Map
|
|
1144
|
-
string,
|
|
1145
|
-
{ balance: bigint; tokenInfo: TokenInfo }
|
|
1146
|
-
>();
|
|
1208
|
+
tokenBalances = new Map();
|
|
1147
1209
|
}
|
|
1148
1210
|
|
|
1149
1211
|
return {
|
|
@@ -1152,32 +1214,33 @@ export class SparkWallet extends EventEmitter {
|
|
|
1152
1214
|
};
|
|
1153
1215
|
}
|
|
1154
1216
|
|
|
1155
|
-
private async getTokenBalance(): Promise<
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1217
|
+
private async getTokenBalance(): Promise<TokenBalanceMap> {
|
|
1218
|
+
const sparkTokenClient =
|
|
1219
|
+
await this.connectionManager.createSparkTokenClient(
|
|
1220
|
+
this.config.getCoordinatorAddress(),
|
|
1221
|
+
);
|
|
1159
1222
|
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
publicKeys: Array.from(this.tokenOutputs.keys()).map(hexToBytes),
|
|
1223
|
+
const tokenMetadata = await sparkTokenClient.query_token_metadata({
|
|
1224
|
+
issuerPublicKeys: Array.from(this.tokenOutputs.keys()).map(hexToBytes),
|
|
1163
1225
|
});
|
|
1226
|
+
const result: TokenBalanceMap = new Map();
|
|
1164
1227
|
|
|
1165
|
-
const
|
|
1166
|
-
|
|
1167
|
-
for (const info of tokenInfo.tokenPubkeyInfos) {
|
|
1168
|
-
const tokenPublicKey = bytesToHex(
|
|
1169
|
-
info.announcement!.publicKey!.publicKey,
|
|
1170
|
-
);
|
|
1228
|
+
for (const metadata of tokenMetadata.tokenMetadata) {
|
|
1229
|
+
const tokenPublicKey = bytesToHex(metadata.issuerPublicKey);
|
|
1171
1230
|
const leaves = this.tokenOutputs.get(tokenPublicKey);
|
|
1172
|
-
|
|
1173
|
-
|
|
1231
|
+
const humanReadableTokenIdentifier = encodeHumanReadableTokenIdentifier({
|
|
1232
|
+
tokenIdentifier: metadata.tokenIdentifier,
|
|
1233
|
+
network: this.config.getNetworkType(),
|
|
1234
|
+
});
|
|
1235
|
+
result.set(humanReadableTokenIdentifier, {
|
|
1174
1236
|
balance: leaves ? calculateAvailableTokenAmount(leaves) : BigInt(0),
|
|
1175
|
-
|
|
1237
|
+
tokenMetadata: {
|
|
1176
1238
|
tokenPublicKey,
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1239
|
+
rawTokenIdentifier: metadata.tokenIdentifier,
|
|
1240
|
+
tokenName: metadata.tokenName,
|
|
1241
|
+
tokenTicker: metadata.tokenTicker,
|
|
1242
|
+
decimals: metadata.decimals,
|
|
1243
|
+
maxSupply: bytesToNumberBE(metadata.maxSupply),
|
|
1181
1244
|
},
|
|
1182
1245
|
});
|
|
1183
1246
|
}
|
|
@@ -1750,16 +1813,60 @@ export class SparkWallet extends EventEmitter {
|
|
|
1750
1813
|
* @returns {Promise<string[]>} The unused deposit addresses
|
|
1751
1814
|
*/
|
|
1752
1815
|
public async getUnusedDepositAddresses(): Promise<string[]> {
|
|
1816
|
+
return (await this.queryAllUnusedDepositAddresses({})).map(
|
|
1817
|
+
(addr) => addr.depositAddress,
|
|
1818
|
+
);
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
/**
|
|
1822
|
+
* Gets all unused deposit addresses for the wallet.
|
|
1823
|
+
*
|
|
1824
|
+
* @param {Object} params - Parameters for querying unused deposit addresses
|
|
1825
|
+
* @param {Uint8Array<ArrayBufferLike>} [params.identityPublicKey] - The identity public key
|
|
1826
|
+
* @param {NetworkProto} [params.network] - The network
|
|
1827
|
+
* @returns {Promise<DepositAddressQueryResult[]>} The unused deposit addresses
|
|
1828
|
+
*/
|
|
1829
|
+
private async queryAllUnusedDepositAddresses({
|
|
1830
|
+
identityPublicKey,
|
|
1831
|
+
network,
|
|
1832
|
+
}: {
|
|
1833
|
+
identityPublicKey?: Uint8Array<ArrayBufferLike>;
|
|
1834
|
+
network?: NetworkProto | undefined;
|
|
1835
|
+
}): Promise<DepositAddressQueryResult[]> {
|
|
1753
1836
|
const sparkClient = await this.connectionManager.createSparkClient(
|
|
1754
1837
|
this.config.getCoordinatorAddress(),
|
|
1755
1838
|
);
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1839
|
+
|
|
1840
|
+
let limit = 100;
|
|
1841
|
+
let offset = 0;
|
|
1842
|
+
const pastOffsets = new Set<number>();
|
|
1843
|
+
const depositAddresses: DepositAddressQueryResult[] = [];
|
|
1844
|
+
|
|
1845
|
+
while (offset >= 0) {
|
|
1846
|
+
// Prevent infinite loop in case error with coordinator
|
|
1847
|
+
if (pastOffsets.has(offset)) {
|
|
1848
|
+
console.warn("Offset has already been seen, stopping");
|
|
1849
|
+
break;
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
const response = await sparkClient.query_unused_deposit_addresses({
|
|
1853
|
+
identityPublicKey:
|
|
1854
|
+
identityPublicKey ??
|
|
1855
|
+
(await this.config.signer.getIdentityPublicKey()),
|
|
1856
|
+
network: network ?? NetworkToProto[this.config.getNetwork()],
|
|
1857
|
+
limit,
|
|
1858
|
+
offset,
|
|
1859
|
+
});
|
|
1860
|
+
|
|
1861
|
+
depositAddresses.push(...response.depositAddresses);
|
|
1862
|
+
|
|
1863
|
+
pastOffsets.add(offset);
|
|
1864
|
+
offset = response.offset;
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
return depositAddresses;
|
|
1762
1868
|
}
|
|
1869
|
+
|
|
1763
1870
|
/**
|
|
1764
1871
|
* Claims a deposit to the wallet.
|
|
1765
1872
|
* Note that if you used advancedDeposit, you don't need to call this function.
|
|
@@ -1816,19 +1923,15 @@ export class SparkWallet extends EventEmitter {
|
|
|
1816
1923
|
}
|
|
1817
1924
|
const depositTx = getTxFromRawTxHex(txHex);
|
|
1818
1925
|
|
|
1819
|
-
const sparkClient = await this.connectionManager.createSparkClient(
|
|
1820
|
-
this.config.getCoordinatorAddress(),
|
|
1821
|
-
);
|
|
1822
|
-
|
|
1823
1926
|
const unusedDepositAddresses: Map<string, DepositAddressQueryResult> =
|
|
1824
1927
|
new Map(
|
|
1825
1928
|
(
|
|
1826
|
-
await
|
|
1929
|
+
await this.queryAllUnusedDepositAddresses({
|
|
1827
1930
|
identityPublicKey:
|
|
1828
1931
|
await this.config.signer.getIdentityPublicKey(),
|
|
1829
1932
|
network: NetworkToProto[this.config.getNetwork()],
|
|
1830
1933
|
})
|
|
1831
|
-
).
|
|
1934
|
+
).map((addr) => [addr.depositAddress, addr]),
|
|
1832
1935
|
);
|
|
1833
1936
|
let depositAddress: DepositAddressQueryResult | undefined;
|
|
1834
1937
|
let vout = 0;
|
|
@@ -1888,17 +1991,15 @@ export class SparkWallet extends EventEmitter {
|
|
|
1888
1991
|
*/
|
|
1889
1992
|
public async advancedDeposit(txHex: string) {
|
|
1890
1993
|
const depositTx = getTxFromRawTxHex(txHex);
|
|
1891
|
-
|
|
1892
|
-
this.config.getCoordinatorAddress(),
|
|
1893
|
-
);
|
|
1994
|
+
|
|
1894
1995
|
const unusedDepositAddresses: Map<string, DepositAddressQueryResult> =
|
|
1895
1996
|
new Map(
|
|
1896
1997
|
(
|
|
1897
|
-
await
|
|
1998
|
+
await this.queryAllUnusedDepositAddresses({
|
|
1898
1999
|
identityPublicKey: await this.config.signer.getIdentityPublicKey(),
|
|
1899
2000
|
network: NetworkToProto[this.config.getNetwork()],
|
|
1900
2001
|
})
|
|
1901
|
-
).
|
|
2002
|
+
).map((addr) => [addr.depositAddress, addr]),
|
|
1902
2003
|
);
|
|
1903
2004
|
|
|
1904
2005
|
let vout = 0;
|
|
@@ -2023,7 +2124,9 @@ export class SparkWallet extends EventEmitter {
|
|
|
2023
2124
|
);
|
|
2024
2125
|
|
|
2025
2126
|
return await this.withLeaves(async () => {
|
|
2026
|
-
let leavesToSend = await this.selectLeaves(amountSats)
|
|
2127
|
+
let leavesToSend = (await this.selectLeaves([amountSats])).get(
|
|
2128
|
+
amountSats,
|
|
2129
|
+
)!;
|
|
2027
2130
|
|
|
2028
2131
|
leavesToSend = await this.checkRefreshTimelockNodes(leavesToSend);
|
|
2029
2132
|
leavesToSend = await this.checkExtendTimeLockNodes(leavesToSend);
|
|
@@ -2325,7 +2428,7 @@ export class SparkWallet extends EventEmitter {
|
|
|
2325
2428
|
transfer.status !==
|
|
2326
2429
|
TransferStatus.TRANSFER_STATUS_RECEIVER_KEY_TWEAKED &&
|
|
2327
2430
|
transfer.status !==
|
|
2328
|
-
TransferStatus.
|
|
2431
|
+
TransferStatus.TRANSFER_STATUS_RECEIVER_REFUND_SIGNED &&
|
|
2329
2432
|
transfer.status !==
|
|
2330
2433
|
TransferStatus.TRANSFER_STATUS_RECEIVER_KEY_TWEAK_APPLIED
|
|
2331
2434
|
) {
|
|
@@ -2482,27 +2585,9 @@ export class SparkWallet extends EventEmitter {
|
|
|
2482
2585
|
invoice.invoice.encodedInvoice,
|
|
2483
2586
|
).fallbackAddress;
|
|
2484
2587
|
|
|
2485
|
-
if (
|
|
2486
|
-
sparkFallbackAddress &&
|
|
2487
|
-
isValidSparkFallback(hexToBytes(sparkFallbackAddress))
|
|
2488
|
-
) {
|
|
2489
|
-
const invoiceIdentityPubkey = sparkFallbackAddress.slice(6); // remove the 3 byte header
|
|
2490
|
-
const expectedIdentityPubkey =
|
|
2491
|
-
receiverIdentityPubkey ?? (await this.getIdentityPublicKey());
|
|
2492
|
-
|
|
2493
|
-
if (invoiceIdentityPubkey !== expectedIdentityPubkey) {
|
|
2494
|
-
throw new ValidationError(
|
|
2495
|
-
"Mismatch between spark identity embedded in lightning invoice and designated recipient spark identity",
|
|
2496
|
-
{
|
|
2497
|
-
field: "sparkFallbackAddress",
|
|
2498
|
-
value: invoiceIdentityPubkey,
|
|
2499
|
-
expected: expectedIdentityPubkey,
|
|
2500
|
-
},
|
|
2501
|
-
);
|
|
2502
|
-
}
|
|
2503
|
-
} else {
|
|
2588
|
+
if (!sparkFallbackAddress) {
|
|
2504
2589
|
throw new ValidationError(
|
|
2505
|
-
"No
|
|
2590
|
+
"No spark fallback address found in lightning invoice",
|
|
2506
2591
|
{
|
|
2507
2592
|
field: "sparkFallbackAddress",
|
|
2508
2593
|
value: sparkFallbackAddress,
|
|
@@ -2510,6 +2595,20 @@ export class SparkWallet extends EventEmitter {
|
|
|
2510
2595
|
},
|
|
2511
2596
|
);
|
|
2512
2597
|
}
|
|
2598
|
+
|
|
2599
|
+
const expectedIdentityPubkey =
|
|
2600
|
+
receiverIdentityPubkey ?? (await this.getIdentityPublicKey());
|
|
2601
|
+
|
|
2602
|
+
if (sparkFallbackAddress !== expectedIdentityPubkey) {
|
|
2603
|
+
throw new ValidationError(
|
|
2604
|
+
"Mismatch between spark identity embedded in lightning invoice and designated recipient spark identity",
|
|
2605
|
+
{
|
|
2606
|
+
field: "sparkFallbackAddress",
|
|
2607
|
+
value: sparkFallbackAddress,
|
|
2608
|
+
expected: expectedIdentityPubkey,
|
|
2609
|
+
},
|
|
2610
|
+
);
|
|
2611
|
+
}
|
|
2513
2612
|
}
|
|
2514
2613
|
|
|
2515
2614
|
return invoice;
|
|
@@ -2607,9 +2706,8 @@ export class SparkWallet extends EventEmitter {
|
|
|
2607
2706
|
"No valid spark address found in invoice. Defaulting to lightning.",
|
|
2608
2707
|
);
|
|
2609
2708
|
} else {
|
|
2610
|
-
const identityPublicKey = sparkFallbackAddress.slice(6); // remove the 3 byte header
|
|
2611
2709
|
const receiverSparkAddress = encodeSparkAddress({
|
|
2612
|
-
identityPublicKey,
|
|
2710
|
+
identityPublicKey: sparkFallbackAddress,
|
|
2613
2711
|
network: Network[invoiceNetwork] as NetworkType,
|
|
2614
2712
|
});
|
|
2615
2713
|
return await this.transfer({
|
|
@@ -2648,7 +2746,7 @@ export class SparkWallet extends EventEmitter {
|
|
|
2648
2746
|
});
|
|
2649
2747
|
}
|
|
2650
2748
|
|
|
2651
|
-
let leaves = await this.selectLeaves(totalAmount)
|
|
2749
|
+
let leaves = (await this.selectLeaves([totalAmount])).get(totalAmount)!;
|
|
2652
2750
|
|
|
2653
2751
|
leaves = await this.checkRefreshTimelockNodes(leaves);
|
|
2654
2752
|
leaves = await this.checkExtendTimeLockNodes(leaves);
|
|
@@ -2786,17 +2884,24 @@ export class SparkWallet extends EventEmitter {
|
|
|
2786
2884
|
*
|
|
2787
2885
|
* @param {Object} params - Parameters for the withdrawal
|
|
2788
2886
|
* @param {string} params.onchainAddress - The Bitcoin address where the funds should be sent
|
|
2789
|
-
* @param {
|
|
2887
|
+
* @param {CoopExitFeeQuote} params.feeQuote - The fee quote for the withdrawal
|
|
2888
|
+
* @param {ExitSpeed} params.exitSpeed - The exit speed chosen for the withdrawal
|
|
2889
|
+
* @param {number} [params.amountSats] - The amount in satoshis to withdraw. If not specified, attempts to withdraw all available funds and deductFeeFromWithdrawalAmount is set to true.
|
|
2890
|
+
* @param {boolean} [params.deductFeeFromWithdrawalAmount] - Controls how the withdrawal fee is handled. If true, the fee is deducted from the withdrawal amount (amountSats), meaning the recipient will receive amountSats minus the fee. If false, the fee is paid separately from the wallet balance, meaning the recipient will receive the full amountSats.
|
|
2790
2891
|
* @returns {Promise<CoopExitRequest | null | undefined>} The withdrawal request details, or null/undefined if the request cannot be completed
|
|
2791
2892
|
*/
|
|
2792
2893
|
public async withdraw({
|
|
2793
2894
|
onchainAddress,
|
|
2794
2895
|
exitSpeed,
|
|
2896
|
+
feeQuote,
|
|
2795
2897
|
amountSats,
|
|
2898
|
+
deductFeeFromWithdrawalAmount = true,
|
|
2796
2899
|
}: {
|
|
2797
2900
|
onchainAddress: string;
|
|
2798
2901
|
exitSpeed: ExitSpeed;
|
|
2902
|
+
feeQuote: CoopExitFeeQuote;
|
|
2799
2903
|
amountSats?: number;
|
|
2904
|
+
deductFeeFromWithdrawalAmount?: boolean;
|
|
2800
2905
|
}) {
|
|
2801
2906
|
if (!Number.isSafeInteger(amountSats)) {
|
|
2802
2907
|
throw new ValidationError("Sats amount must be less than 2^53", {
|
|
@@ -2806,7 +2911,13 @@ export class SparkWallet extends EventEmitter {
|
|
|
2806
2911
|
});
|
|
2807
2912
|
}
|
|
2808
2913
|
return await this.withLeaves(async () => {
|
|
2809
|
-
return await this.coopExit(
|
|
2914
|
+
return await this.coopExit(
|
|
2915
|
+
onchainAddress,
|
|
2916
|
+
feeQuote,
|
|
2917
|
+
exitSpeed,
|
|
2918
|
+
deductFeeFromWithdrawalAmount,
|
|
2919
|
+
amountSats,
|
|
2920
|
+
);
|
|
2810
2921
|
});
|
|
2811
2922
|
}
|
|
2812
2923
|
|
|
@@ -2820,7 +2931,9 @@ export class SparkWallet extends EventEmitter {
|
|
|
2820
2931
|
*/
|
|
2821
2932
|
private async coopExit(
|
|
2822
2933
|
onchainAddress: string,
|
|
2934
|
+
feeEstimate: CoopExitFeeQuote,
|
|
2823
2935
|
exitSpeed: ExitSpeed,
|
|
2936
|
+
deductFeeFromWithdrawalAmount: boolean,
|
|
2824
2937
|
targetAmountSats?: number,
|
|
2825
2938
|
) {
|
|
2826
2939
|
if (!Number.isSafeInteger(targetAmountSats)) {
|
|
@@ -2831,53 +2944,46 @@ export class SparkWallet extends EventEmitter {
|
|
|
2831
2944
|
});
|
|
2832
2945
|
}
|
|
2833
2946
|
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
leavesToSend = await this.selectLeaves(targetAmountSats);
|
|
2837
|
-
} else {
|
|
2838
|
-
leavesToSend = this.leaves.map((leaf) => ({
|
|
2839
|
-
...leaf,
|
|
2840
|
-
}));
|
|
2947
|
+
if (!targetAmountSats) {
|
|
2948
|
+
deductFeeFromWithdrawalAmount = true;
|
|
2841
2949
|
}
|
|
2842
2950
|
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2951
|
+
let fee: number | undefined;
|
|
2952
|
+
switch (exitSpeed) {
|
|
2953
|
+
case ExitSpeed.FAST:
|
|
2954
|
+
fee =
|
|
2955
|
+
(feeEstimate.l1BroadcastFeeFast?.originalValue || 0) +
|
|
2956
|
+
(feeEstimate.userFeeFast?.originalValue || 0);
|
|
2957
|
+
break;
|
|
2958
|
+
case ExitSpeed.MEDIUM:
|
|
2959
|
+
fee =
|
|
2960
|
+
(feeEstimate.l1BroadcastFeeMedium?.originalValue || 0) +
|
|
2961
|
+
(feeEstimate.userFeeMedium?.originalValue || 0);
|
|
2962
|
+
break;
|
|
2963
|
+
case ExitSpeed.SLOW:
|
|
2964
|
+
fee =
|
|
2965
|
+
(feeEstimate.l1BroadcastFeeSlow?.originalValue || 0) +
|
|
2966
|
+
(feeEstimate.userFeeSlow?.originalValue || 0);
|
|
2967
|
+
break;
|
|
2968
|
+
default:
|
|
2969
|
+
throw new ValidationError("Invalid exit speed", {
|
|
2970
|
+
field: "exitSpeed",
|
|
2971
|
+
value: exitSpeed,
|
|
2972
|
+
expected: "FAST, MEDIUM, or SLOW",
|
|
2973
|
+
});
|
|
2974
|
+
}
|
|
2848
2975
|
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
switch (exitSpeed) {
|
|
2852
|
-
case ExitSpeed.FAST:
|
|
2853
|
-
fee =
|
|
2854
|
-
(feeEstimate.speedFast?.l1BroadcastFee.originalValue || 0) +
|
|
2855
|
-
(feeEstimate.speedFast?.userFee.originalValue || 0);
|
|
2856
|
-
break;
|
|
2857
|
-
case ExitSpeed.MEDIUM:
|
|
2858
|
-
fee =
|
|
2859
|
-
(feeEstimate.speedMedium?.l1BroadcastFee.originalValue || 0) +
|
|
2860
|
-
(feeEstimate.speedMedium?.userFee.originalValue || 0);
|
|
2861
|
-
break;
|
|
2862
|
-
case ExitSpeed.SLOW:
|
|
2863
|
-
fee =
|
|
2864
|
-
(feeEstimate.speedSlow?.l1BroadcastFee.originalValue || 0) +
|
|
2865
|
-
(feeEstimate.speedSlow?.userFee.originalValue || 0);
|
|
2866
|
-
break;
|
|
2867
|
-
default:
|
|
2868
|
-
throw new ValidationError("Invalid exit speed", {
|
|
2869
|
-
field: "exitSpeed",
|
|
2870
|
-
value: exitSpeed,
|
|
2871
|
-
expected: "FAST, MEDIUM, or SLOW",
|
|
2872
|
-
});
|
|
2873
|
-
}
|
|
2976
|
+
let leavesToSendToSsp: TreeNode[] = [];
|
|
2977
|
+
let leavesToSendToSE: TreeNode[] = [];
|
|
2874
2978
|
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2979
|
+
if (deductFeeFromWithdrawalAmount) {
|
|
2980
|
+
leavesToSendToSsp = targetAmountSats
|
|
2981
|
+
? (await this.selectLeaves([targetAmountSats])).get(targetAmountSats)!
|
|
2982
|
+
: this.leaves;
|
|
2983
|
+
|
|
2984
|
+
if (fee > leavesToSendToSsp.reduce((acc, leaf) => acc + leaf.value, 0)) {
|
|
2879
2985
|
throw new ValidationError(
|
|
2880
|
-
"The fee for the withdrawal is greater than the target amount",
|
|
2986
|
+
"The fee for the withdrawal is greater than the target withdrawal amount",
|
|
2881
2987
|
{
|
|
2882
2988
|
field: "fee",
|
|
2883
2989
|
value: fee,
|
|
@@ -2885,12 +2991,38 @@ export class SparkWallet extends EventEmitter {
|
|
|
2885
2991
|
},
|
|
2886
2992
|
);
|
|
2887
2993
|
}
|
|
2994
|
+
} else {
|
|
2995
|
+
if (!targetAmountSats) {
|
|
2996
|
+
throw new ValidationError(
|
|
2997
|
+
"targetAmountSats is required when deductFeeFromWithdrawalAmount is false",
|
|
2998
|
+
{
|
|
2999
|
+
field: "targetAmountSats",
|
|
3000
|
+
value: targetAmountSats,
|
|
3001
|
+
expected: "defined when deductFeeFromWithdrawalAmount is false",
|
|
3002
|
+
},
|
|
3003
|
+
);
|
|
3004
|
+
}
|
|
3005
|
+
|
|
3006
|
+
const leaves = await this.selectLeaves([targetAmountSats, fee]);
|
|
3007
|
+
|
|
3008
|
+
const leavesForTargetAmount = leaves.get(targetAmountSats);
|
|
3009
|
+
const leavesForFee = leaves.get(fee);
|
|
3010
|
+
|
|
3011
|
+
if (!leavesForTargetAmount || !leavesForFee) {
|
|
3012
|
+
throw new Error("Failed to select leaves for target amount and fee");
|
|
3013
|
+
}
|
|
3014
|
+
|
|
3015
|
+
leavesToSendToSsp = leavesForTargetAmount;
|
|
3016
|
+
leavesToSendToSE = leavesForFee;
|
|
2888
3017
|
}
|
|
2889
|
-
|
|
2890
|
-
|
|
3018
|
+
|
|
3019
|
+
leavesToSendToSsp = await this.checkRefreshTimelockNodes(leavesToSendToSsp);
|
|
3020
|
+
leavesToSendToSsp = await this.checkExtendTimeLockNodes(leavesToSendToSsp);
|
|
3021
|
+
leavesToSendToSE = await this.checkRefreshTimelockNodes(leavesToSendToSE);
|
|
3022
|
+
leavesToSendToSE = await this.checkExtendTimeLockNodes(leavesToSendToSE);
|
|
2891
3023
|
|
|
2892
3024
|
const leafKeyTweaks = await Promise.all(
|
|
2893
|
-
|
|
3025
|
+
[...leavesToSendToSE, ...leavesToSendToSsp].map(async (leaf) => ({
|
|
2894
3026
|
leaf,
|
|
2895
3027
|
signingPubKey: await this.config.signer.generatePublicKey(
|
|
2896
3028
|
sha256(leaf.id),
|
|
@@ -2899,13 +3031,26 @@ export class SparkWallet extends EventEmitter {
|
|
|
2899
3031
|
})),
|
|
2900
3032
|
);
|
|
2901
3033
|
|
|
2902
|
-
const
|
|
2903
|
-
leafExternalIds:
|
|
3034
|
+
const requestCoopExitParams: RequestCoopExitInput = {
|
|
3035
|
+
leafExternalIds: leavesToSendToSsp.map((leaf) => leaf.id),
|
|
2904
3036
|
withdrawalAddress: onchainAddress,
|
|
2905
3037
|
idempotencyKey: uuidv7(),
|
|
2906
3038
|
exitSpeed,
|
|
2907
|
-
withdrawAll:
|
|
2908
|
-
}
|
|
3039
|
+
withdrawAll: deductFeeFromWithdrawalAmount,
|
|
3040
|
+
};
|
|
3041
|
+
|
|
3042
|
+
if (!deductFeeFromWithdrawalAmount) {
|
|
3043
|
+
requestCoopExitParams.feeQuoteId = feeEstimate.id;
|
|
3044
|
+
requestCoopExitParams.feeLeafExternalIds = leavesToSendToSE.map(
|
|
3045
|
+
(leaf) => leaf.id,
|
|
3046
|
+
);
|
|
3047
|
+
}
|
|
3048
|
+
|
|
3049
|
+
const sspClient = this.getSspClient();
|
|
3050
|
+
|
|
3051
|
+
const coopExitRequest = await sspClient.requestCoopExit(
|
|
3052
|
+
requestCoopExitParams,
|
|
3053
|
+
);
|
|
2909
3054
|
|
|
2910
3055
|
if (!coopExitRequest?.rawConnectorTransaction) {
|
|
2911
3056
|
throw new Error("Failed to request coop exit");
|
|
@@ -2952,15 +3097,15 @@ export class SparkWallet extends EventEmitter {
|
|
|
2952
3097
|
* @param {Object} params - Input parameters for fee estimation
|
|
2953
3098
|
* @param {number} params.amountSats - The amount in satoshis to withdraw
|
|
2954
3099
|
* @param {string} params.withdrawalAddress - The Bitcoin address where the funds should be sent
|
|
2955
|
-
* @returns {Promise<
|
|
3100
|
+
* @returns {Promise<CoopExitFeeQuote | null>} Fee estimate for the withdrawal
|
|
2956
3101
|
*/
|
|
2957
|
-
public async
|
|
3102
|
+
public async getWithdrawalFeeQuote({
|
|
2958
3103
|
amountSats,
|
|
2959
3104
|
withdrawalAddress,
|
|
2960
3105
|
}: {
|
|
2961
3106
|
amountSats: number;
|
|
2962
3107
|
withdrawalAddress: string;
|
|
2963
|
-
}): Promise<
|
|
3108
|
+
}): Promise<CoopExitFeeQuote | null> {
|
|
2964
3109
|
const sspClient = this.getSspClient();
|
|
2965
3110
|
|
|
2966
3111
|
if (!Number.isSafeInteger(amountSats)) {
|
|
@@ -2971,12 +3116,12 @@ export class SparkWallet extends EventEmitter {
|
|
|
2971
3116
|
});
|
|
2972
3117
|
}
|
|
2973
3118
|
|
|
2974
|
-
let leaves = await this.selectLeaves(amountSats)
|
|
3119
|
+
let leaves = (await this.selectLeaves([amountSats])).get(amountSats)!;
|
|
2975
3120
|
|
|
2976
3121
|
leaves = await this.checkRefreshTimelockNodes(leaves);
|
|
2977
3122
|
leaves = await this.checkExtendTimeLockNodes(leaves);
|
|
2978
3123
|
|
|
2979
|
-
const feeEstimate = await sspClient.
|
|
3124
|
+
const feeEstimate = await sspClient.getCoopExitFeeQuote({
|
|
2980
3125
|
leafExternalIds: leaves.map((leaf) => leaf.id),
|
|
2981
3126
|
withdrawalAddress,
|
|
2982
3127
|
});
|
|
@@ -3005,8 +3150,15 @@ export class SparkWallet extends EventEmitter {
|
|
|
3005
3150
|
* @param {string} id - The ID of the transfer
|
|
3006
3151
|
* @returns {Promise<Transfer | undefined>} The transfer
|
|
3007
3152
|
*/
|
|
3008
|
-
public async getTransfer(id: string): Promise<
|
|
3009
|
-
|
|
3153
|
+
public async getTransfer(id: string): Promise<WalletTransfer | undefined> {
|
|
3154
|
+
const transfer = await this.transferService.queryTransfer(id);
|
|
3155
|
+
if (!transfer) {
|
|
3156
|
+
return undefined;
|
|
3157
|
+
}
|
|
3158
|
+
return mapTransferToWalletTransfer(
|
|
3159
|
+
transfer,
|
|
3160
|
+
bytesToHex(await this.config.signer.getIdentityPublicKey()),
|
|
3161
|
+
);
|
|
3010
3162
|
}
|
|
3011
3163
|
|
|
3012
3164
|
// ***** Token Flow *****
|
|
@@ -3021,10 +3173,9 @@ export class SparkWallet extends EventEmitter {
|
|
|
3021
3173
|
this.tokenOutputs.clear();
|
|
3022
3174
|
|
|
3023
3175
|
const unsortedTokenOutputs =
|
|
3024
|
-
await this.tokenTransactionService.fetchOwnedTokenOutputs(
|
|
3025
|
-
[await this.config.signer.getIdentityPublicKey()],
|
|
3026
|
-
|
|
3027
|
-
);
|
|
3176
|
+
await this.tokenTransactionService.fetchOwnedTokenOutputs({
|
|
3177
|
+
ownerPublicKeys: [await this.config.signer.getIdentityPublicKey()],
|
|
3178
|
+
});
|
|
3028
3179
|
|
|
3029
3180
|
const filteredTokenOutputs = unsortedTokenOutputs.filter(
|
|
3030
3181
|
(output) =>
|
|
@@ -3155,34 +3306,27 @@ export class SparkWallet extends EventEmitter {
|
|
|
3155
3306
|
* Retrieves token transaction history for specified tokens owned by the wallet.
|
|
3156
3307
|
* Can optionally filter by specific transaction hashes.
|
|
3157
3308
|
*
|
|
3158
|
-
* @param
|
|
3309
|
+
* @param ownerPublicKeys - Optional array of owner public keys to query transactions for
|
|
3310
|
+
* @param issuerPublicKeys - Optional array of issuer public keys to query transactions for
|
|
3159
3311
|
* @param tokenTransactionHashes - Optional array of specific transaction hashes to filter by
|
|
3312
|
+
* @param tokenIdentifiers - Optional array of token identifiers to filter by
|
|
3313
|
+
* @param outputIds - Optional array of output IDs to filter by
|
|
3160
3314
|
* @returns Promise resolving to array of token transactions with their current status
|
|
3161
3315
|
*/
|
|
3162
3316
|
public async queryTokenTransactions(
|
|
3163
|
-
|
|
3317
|
+
ownerPublicKeys?: string[],
|
|
3318
|
+
issuerPublicKeys?: string[],
|
|
3164
3319
|
tokenTransactionHashes?: string[],
|
|
3320
|
+
tokenIdentifiers?: string[],
|
|
3321
|
+
outputIds?: string[],
|
|
3165
3322
|
): Promise<TokenTransactionWithStatus[]> {
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
tokenPublicKeys: tokenPublicKeys?.map(hexToBytes)!,
|
|
3174
|
-
ownerPublicKeys: [hexToBytes(await this.getIdentityPublicKey())],
|
|
3175
|
-
tokenTransactionHashes: tokenTransactionHashes.map(hexToBytes),
|
|
3176
|
-
};
|
|
3177
|
-
} else {
|
|
3178
|
-
queryParams = {
|
|
3179
|
-
tokenPublicKeys: tokenPublicKeys?.map(hexToBytes)!,
|
|
3180
|
-
ownerPublicKeys: [hexToBytes(await this.getIdentityPublicKey())],
|
|
3181
|
-
};
|
|
3182
|
-
}
|
|
3183
|
-
|
|
3184
|
-
const response = await sparkClient.query_token_transactions(queryParams);
|
|
3185
|
-
return response.tokenTransactionsWithStatus;
|
|
3323
|
+
return this.tokenTransactionService.queryTokenTransactions({
|
|
3324
|
+
ownerPublicKeys,
|
|
3325
|
+
issuerPublicKeys,
|
|
3326
|
+
tokenTransactionHashes,
|
|
3327
|
+
tokenIdentifiers,
|
|
3328
|
+
outputIds,
|
|
3329
|
+
}) as Promise<TokenTransactionWithStatus[]>;
|
|
3186
3330
|
}
|
|
3187
3331
|
|
|
3188
3332
|
public async getTokenL1Address(): Promise<string> {
|