@buildonspark/spark-sdk 0.0.21 → 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 +47 -10
- package/dist/spark-sdk.js +90 -10
- package/dist/spark-sdk.js.map +1 -1
- package/dist/tests/lightning.test.js +31 -2
- package/dist/tests/lightning.test.js.map +1 -1
- package/package.json +2 -2
- package/src/examples/example.ts +98 -18
- 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 +137 -16
- package/src/tests/lightning.test.ts +39 -3
package/src/examples/example.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import readline from "readline";
|
|
2
2
|
import { ConfigOptions } from "../../dist/services/wallet-config.js";
|
|
3
3
|
import { SparkWallet } from "../../dist/spark-sdk";
|
|
4
|
+
import { BitcoinNetwork } from "../../dist/types/index.js";
|
|
4
5
|
import { getLatestDepositTxId } from "../../dist/utils/mempool.js";
|
|
5
6
|
|
|
6
7
|
// Initialize Spark Wallet
|
|
@@ -18,23 +19,29 @@ async function runCLI() {
|
|
|
18
19
|
});
|
|
19
20
|
const helpMessage = `
|
|
20
21
|
Available commands:
|
|
21
|
-
initwallet [mnemonic | seed]
|
|
22
|
-
getbalance
|
|
23
|
-
getdepositaddress
|
|
24
|
-
getsparkaddress
|
|
25
|
-
getlatesttx <address>
|
|
26
|
-
claimdeposit <txid>
|
|
27
|
-
claimtransfers
|
|
28
|
-
createinvoice <amount> <memo>
|
|
29
|
-
payinvoice <invoice>
|
|
30
|
-
sendtransfer <amount> <receiverSparkAddress>
|
|
31
|
-
withdraw <
|
|
32
|
-
|
|
33
|
-
|
|
22
|
+
initwallet [mnemonic | seed] - Create a new wallet from a mnemonic or seed. If no mnemonic or seed is provided, a new mnemonic will be generated.
|
|
23
|
+
getbalance - Get the wallet's balance
|
|
24
|
+
getdepositaddress - Get an address to deposit funds from L1 to Spark
|
|
25
|
+
getsparkaddress - Get the wallet's spark address
|
|
26
|
+
getlatesttx <address> - Get the latest deposit transaction id for an address
|
|
27
|
+
claimdeposit <txid> - Claim any pending deposits to the wallet
|
|
28
|
+
claimtransfers - Claim any pending transfers to the wallet
|
|
29
|
+
createinvoice <amount> <memo> - Create a new lightning invoice
|
|
30
|
+
payinvoice <invoice> - Pay a lightning invoice
|
|
31
|
+
sendtransfer <amount> <receiverSparkAddress> - Send a spark transfer
|
|
32
|
+
withdraw <amount> <onchainAddress> - Withdraw funds to an L1 address
|
|
33
|
+
coopfee <amount> <withdrawalAddress> - Get a fee estimate for a cooperative exit
|
|
34
|
+
lightningsendfee <invoice> - Get a fee estimate for a lightning send
|
|
35
|
+
lightningreceivefee <amount> <REGTEST | MAINNET | TESTNET | SIGNET> - Get a fee estimate for a lightning receive
|
|
36
|
+
getlightningsendrequest <requestId> - Get a lightning send request by ID
|
|
37
|
+
getlightningreceiverequest <requestId> - Get a lightning receive request by ID
|
|
38
|
+
getcoopexitrequest <requestId> - Get a coop exit request by ID
|
|
39
|
+
sendtokentransfer <tokenPubKey> <amount> <receiverSparkAddress> - Transfer tokens
|
|
40
|
+
help - Show this help message
|
|
34
41
|
exit/quit
|
|
35
42
|
|
|
36
43
|
L1 commands:
|
|
37
|
-
tokenwithdraw <tokenPublicKey> [
|
|
44
|
+
tokenwithdraw <tokenPublicKey> [tokenAmount] - Unilaterally withdraw tokens to L1- Exit the program
|
|
38
45
|
`;
|
|
39
46
|
console.log(helpMessage);
|
|
40
47
|
|
|
@@ -67,6 +74,42 @@ async function runCLI() {
|
|
|
67
74
|
const depositResult = await wallet.claimDeposit(args[0]);
|
|
68
75
|
console.log(depositResult);
|
|
69
76
|
break;
|
|
77
|
+
case "pendingtransfers":
|
|
78
|
+
if (!wallet) {
|
|
79
|
+
console.log("Please initialize a wallet first");
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
const pendingTransfers = await wallet.getPendingTransfers();
|
|
83
|
+
console.log(pendingTransfers);
|
|
84
|
+
break;
|
|
85
|
+
case "getlightningsendrequest":
|
|
86
|
+
if (!wallet) {
|
|
87
|
+
console.log("Please initialize a wallet first");
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
const lightningSendRequest = await wallet.getLightningSendRequest(
|
|
91
|
+
args[0],
|
|
92
|
+
);
|
|
93
|
+
console.log(lightningSendRequest);
|
|
94
|
+
break;
|
|
95
|
+
case "getlightningreceiverequest":
|
|
96
|
+
if (!wallet) {
|
|
97
|
+
console.log("Please initialize a wallet first");
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
const lightningReceiveRequest = await wallet.getLightningReceiveRequest(
|
|
101
|
+
args[0],
|
|
102
|
+
);
|
|
103
|
+
console.log(lightningReceiveRequest);
|
|
104
|
+
break;
|
|
105
|
+
case "getcoopexitrequest":
|
|
106
|
+
if (!wallet) {
|
|
107
|
+
console.log("Please initialize a wallet first");
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
const coopExitRequest = await wallet.getCoopExitRequest(args[0]);
|
|
111
|
+
console.log(coopExitRequest);
|
|
112
|
+
break;
|
|
70
113
|
case "claimtransfers":
|
|
71
114
|
if (!wallet) {
|
|
72
115
|
console.log("Please initialize a wallet first");
|
|
@@ -187,8 +230,8 @@ async function runCLI() {
|
|
|
187
230
|
break;
|
|
188
231
|
}
|
|
189
232
|
const withdrawal = await wallet.withdraw({
|
|
190
|
-
|
|
191
|
-
|
|
233
|
+
amountSats: parseInt(args[0]),
|
|
234
|
+
onchainAddress: args[1],
|
|
192
235
|
});
|
|
193
236
|
console.log(withdrawal);
|
|
194
237
|
break;
|
|
@@ -198,17 +241,54 @@ async function runCLI() {
|
|
|
198
241
|
break;
|
|
199
242
|
}
|
|
200
243
|
const tokenPublicKey = args[0];
|
|
201
|
-
const
|
|
244
|
+
const amount = BigInt(parseInt(args[1]));
|
|
202
245
|
|
|
203
246
|
let withdrawResult = await wallet.withdrawTokens(
|
|
204
247
|
tokenPublicKey,
|
|
205
|
-
|
|
248
|
+
amount,
|
|
206
249
|
);
|
|
207
250
|
if (withdrawResult) {
|
|
208
251
|
console.log("Withdrawal L1 Transaction ID:", withdrawResult.txid);
|
|
209
252
|
}
|
|
210
253
|
break;
|
|
211
254
|
}
|
|
255
|
+
case "coopfee": {
|
|
256
|
+
if (!wallet) {
|
|
257
|
+
console.log("Please initialize a wallet first");
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
const fee = await wallet.getCoopExitFeeEstimate({
|
|
261
|
+
amountSats: parseInt(args[0]),
|
|
262
|
+
withdrawalAddress: args[1],
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
console.log(fee);
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
case "lightningsendfee": {
|
|
269
|
+
if (!wallet) {
|
|
270
|
+
console.log("Please initialize a wallet first");
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
const fee = await wallet.getLightningSendFeeEstimate({
|
|
274
|
+
encodedInvoice: args[0],
|
|
275
|
+
});
|
|
276
|
+
console.log(fee);
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
case "lightningreceivefee": {
|
|
280
|
+
if (!wallet) {
|
|
281
|
+
console.log("Please initialize a wallet first");
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
const network = args[1] as BitcoinNetwork;
|
|
285
|
+
const fee = await wallet.getLightningReceiveFeeEstimate({
|
|
286
|
+
amountSats: parseInt(args[0]),
|
|
287
|
+
network,
|
|
288
|
+
});
|
|
289
|
+
console.log(fee);
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
212
292
|
}
|
|
213
293
|
}
|
|
214
294
|
}
|
package/src/graphql/client.ts
CHANGED
|
@@ -47,6 +47,7 @@ import { CoopExitFeeEstimate } from "./queries/CoopExitFeeEstimate.js";
|
|
|
47
47
|
import { LeavesSwapFeeEstimate } from "./queries/LeavesSwapFeeEstimate.js";
|
|
48
48
|
import { LightningReceiveFeeEstimate } from "./queries/LightningReceiveFeeEstimate.js";
|
|
49
49
|
import { LightningSendFeeEstimate } from "./queries/LightningSendFeeEstimate.js";
|
|
50
|
+
import { UserRequest } from "./queries/UserRequest.js";
|
|
50
51
|
|
|
51
52
|
export default class SspClient {
|
|
52
53
|
private readonly requester: Requester;
|
|
@@ -267,6 +268,74 @@ export default class SspClient {
|
|
|
267
268
|
},
|
|
268
269
|
});
|
|
269
270
|
}
|
|
271
|
+
|
|
272
|
+
async getLightningReceiveRequest(
|
|
273
|
+
id: string,
|
|
274
|
+
): Promise<LightningReceiveRequest | null> {
|
|
275
|
+
return await this.executeRawQuery({
|
|
276
|
+
queryPayload: UserRequest,
|
|
277
|
+
variables: {
|
|
278
|
+
request_id: id,
|
|
279
|
+
},
|
|
280
|
+
constructObject: (response: { user_request: any }) => {
|
|
281
|
+
if (!response.user_request) {
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return LightningReceiveRequestFromJson(response.user_request);
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async getLightningSendRequest(
|
|
291
|
+
id: string,
|
|
292
|
+
): Promise<LightningSendRequest | null> {
|
|
293
|
+
return await this.executeRawQuery({
|
|
294
|
+
queryPayload: UserRequest,
|
|
295
|
+
variables: {
|
|
296
|
+
request_id: id,
|
|
297
|
+
},
|
|
298
|
+
constructObject: (response: { user_request: any }) => {
|
|
299
|
+
if (!response.user_request) {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return LightningSendRequestFromJson(response.user_request);
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async getLeaveSwapRequest(id: string): Promise<LeavesSwapRequest | null> {
|
|
309
|
+
return await this.executeRawQuery({
|
|
310
|
+
queryPayload: UserRequest,
|
|
311
|
+
variables: {
|
|
312
|
+
request_id: id,
|
|
313
|
+
},
|
|
314
|
+
constructObject: (response: { user_request: any }) => {
|
|
315
|
+
if (!response.user_request) {
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return LeavesSwapRequestFromJson(response.user_request);
|
|
320
|
+
},
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async getCoopExitRequest(id: string): Promise<CoopExitRequest | null> {
|
|
325
|
+
return await this.executeRawQuery({
|
|
326
|
+
queryPayload: UserRequest,
|
|
327
|
+
variables: {
|
|
328
|
+
request_id: id,
|
|
329
|
+
},
|
|
330
|
+
constructObject: (response: { user_request: any }) => {
|
|
331
|
+
if (!response.user_request) {
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return CoopExitRequestFromJson(response.user_request);
|
|
336
|
+
},
|
|
337
|
+
});
|
|
338
|
+
}
|
|
270
339
|
}
|
|
271
340
|
|
|
272
341
|
class SparkAuthProvider implements AuthProvider {
|
|
File without changes
|
|
@@ -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
|
@@ -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,7 +40,11 @@ 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";
|
|
@@ -102,6 +108,14 @@ type DepositParams = {
|
|
|
102
108
|
vout: number;
|
|
103
109
|
};
|
|
104
110
|
|
|
111
|
+
export type TokenInfo = {
|
|
112
|
+
tokenPublicKey: string;
|
|
113
|
+
tokenName: string;
|
|
114
|
+
tokenSymbol: string;
|
|
115
|
+
tokenDecimals: number;
|
|
116
|
+
tokenSupply: bigint;
|
|
117
|
+
}
|
|
118
|
+
|
|
105
119
|
export type InitWalletResponse = {
|
|
106
120
|
mnemonic?: string | undefined;
|
|
107
121
|
};
|
|
@@ -121,6 +135,7 @@ export class SparkWallet {
|
|
|
121
135
|
protected config: WalletConfigService;
|
|
122
136
|
|
|
123
137
|
protected connectionManager: ConnectionManager;
|
|
138
|
+
protected lrc20ConnectionManager: ILrc20ConnectionManager;
|
|
124
139
|
protected lrc20Wallet: LRCWallet | undefined;
|
|
125
140
|
|
|
126
141
|
private depositService: DepositService;
|
|
@@ -147,6 +162,7 @@ export class SparkWallet {
|
|
|
147
162
|
protected constructor(options?: ConfigOptions, signer?: SparkSigner) {
|
|
148
163
|
this.config = new WalletConfigService(options, signer);
|
|
149
164
|
this.connectionManager = new ConnectionManager(this.config);
|
|
165
|
+
this.lrc20ConnectionManager = createLrc20ConnectionManager(this.config.getLrc20Address());
|
|
150
166
|
this.depositService = new DepositService(
|
|
151
167
|
this.config,
|
|
152
168
|
this.connectionManager,
|
|
@@ -327,7 +343,7 @@ export class SparkWallet {
|
|
|
327
343
|
this.leaves = await this.getLeaves();
|
|
328
344
|
await this.config.signer.restoreSigningKeysFromLeafs(this.leaves);
|
|
329
345
|
await this.refreshTimelockNodes();
|
|
330
|
-
|
|
346
|
+
await this.extendTimeLockNodes();
|
|
331
347
|
this.optimizeLeaves().catch((e) => {
|
|
332
348
|
console.error("Failed to optimize leaves", e);
|
|
333
349
|
});
|
|
@@ -671,6 +687,25 @@ export class SparkWallet {
|
|
|
671
687
|
return await this.transferService.queryAllTransfers(limit, offset);
|
|
672
688
|
}
|
|
673
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
|
+
|
|
674
709
|
/**
|
|
675
710
|
* Gets the current balance of the wallet.
|
|
676
711
|
* You can use the forceRefetch option to synchronize your wallet and claim any
|
|
@@ -900,6 +935,7 @@ export class SparkWallet {
|
|
|
900
935
|
const leavesToSend = await this.selectLeaves(amountSats);
|
|
901
936
|
|
|
902
937
|
await this.refreshTimelockNodes();
|
|
938
|
+
await this.extendTimeLockNodes();
|
|
903
939
|
|
|
904
940
|
const leafKeyTweaks = await Promise.all(
|
|
905
941
|
leavesToSend.map(async (leaf) => ({
|
|
@@ -923,6 +959,29 @@ export class SparkWallet {
|
|
|
923
959
|
});
|
|
924
960
|
}
|
|
925
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
|
+
|
|
926
985
|
/**
|
|
927
986
|
* Internal method to refresh timelock nodes.
|
|
928
987
|
*
|
|
@@ -1056,6 +1115,7 @@ export class SparkWallet {
|
|
|
1056
1115
|
|
|
1057
1116
|
this.leaves.push(...response.nodes);
|
|
1058
1117
|
await this.refreshTimelockNodes();
|
|
1118
|
+
await this.extendTimeLockNodes();
|
|
1059
1119
|
|
|
1060
1120
|
return response.nodes;
|
|
1061
1121
|
});
|
|
@@ -1120,13 +1180,13 @@ export class SparkWallet {
|
|
|
1120
1180
|
* @param {number} params.amountSats - Amount in satoshis
|
|
1121
1181
|
* @param {string} params.memo - Description for the invoice
|
|
1122
1182
|
* @param {number} [params.expirySeconds] - Optional expiry time in seconds
|
|
1123
|
-
* @returns {Promise<
|
|
1183
|
+
* @returns {Promise<LightningReceiveRequest>} BOLT11 encoded invoice
|
|
1124
1184
|
*/
|
|
1125
1185
|
public async createLightningInvoice({
|
|
1126
1186
|
amountSats,
|
|
1127
1187
|
memo,
|
|
1128
1188
|
expirySeconds = 60 * 60 * 24 * 30,
|
|
1129
|
-
}: CreateLightningInvoiceParams) {
|
|
1189
|
+
}: CreateLightningInvoiceParams): Promise<LightningReceiveRequest> {
|
|
1130
1190
|
if (!this.sspClient) {
|
|
1131
1191
|
throw new Error("SSP client not initialized");
|
|
1132
1192
|
}
|
|
@@ -1152,14 +1212,16 @@ export class SparkWallet {
|
|
|
1152
1212
|
memo,
|
|
1153
1213
|
});
|
|
1154
1214
|
|
|
1155
|
-
return invoice
|
|
1215
|
+
return invoice;
|
|
1156
1216
|
};
|
|
1157
1217
|
|
|
1158
|
-
|
|
1218
|
+
const invoice = await this.lightningService!.createLightningInvoice({
|
|
1159
1219
|
amountSats,
|
|
1160
1220
|
memo,
|
|
1161
1221
|
invoiceCreator: requestLightningInvoice,
|
|
1162
1222
|
});
|
|
1223
|
+
|
|
1224
|
+
return invoice;
|
|
1163
1225
|
}
|
|
1164
1226
|
|
|
1165
1227
|
/**
|
|
@@ -1199,6 +1261,8 @@ export class SparkWallet {
|
|
|
1199
1261
|
const leaves = await this.selectLeaves(amountSats);
|
|
1200
1262
|
|
|
1201
1263
|
await this.refreshTimelockNodes();
|
|
1264
|
+
await this.extendTimeLockNodes();
|
|
1265
|
+
|
|
1202
1266
|
const leavesToSend = await Promise.all(
|
|
1203
1267
|
leaves.map(async (leaf) => ({
|
|
1204
1268
|
leaf,
|
|
@@ -1354,21 +1418,21 @@ export class SparkWallet {
|
|
|
1354
1418
|
*
|
|
1355
1419
|
* @param {Object} params - Parameters for the withdrawal
|
|
1356
1420
|
* @param {string} params.onchainAddress - The Bitcoin address where the funds should be sent
|
|
1357
|
-
* @param {number} [params.
|
|
1421
|
+
* @param {number} [params.amountSats] - The amount in satoshis to withdraw. If not specified, attempts to withdraw all available funds
|
|
1358
1422
|
* @returns {Promise<CoopExitRequest | null | undefined>} The withdrawal request details, or null/undefined if the request cannot be completed
|
|
1359
1423
|
*/
|
|
1360
1424
|
public async withdraw({
|
|
1361
1425
|
onchainAddress,
|
|
1362
|
-
|
|
1426
|
+
amountSats,
|
|
1363
1427
|
}: {
|
|
1364
1428
|
onchainAddress: string;
|
|
1365
|
-
|
|
1429
|
+
amountSats?: number;
|
|
1366
1430
|
}) {
|
|
1367
|
-
if (
|
|
1431
|
+
if (amountSats && amountSats < 10000) {
|
|
1368
1432
|
throw new Error("The minimum amount for a withdrawal is 10000 sats");
|
|
1369
1433
|
}
|
|
1370
1434
|
return await this.withLeaves(async () => {
|
|
1371
|
-
return await this.coopExit(onchainAddress,
|
|
1435
|
+
return await this.coopExit(onchainAddress, amountSats);
|
|
1372
1436
|
});
|
|
1373
1437
|
}
|
|
1374
1438
|
|
|
@@ -1454,19 +1518,30 @@ export class SparkWallet {
|
|
|
1454
1518
|
/**
|
|
1455
1519
|
* Gets fee estimate for cooperative exit (on-chain withdrawal).
|
|
1456
1520
|
*
|
|
1457
|
-
* @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
|
|
1458
1524
|
* @returns {Promise<CoopExitFeeEstimateOutput | null>} Fee estimate for the withdrawal
|
|
1459
1525
|
*/
|
|
1460
1526
|
public async getCoopExitFeeEstimate({
|
|
1461
|
-
|
|
1527
|
+
amountSats,
|
|
1462
1528
|
withdrawalAddress,
|
|
1463
|
-
}:
|
|
1529
|
+
}: {
|
|
1530
|
+
amountSats: number;
|
|
1531
|
+
withdrawalAddress: string;
|
|
1532
|
+
}): Promise<CoopExitFeeEstimateOutput | null> {
|
|
1464
1533
|
if (!this.sspClient) {
|
|
1465
1534
|
throw new Error("SSP client not initialized");
|
|
1466
1535
|
}
|
|
1467
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
|
+
|
|
1468
1543
|
return await this.sspClient.getCoopExitFeeEstimate({
|
|
1469
|
-
leafExternalIds,
|
|
1544
|
+
leafExternalIds: leaves.map((leaf) => leaf.id),
|
|
1470
1545
|
withdrawalAddress,
|
|
1471
1546
|
});
|
|
1472
1547
|
}
|
|
@@ -1782,4 +1857,50 @@ export class SparkWallet {
|
|
|
1782
1857
|
await this.config.signer.signMessageWithIdentityKey(hash, compact),
|
|
1783
1858
|
);
|
|
1784
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");
|
|
1872
|
+
}
|
|
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);
|
|
1905
|
+
}
|
|
1785
1906
|
}
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { afterEach, beforeAll, describe, expect, it } from "@jest/globals";
|
|
2
2
|
import { hexToBytes } from "@noble/curves/abstract/utils";
|
|
3
3
|
import { equalBytes, sha256 } from "@scure/btc-signer/utils";
|
|
4
|
+
import LightningReceiveRequest from "../graphql/objects/LightningReceiveRequest.js";
|
|
4
5
|
import { TransferStatus } from "../proto/spark.js";
|
|
5
6
|
import { WalletConfigService } from "../services/config.js";
|
|
6
7
|
import { ConnectionManager } from "../services/connection.js";
|
|
7
8
|
import { LightningService } from "../services/lightning.js";
|
|
8
9
|
import { LeafKeyTweak, TransferService } from "../services/transfer.js";
|
|
10
|
+
import {
|
|
11
|
+
BitcoinNetwork,
|
|
12
|
+
CurrencyUnit,
|
|
13
|
+
LightningReceiveRequestStatus,
|
|
14
|
+
} from "../types/index.js";
|
|
9
15
|
import { createNewTree, getTestWalletConfig } from "./test-util.js";
|
|
10
16
|
import { SparkWalletTesting } from "./utils/spark-testing-wallet.js";
|
|
11
17
|
import { BitcoinFaucet } from "./utils/test-faucet.js";
|
|
@@ -27,8 +33,38 @@ async function cleanUp() {
|
|
|
27
33
|
}
|
|
28
34
|
}
|
|
29
35
|
|
|
30
|
-
const fakeInvoiceCreator = async () => {
|
|
31
|
-
return
|
|
36
|
+
const fakeInvoiceCreator = async (): Promise<LightningReceiveRequest> => {
|
|
37
|
+
return {
|
|
38
|
+
id: "123",
|
|
39
|
+
createdAt: new Date().toISOString(),
|
|
40
|
+
updatedAt: new Date().toISOString(),
|
|
41
|
+
network: BitcoinNetwork.REGTEST,
|
|
42
|
+
fee: {
|
|
43
|
+
originalValue: 1000,
|
|
44
|
+
originalUnit: CurrencyUnit.SATOSHI,
|
|
45
|
+
preferredCurrencyUnit: CurrencyUnit.SATOSHI,
|
|
46
|
+
preferredCurrencyValueRounded: 1000,
|
|
47
|
+
preferredCurrencyValueApprox: 1000,
|
|
48
|
+
},
|
|
49
|
+
status: LightningReceiveRequestStatus.INVOICE_CREATED,
|
|
50
|
+
typename: "LightningReceiveRequest",
|
|
51
|
+
invoice: {
|
|
52
|
+
encodedEnvoice:
|
|
53
|
+
"lnbcrt123450n1pnj6uf4pp5l26hsdxssmr52vd4xmn5xran7puzx34hpr6uevaq7ta0ayzrp8esdqqcqzpgxqyz5vqrzjqtr2vd60g57hu63rdqk87u3clac6jlfhej4kldrrjvfcw3mphcw8sqqqqzp3jlj6zyqqqqqqqqqqqqqq9qsp5w22fd8aqn7sdum7hxdf59ptgk322fkv589ejxjltngvgehlcqcyq9qxpqysgqvykwsxdx64qrj0s5pgcgygmrpj8w25jsjgltwn09yp24l9nvghe3dl3y0ycy70ksrlqmcn42hxn24e0ucuy3g9fjltudvhv4lrhhamgq3stqgp",
|
|
54
|
+
bitcoinNetwork: BitcoinNetwork.REGTEST,
|
|
55
|
+
paymentHash:
|
|
56
|
+
"2d059c3ede82a107aa1452c0bea47759be3c5c6e5342be6a310f6c3a907d9f4c",
|
|
57
|
+
amount: {
|
|
58
|
+
originalValue: 10000,
|
|
59
|
+
originalUnit: CurrencyUnit.SATOSHI,
|
|
60
|
+
preferredCurrencyUnit: CurrencyUnit.SATOSHI,
|
|
61
|
+
preferredCurrencyValueRounded: 10000,
|
|
62
|
+
preferredCurrencyValueApprox: 10000,
|
|
63
|
+
},
|
|
64
|
+
createdAt: new Date().toISOString(),
|
|
65
|
+
expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24).toISOString(),
|
|
66
|
+
},
|
|
67
|
+
};
|
|
32
68
|
};
|
|
33
69
|
|
|
34
70
|
describe("LightningService", () => {
|
|
@@ -244,7 +280,7 @@ describe("LightningService", () => {
|
|
|
244
280
|
receiverIdentityPubkey: await sspConfig.signer.getIdentityPublicKey(),
|
|
245
281
|
paymentHash,
|
|
246
282
|
isInboundPayment: false,
|
|
247
|
-
invoiceString: await fakeInvoiceCreator(),
|
|
283
|
+
invoiceString: (await fakeInvoiceCreator()).invoice.encodedEnvoice,
|
|
248
284
|
});
|
|
249
285
|
|
|
250
286
|
expect(response.transfer).toBeDefined();
|