@buildonspark/spark-sdk 0.0.20 → 0.0.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/graphql/client.d.ts +4 -0
- package/dist/graphql/client.js +57 -0
- package/dist/graphql/client.js.map +1 -1
- package/dist/graphql/queries/InvoiceRequest.d.ts +1 -0
- package/dist/graphql/queries/InvoiceRequest.js +2 -0
- package/dist/graphql/queries/InvoiceRequest.js.map +1 -0
- package/dist/graphql/queries/UserRequest.d.ts +1 -0
- package/dist/graphql/queries/UserRequest.js +10 -0
- package/dist/graphql/queries/UserRequest.js.map +1 -0
- package/dist/services/lightning.d.ts +4 -3
- package/dist/services/lightning.js +1 -1
- package/dist/services/lightning.js.map +1 -1
- package/dist/services/transfer.d.ts +1 -1
- package/dist/services/transfer.js.map +1 -1
- package/dist/spark-sdk.d.ts +57 -11
- package/dist/spark-sdk.js +154 -81
- package/dist/spark-sdk.js.map +1 -1
- package/dist/tests/adaptor-signature.test.js +1 -1
- package/dist/tests/adaptor-signature.test.js.map +1 -1
- package/dist/tests/coop-exit.test.js +2 -2
- package/dist/tests/coop-exit.test.js.map +1 -1
- package/dist/tests/deposit.test.js +2 -2
- package/dist/tests/deposit.test.js.map +1 -1
- package/dist/tests/lightning.test.js +33 -4
- package/dist/tests/lightning.test.js.map +1 -1
- package/dist/tests/swap.test.js +2 -2
- package/dist/tests/swap.test.js.map +1 -1
- package/dist/tests/transfer.test.js +7 -7
- package/dist/tests/transfer.test.js.map +1 -1
- package/dist/tests/utils/spark-testing-wallet.d.ts +1 -1
- package/dist/tests/utils/spark-testing-wallet.js +1 -1
- package/dist/tests/utils/spark-testing-wallet.js.map +1 -1
- package/package.json +2 -2
- package/src/examples/example.ts +99 -19
- package/src/graphql/client.ts +69 -0
- package/src/graphql/queries/InvoiceRequest.ts +0 -0
- package/src/graphql/queries/UserRequest.ts +10 -0
- package/src/services/lightning.ts +5 -4
- package/src/services/transfer.ts +1 -1
- package/src/spark-sdk.ts +223 -98
- package/src/tests/adaptor-signature.test.ts +1 -1
- package/src/tests/coop-exit.test.ts +2 -2
- package/src/tests/deposit.test.ts +2 -2
- package/src/tests/lightning.test.ts +41 -5
- package/src/tests/swap.test.ts +2 -2
- package/src/tests/transfer.test.ts +7 -7
- package/src/tests/utils/spark-testing-wallet.ts +1 -1
|
@@ -7,6 +7,7 @@ import { secp256k1 } from "@noble/curves/secp256k1";
|
|
|
7
7
|
import { TransactionInput } from "@scure/btc-signer/psbt";
|
|
8
8
|
import { sha256 } from "@scure/btc-signer/utils";
|
|
9
9
|
import { decode } from "light-bolt11-decoder";
|
|
10
|
+
import LightningReceiveRequest from "../graphql/objects/LightningReceiveRequest.js";
|
|
10
11
|
import {
|
|
11
12
|
GetSigningCommitmentsResponse,
|
|
12
13
|
InitiatePreimageSwapRequest_Reason,
|
|
@@ -38,7 +39,7 @@ export type CreateLightningInvoiceParams = {
|
|
|
38
39
|
amountSats: number,
|
|
39
40
|
paymentHash: Uint8Array,
|
|
40
41
|
memo?: string,
|
|
41
|
-
) => Promise<
|
|
42
|
+
) => Promise<LightningReceiveRequest | null>;
|
|
42
43
|
amountSats: number;
|
|
43
44
|
memo?: string;
|
|
44
45
|
};
|
|
@@ -71,7 +72,7 @@ export class LightningService {
|
|
|
71
72
|
invoiceCreator,
|
|
72
73
|
amountSats,
|
|
73
74
|
memo,
|
|
74
|
-
}: CreateLightningInvoiceParams): Promise<
|
|
75
|
+
}: CreateLightningInvoiceParams): Promise<LightningReceiveRequest> {
|
|
75
76
|
const randBytes = crypto.getRandomValues(new Uint8Array(32));
|
|
76
77
|
const preimage = numberToBytesBE(
|
|
77
78
|
bytesToNumberBE(randBytes) % secp256k1.CURVE.n,
|
|
@@ -90,7 +91,7 @@ export class LightningService {
|
|
|
90
91
|
amountSats,
|
|
91
92
|
memo,
|
|
92
93
|
preimage,
|
|
93
|
-
}: CreateLightningInvoiceWithPreimageParams): Promise<
|
|
94
|
+
}: CreateLightningInvoiceWithPreimageParams): Promise<LightningReceiveRequest> {
|
|
94
95
|
const paymentHash = sha256(preimage);
|
|
95
96
|
const invoice = await invoiceCreator(amountSats, paymentHash, memo);
|
|
96
97
|
if (!invoice) {
|
|
@@ -124,7 +125,7 @@ export class LightningService {
|
|
|
124
125
|
proofs: share.proofs,
|
|
125
126
|
},
|
|
126
127
|
threshold: this.config.getThreshold(),
|
|
127
|
-
invoiceString: invoice,
|
|
128
|
+
invoiceString: invoice.invoice.encodedEnvoice,
|
|
128
129
|
userIdentityPublicKey:
|
|
129
130
|
await this.config.signer.getIdentityPublicKey(),
|
|
130
131
|
});
|
package/src/services/transfer.ts
CHANGED
|
@@ -1056,7 +1056,7 @@ export class TransferService extends BaseTransferService {
|
|
|
1056
1056
|
});
|
|
1057
1057
|
}
|
|
1058
1058
|
|
|
1059
|
-
|
|
1059
|
+
async extendTimelock(node: TreeNode, signingPubKey: Uint8Array) {
|
|
1060
1060
|
const nodeTx = getTxFromRawTxBytes(node.nodeTx);
|
|
1061
1061
|
const refundTx = getTxFromRawTxBytes(node.refundTx);
|
|
1062
1062
|
|
package/src/spark-sdk.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
bytesToHex,
|
|
3
|
-
hexToBytes,
|
|
4
3
|
bytesToNumberBE,
|
|
4
|
+
hexToBytes,
|
|
5
5
|
} from "@noble/curves/abstract/utils";
|
|
6
6
|
import { secp256k1 } from "@noble/curves/secp256k1";
|
|
7
7
|
import { Address, OutScript, Transaction } from "@scure/btc-signer";
|
|
@@ -11,14 +11,16 @@ import { decode } from "light-bolt11-decoder";
|
|
|
11
11
|
import SspClient from "./graphql/client.js";
|
|
12
12
|
import {
|
|
13
13
|
BitcoinNetwork,
|
|
14
|
-
CoopExitFeeEstimateInput,
|
|
15
14
|
CoopExitFeeEstimateOutput,
|
|
15
|
+
CoopExitRequest,
|
|
16
16
|
LeavesSwapFeeEstimateOutput,
|
|
17
17
|
LeavesSwapRequest,
|
|
18
18
|
LightningReceiveFeeEstimateInput,
|
|
19
19
|
LightningReceiveFeeEstimateOutput,
|
|
20
|
+
LightningReceiveRequest,
|
|
20
21
|
LightningSendFeeEstimateInput,
|
|
21
22
|
LightningSendFeeEstimateOutput,
|
|
23
|
+
LightningSendRequest,
|
|
22
24
|
UserLeafInput,
|
|
23
25
|
} from "./graphql/objects/index.js";
|
|
24
26
|
import {
|
|
@@ -38,11 +40,14 @@ import { LightningService } from "./services/lightning.js";
|
|
|
38
40
|
import { TokenTransactionService } from "./services/token-transactions.js";
|
|
39
41
|
import { LeafKeyTweak, TransferService } from "./services/transfer.js";
|
|
40
42
|
import { ConfigOptions } from "./services/wallet-config.js";
|
|
41
|
-
|
|
43
|
+
import {
|
|
44
|
+
createLrc20ConnectionManager,
|
|
45
|
+
ILrc20ConnectionManager,
|
|
46
|
+
Lrc20SparkClient,
|
|
47
|
+
} from "@buildonspark/lrc20-sdk/grpc";
|
|
42
48
|
import { validateMnemonic } from "@scure/bip39";
|
|
43
49
|
import { wordlist } from "@scure/bip39/wordlists/english";
|
|
44
50
|
import { Mutex } from "async-mutex";
|
|
45
|
-
import bitcoin from "bitcoinjs-lib";
|
|
46
51
|
import {
|
|
47
52
|
DepositAddressTree,
|
|
48
53
|
TreeCreationService,
|
|
@@ -103,6 +108,14 @@ type DepositParams = {
|
|
|
103
108
|
vout: number;
|
|
104
109
|
};
|
|
105
110
|
|
|
111
|
+
export type TokenInfo = {
|
|
112
|
+
tokenPublicKey: string;
|
|
113
|
+
tokenName: string;
|
|
114
|
+
tokenSymbol: string;
|
|
115
|
+
tokenDecimals: number;
|
|
116
|
+
tokenSupply: bigint;
|
|
117
|
+
}
|
|
118
|
+
|
|
106
119
|
export type InitWalletResponse = {
|
|
107
120
|
mnemonic?: string | undefined;
|
|
108
121
|
};
|
|
@@ -122,6 +135,7 @@ export class SparkWallet {
|
|
|
122
135
|
protected config: WalletConfigService;
|
|
123
136
|
|
|
124
137
|
protected connectionManager: ConnectionManager;
|
|
138
|
+
protected lrc20ConnectionManager: ILrc20ConnectionManager;
|
|
125
139
|
protected lrc20Wallet: LRCWallet | undefined;
|
|
126
140
|
|
|
127
141
|
private depositService: DepositService;
|
|
@@ -137,6 +151,8 @@ export class SparkWallet {
|
|
|
137
151
|
private sspClient: SspClient | null = null;
|
|
138
152
|
private wasmModule: InitOutput | null = null;
|
|
139
153
|
|
|
154
|
+
private mutexes: Map<string, Mutex> = new Map();
|
|
155
|
+
|
|
140
156
|
private pendingWithdrawnLeafIds: string[] = [];
|
|
141
157
|
|
|
142
158
|
protected leaves: TreeNode[] = [];
|
|
@@ -146,6 +162,7 @@ export class SparkWallet {
|
|
|
146
162
|
protected constructor(options?: ConfigOptions, signer?: SparkSigner) {
|
|
147
163
|
this.config = new WalletConfigService(options, signer);
|
|
148
164
|
this.connectionManager = new ConnectionManager(this.config);
|
|
165
|
+
this.lrc20ConnectionManager = createLrc20ConnectionManager(this.config.getLrc20Address());
|
|
149
166
|
this.depositService = new DepositService(
|
|
150
167
|
this.config,
|
|
151
168
|
this.connectionManager,
|
|
@@ -172,7 +189,7 @@ export class SparkWallet {
|
|
|
172
189
|
);
|
|
173
190
|
}
|
|
174
191
|
|
|
175
|
-
public static async
|
|
192
|
+
public static async initialize({
|
|
176
193
|
mnemonicOrSeed,
|
|
177
194
|
signer,
|
|
178
195
|
options,
|
|
@@ -326,7 +343,7 @@ export class SparkWallet {
|
|
|
326
343
|
this.leaves = await this.getLeaves();
|
|
327
344
|
await this.config.signer.restoreSigningKeysFromLeafs(this.leaves);
|
|
328
345
|
await this.refreshTimelockNodes();
|
|
329
|
-
|
|
346
|
+
await this.extendTimeLockNodes();
|
|
330
347
|
this.optimizeLeaves().catch((e) => {
|
|
331
348
|
console.error("Failed to optimize leaves", e);
|
|
332
349
|
});
|
|
@@ -670,6 +687,25 @@ export class SparkWallet {
|
|
|
670
687
|
return await this.transferService.queryAllTransfers(limit, offset);
|
|
671
688
|
}
|
|
672
689
|
|
|
690
|
+
public async getTokenInfo(): Promise<TokenInfo[]> {
|
|
691
|
+
await this.syncTokenLeaves();
|
|
692
|
+
|
|
693
|
+
const lrc20Client = await this.lrc20ConnectionManager.createLrc20Client();
|
|
694
|
+
const { balance, tokenBalances } = await this.getBalance();
|
|
695
|
+
|
|
696
|
+
const tokenInfo = await lrc20Client.getTokenPubkeyInfo({
|
|
697
|
+
publicKeys: Array.from(tokenBalances.keys()).map(hexToBytes)
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
return tokenInfo.tokenPubkeyInfos.map((info) => ({
|
|
701
|
+
tokenPublicKey: bytesToHex(info.announcement!.publicKey!.publicKey),
|
|
702
|
+
tokenName: info.announcement!.name,
|
|
703
|
+
tokenSymbol: info.announcement!.symbol,
|
|
704
|
+
tokenDecimals: Number(bytesToNumberBE(info.announcement!.decimal)),
|
|
705
|
+
tokenSupply: bytesToNumberBE(info.totalSupply),
|
|
706
|
+
}));
|
|
707
|
+
}
|
|
708
|
+
|
|
673
709
|
/**
|
|
674
710
|
* Gets the current balance of the wallet.
|
|
675
711
|
* You can use the forceRefetch option to synchronize your wallet and claim any
|
|
@@ -776,71 +812,84 @@ export class SparkWallet {
|
|
|
776
812
|
* @returns {Promise<TreeNode[] | undefined>} The nodes resulting from the deposit
|
|
777
813
|
*/
|
|
778
814
|
public async claimDeposit(txid: string) {
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
const auth = btoa("spark-sdk:mCMk1JqlBNtetUNy");
|
|
784
|
-
|
|
785
|
-
const headers: Record<string, string> = {
|
|
786
|
-
"Content-Type": "application/json",
|
|
787
|
-
};
|
|
788
|
-
|
|
789
|
-
if (this.config.getNetwork() === Network.REGTEST) {
|
|
790
|
-
headers["Authorization"] = `Basic ${auth}`;
|
|
815
|
+
let mutex = this.mutexes.get(txid);
|
|
816
|
+
if (!mutex) {
|
|
817
|
+
mutex = new Mutex();
|
|
818
|
+
this.mutexes.set(txid, mutex);
|
|
791
819
|
}
|
|
792
820
|
|
|
793
|
-
const
|
|
794
|
-
|
|
795
|
-
|
|
821
|
+
const nodes = await mutex.runExclusive(async () => {
|
|
822
|
+
const baseUrl =
|
|
823
|
+
this.config.getNetwork() === Network.REGTEST
|
|
824
|
+
? "https://regtest-mempool.dev.dev.sparkinfra.net/api"
|
|
825
|
+
: "https://mempool.space/api";
|
|
826
|
+
const auth = btoa("spark-sdk:mCMk1JqlBNtetUNy");
|
|
796
827
|
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
}
|
|
801
|
-
const depositTx = getTxFromRawTxHex(txHex);
|
|
828
|
+
const headers: Record<string, string> = {
|
|
829
|
+
"Content-Type": "application/json",
|
|
830
|
+
};
|
|
802
831
|
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
832
|
+
if (this.config.getNetwork() === Network.REGTEST) {
|
|
833
|
+
headers["Authorization"] = `Basic ${auth}`;
|
|
834
|
+
}
|
|
806
835
|
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
await sparkClient.query_unused_deposit_addresses({
|
|
811
|
-
identityPublicKey: await this.config.signer.getIdentityPublicKey(),
|
|
812
|
-
})
|
|
813
|
-
).depositAddresses.map((addr) => [addr.depositAddress, addr]),
|
|
814
|
-
);
|
|
836
|
+
const response = await fetch(`${baseUrl}/tx/${txid}/hex`, {
|
|
837
|
+
headers,
|
|
838
|
+
});
|
|
815
839
|
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
const output = depositTx.getOutput(i);
|
|
820
|
-
if (!output) {
|
|
821
|
-
continue;
|
|
840
|
+
const txHex = await response.text();
|
|
841
|
+
if (!/^[0-9A-Fa-f]+$/.test(txHex)) {
|
|
842
|
+
throw new Error("Transaction not found");
|
|
822
843
|
}
|
|
823
|
-
const
|
|
824
|
-
|
|
825
|
-
|
|
844
|
+
const depositTx = getTxFromRawTxHex(txHex);
|
|
845
|
+
|
|
846
|
+
const sparkClient = await this.connectionManager.createSparkClient(
|
|
847
|
+
this.config.getCoordinatorAddress(),
|
|
826
848
|
);
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
849
|
+
|
|
850
|
+
const unusedDepositAddresses: Map<string, DepositAddressQueryResult> =
|
|
851
|
+
new Map(
|
|
852
|
+
(
|
|
853
|
+
await sparkClient.query_unused_deposit_addresses({
|
|
854
|
+
identityPublicKey:
|
|
855
|
+
await this.config.signer.getIdentityPublicKey(),
|
|
856
|
+
})
|
|
857
|
+
).depositAddresses.map((addr) => [addr.depositAddress, addr]),
|
|
858
|
+
);
|
|
859
|
+
|
|
860
|
+
let depositAddress: DepositAddressQueryResult | undefined;
|
|
861
|
+
let vout = 0;
|
|
862
|
+
for (let i = 0; i < depositTx.outputsLength; i++) {
|
|
863
|
+
const output = depositTx.getOutput(i);
|
|
864
|
+
if (!output) {
|
|
865
|
+
continue;
|
|
866
|
+
}
|
|
867
|
+
const parsedScript = OutScript.decode(output.script!);
|
|
868
|
+
const address = Address(getNetwork(this.config.getNetwork())).encode(
|
|
869
|
+
parsedScript,
|
|
870
|
+
);
|
|
871
|
+
if (unusedDepositAddresses.has(address)) {
|
|
872
|
+
vout = i;
|
|
873
|
+
depositAddress = unusedDepositAddresses.get(address);
|
|
874
|
+
break;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
if (!depositAddress) {
|
|
878
|
+
return [];
|
|
831
879
|
}
|
|
832
|
-
}
|
|
833
|
-
if (!depositAddress) {
|
|
834
|
-
throw new Error("Deposit address not found");
|
|
835
|
-
}
|
|
836
880
|
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
881
|
+
const nodes = await this.finalizeDeposit({
|
|
882
|
+
signingPubKey: depositAddress.userSigningPublicKey,
|
|
883
|
+
verifyingKey: depositAddress.verifyingPublicKey,
|
|
884
|
+
depositTx,
|
|
885
|
+
vout,
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
return nodes;
|
|
842
889
|
});
|
|
843
890
|
|
|
891
|
+
this.mutexes.delete(txid);
|
|
892
|
+
|
|
844
893
|
return nodes;
|
|
845
894
|
}
|
|
846
895
|
|
|
@@ -864,18 +913,12 @@ export class SparkWallet {
|
|
|
864
913
|
})),
|
|
865
914
|
);
|
|
866
915
|
|
|
867
|
-
await this.transferService.sendTransfer(
|
|
916
|
+
const transfer = await this.transferService.sendTransfer(
|
|
868
917
|
leafKeyTweaks,
|
|
869
918
|
await this.config.signer.getIdentityPublicKey(),
|
|
870
919
|
);
|
|
871
920
|
|
|
872
|
-
|
|
873
|
-
if (pendingTransfers.transfers.length > 0) {
|
|
874
|
-
// @ts-ignore - We check the length, so the first element is guaranteed to exist
|
|
875
|
-
return (await this.claimTransfer(pendingTransfers.transfers[0])).nodes;
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
return;
|
|
921
|
+
return await this.claimTransfer(transfer);
|
|
879
922
|
}
|
|
880
923
|
// ***** Transfer Flow *****
|
|
881
924
|
|
|
@@ -892,6 +935,7 @@ export class SparkWallet {
|
|
|
892
935
|
const leavesToSend = await this.selectLeaves(amountSats);
|
|
893
936
|
|
|
894
937
|
await this.refreshTimelockNodes();
|
|
938
|
+
await this.extendTimeLockNodes();
|
|
895
939
|
|
|
896
940
|
const leafKeyTweaks = await Promise.all(
|
|
897
941
|
leavesToSend.map(async (leaf) => ({
|
|
@@ -915,6 +959,29 @@ export class SparkWallet {
|
|
|
915
959
|
});
|
|
916
960
|
}
|
|
917
961
|
|
|
962
|
+
private async extendTimeLockNodes() {
|
|
963
|
+
const nodesToExtend: TreeNode[] = [];
|
|
964
|
+
const nodeIds: string[] = [];
|
|
965
|
+
|
|
966
|
+
for (const node of this.leaves) {
|
|
967
|
+
const nodeTx = getTxFromRawTxBytes(node.nodeTx);
|
|
968
|
+
const { nextSequence } = getNextTransactionSequence(
|
|
969
|
+
nodeTx.getInput(0).sequence,
|
|
970
|
+
);
|
|
971
|
+
if (nextSequence <= 0) {
|
|
972
|
+
nodesToExtend.push(node);
|
|
973
|
+
nodeIds.push(node.id);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
for (const node of nodesToExtend) {
|
|
978
|
+
await this.transferService.extendTimelock(
|
|
979
|
+
node,
|
|
980
|
+
await this.config.signer.generatePublicKey(sha256(node.id)),
|
|
981
|
+
);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
|
|
918
985
|
/**
|
|
919
986
|
* Internal method to refresh timelock nodes.
|
|
920
987
|
*
|
|
@@ -1048,6 +1115,7 @@ export class SparkWallet {
|
|
|
1048
1115
|
|
|
1049
1116
|
this.leaves.push(...response.nodes);
|
|
1050
1117
|
await this.refreshTimelockNodes();
|
|
1118
|
+
await this.extendTimeLockNodes();
|
|
1051
1119
|
|
|
1052
1120
|
return response.nodes;
|
|
1053
1121
|
});
|
|
@@ -1112,13 +1180,13 @@ export class SparkWallet {
|
|
|
1112
1180
|
* @param {number} params.amountSats - Amount in satoshis
|
|
1113
1181
|
* @param {string} params.memo - Description for the invoice
|
|
1114
1182
|
* @param {number} [params.expirySeconds] - Optional expiry time in seconds
|
|
1115
|
-
* @returns {Promise<
|
|
1183
|
+
* @returns {Promise<LightningReceiveRequest>} BOLT11 encoded invoice
|
|
1116
1184
|
*/
|
|
1117
1185
|
public async createLightningInvoice({
|
|
1118
1186
|
amountSats,
|
|
1119
1187
|
memo,
|
|
1120
1188
|
expirySeconds = 60 * 60 * 24 * 30,
|
|
1121
|
-
}: CreateLightningInvoiceParams) {
|
|
1189
|
+
}: CreateLightningInvoiceParams): Promise<LightningReceiveRequest> {
|
|
1122
1190
|
if (!this.sspClient) {
|
|
1123
1191
|
throw new Error("SSP client not initialized");
|
|
1124
1192
|
}
|
|
@@ -1144,14 +1212,16 @@ export class SparkWallet {
|
|
|
1144
1212
|
memo,
|
|
1145
1213
|
});
|
|
1146
1214
|
|
|
1147
|
-
return invoice
|
|
1215
|
+
return invoice;
|
|
1148
1216
|
};
|
|
1149
1217
|
|
|
1150
|
-
|
|
1218
|
+
const invoice = await this.lightningService!.createLightningInvoice({
|
|
1151
1219
|
amountSats,
|
|
1152
1220
|
memo,
|
|
1153
1221
|
invoiceCreator: requestLightningInvoice,
|
|
1154
1222
|
});
|
|
1223
|
+
|
|
1224
|
+
return invoice;
|
|
1155
1225
|
}
|
|
1156
1226
|
|
|
1157
1227
|
/**
|
|
@@ -1191,6 +1261,8 @@ export class SparkWallet {
|
|
|
1191
1261
|
const leaves = await this.selectLeaves(amountSats);
|
|
1192
1262
|
|
|
1193
1263
|
await this.refreshTimelockNodes();
|
|
1264
|
+
await this.extendTimeLockNodes();
|
|
1265
|
+
|
|
1194
1266
|
const leavesToSend = await Promise.all(
|
|
1195
1267
|
leaves.map(async (leaf) => ({
|
|
1196
1268
|
leaf,
|
|
@@ -1346,21 +1418,21 @@ export class SparkWallet {
|
|
|
1346
1418
|
*
|
|
1347
1419
|
* @param {Object} params - Parameters for the withdrawal
|
|
1348
1420
|
* @param {string} params.onchainAddress - The Bitcoin address where the funds should be sent
|
|
1349
|
-
* @param {number} [params.
|
|
1421
|
+
* @param {number} [params.amountSats] - The amount in satoshis to withdraw. If not specified, attempts to withdraw all available funds
|
|
1350
1422
|
* @returns {Promise<CoopExitRequest | null | undefined>} The withdrawal request details, or null/undefined if the request cannot be completed
|
|
1351
1423
|
*/
|
|
1352
1424
|
public async withdraw({
|
|
1353
1425
|
onchainAddress,
|
|
1354
|
-
|
|
1426
|
+
amountSats,
|
|
1355
1427
|
}: {
|
|
1356
1428
|
onchainAddress: string;
|
|
1357
|
-
|
|
1429
|
+
amountSats?: number;
|
|
1358
1430
|
}) {
|
|
1359
|
-
if (
|
|
1431
|
+
if (amountSats && amountSats < 10000) {
|
|
1360
1432
|
throw new Error("The minimum amount for a withdrawal is 10000 sats");
|
|
1361
1433
|
}
|
|
1362
1434
|
return await this.withLeaves(async () => {
|
|
1363
|
-
return await this.coopExit(onchainAddress,
|
|
1435
|
+
return await this.coopExit(onchainAddress, amountSats);
|
|
1364
1436
|
});
|
|
1365
1437
|
}
|
|
1366
1438
|
|
|
@@ -1446,19 +1518,30 @@ export class SparkWallet {
|
|
|
1446
1518
|
/**
|
|
1447
1519
|
* Gets fee estimate for cooperative exit (on-chain withdrawal).
|
|
1448
1520
|
*
|
|
1449
|
-
* @param {
|
|
1521
|
+
* @param {Object} params - Input parameters for fee estimation
|
|
1522
|
+
* @param {number} params.amountSats - The amount in satoshis to withdraw
|
|
1523
|
+
* @param {string} params.withdrawalAddress - The Bitcoin address where the funds should be sent
|
|
1450
1524
|
* @returns {Promise<CoopExitFeeEstimateOutput | null>} Fee estimate for the withdrawal
|
|
1451
1525
|
*/
|
|
1452
1526
|
public async getCoopExitFeeEstimate({
|
|
1453
|
-
|
|
1527
|
+
amountSats,
|
|
1454
1528
|
withdrawalAddress,
|
|
1455
|
-
}:
|
|
1529
|
+
}: {
|
|
1530
|
+
amountSats: number;
|
|
1531
|
+
withdrawalAddress: string;
|
|
1532
|
+
}): Promise<CoopExitFeeEstimateOutput | null> {
|
|
1456
1533
|
if (!this.sspClient) {
|
|
1457
1534
|
throw new Error("SSP client not initialized");
|
|
1458
1535
|
}
|
|
1459
1536
|
|
|
1537
|
+
if (amountSats < 10000) {
|
|
1538
|
+
throw new Error("The minimum amount for a withdrawal is 10000 sats");
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
const leaves = await this.selectLeaves(amountSats);
|
|
1542
|
+
|
|
1460
1543
|
return await this.sspClient.getCoopExitFeeEstimate({
|
|
1461
|
-
leafExternalIds,
|
|
1544
|
+
leafExternalIds: leaves.map((leaf) => leaf.id),
|
|
1462
1545
|
withdrawalAddress,
|
|
1463
1546
|
});
|
|
1464
1547
|
}
|
|
@@ -1757,25 +1840,67 @@ export class SparkWallet {
|
|
|
1757
1840
|
return;
|
|
1758
1841
|
}
|
|
1759
1842
|
}
|
|
1760
|
-
}
|
|
1761
1843
|
|
|
1762
|
-
/**
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1844
|
+
/**
|
|
1845
|
+
* Signs a message with the identity key.
|
|
1846
|
+
*
|
|
1847
|
+
* @param {string} message - Unhashed message to sign
|
|
1848
|
+
* @param {boolean} [compact] - Whether to use compact encoding. If false, the message will be encoded as DER.
|
|
1849
|
+
* @returns {Promise<string>} The signed message
|
|
1850
|
+
*/
|
|
1851
|
+
public async signMessage(
|
|
1852
|
+
message: string,
|
|
1853
|
+
compact?: boolean,
|
|
1854
|
+
): Promise<string> {
|
|
1855
|
+
const hash = sha256(message);
|
|
1856
|
+
return bytesToHex(
|
|
1857
|
+
await this.config.signer.signMessageWithIdentityKey(hash, compact),
|
|
1858
|
+
);
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
/**
|
|
1862
|
+
* Get a Lightning receive request by ID.
|
|
1863
|
+
*
|
|
1864
|
+
* @param {string} id - The ID of the Lightning receive request
|
|
1865
|
+
* @returns {Promise<LightningReceiveRequest | null>} The Lightning receive request
|
|
1866
|
+
*/
|
|
1867
|
+
public async getLightningReceiveRequest(
|
|
1868
|
+
id: string,
|
|
1869
|
+
): Promise<LightningReceiveRequest | null> {
|
|
1870
|
+
if (!this.sspClient) {
|
|
1871
|
+
throw new Error("SSP client not initialized");
|
|
1776
1872
|
}
|
|
1777
|
-
|
|
1778
|
-
|
|
1873
|
+
|
|
1874
|
+
return await this.sspClient.getLightningReceiveRequest(id);
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
/**
|
|
1878
|
+
* Get a Lightning send request by ID.
|
|
1879
|
+
*
|
|
1880
|
+
* @param {string} id - The ID of the Lightning send request
|
|
1881
|
+
* @returns {Promise<LightningSendRequest | null>} The Lightning send request
|
|
1882
|
+
*/
|
|
1883
|
+
public async getLightningSendRequest(
|
|
1884
|
+
id: string,
|
|
1885
|
+
): Promise<LightningSendRequest | null> {
|
|
1886
|
+
if (!this.sspClient) {
|
|
1887
|
+
throw new Error("SSP client not initialized");
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
return await this.sspClient.getLightningSendRequest(id);
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
/**
|
|
1894
|
+
* Get a coop exit request by ID.
|
|
1895
|
+
*
|
|
1896
|
+
* @param {string} id - The ID of the coop exit request
|
|
1897
|
+
* @returns {Promise<CoopExitRequest | null>} The coop exit request
|
|
1898
|
+
*/
|
|
1899
|
+
public async getCoopExitRequest(id: string): Promise<CoopExitRequest | null> {
|
|
1900
|
+
if (!this.sspClient) {
|
|
1901
|
+
throw new Error("SSP client not initialized");
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
return await this.sspClient.getCoopExitRequest(id);
|
|
1779
1905
|
}
|
|
1780
|
-
return null;
|
|
1781
1906
|
}
|
|
@@ -41,7 +41,7 @@ describe("coop exit", () => {
|
|
|
41
41
|
};
|
|
42
42
|
|
|
43
43
|
// Setup user with leaves
|
|
44
|
-
const { wallet: userWallet } = await SparkWalletTesting.
|
|
44
|
+
const { wallet: userWallet } = await SparkWalletTesting.initialize({
|
|
45
45
|
options,
|
|
46
46
|
});
|
|
47
47
|
|
|
@@ -66,7 +66,7 @@ describe("coop exit", () => {
|
|
|
66
66
|
);
|
|
67
67
|
|
|
68
68
|
// Setup ssp
|
|
69
|
-
const { wallet: sspWallet } = await SparkWalletTesting.
|
|
69
|
+
const { wallet: sspWallet } = await SparkWalletTesting.initialize({
|
|
70
70
|
options,
|
|
71
71
|
});
|
|
72
72
|
const sspPubkey = await sspWallet.getIdentityPublicKey();
|
|
@@ -15,7 +15,7 @@ describe("deposit", () => {
|
|
|
15
15
|
async () => {
|
|
16
16
|
const mnemonic =
|
|
17
17
|
"raise benefit echo client clutch short pyramid grass fall core slogan boil device plastic drastic discover decide penalty middle appear medal elbow original income";
|
|
18
|
-
const { wallet: sdk } = await SparkWalletTesting.
|
|
18
|
+
const { wallet: sdk } = await SparkWalletTesting.initialize({
|
|
19
19
|
options: {
|
|
20
20
|
network: "LOCAL",
|
|
21
21
|
},
|
|
@@ -39,7 +39,7 @@ describe("deposit", () => {
|
|
|
39
39
|
|
|
40
40
|
const coin = await faucet.fund();
|
|
41
41
|
|
|
42
|
-
const { wallet: sdk } = await SparkWalletTesting.
|
|
42
|
+
const { wallet: sdk } = await SparkWalletTesting.initialize({
|
|
43
43
|
options: {
|
|
44
44
|
network: "LOCAL",
|
|
45
45
|
},
|