@buildonspark/spark-sdk 0.0.4 → 0.0.5
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/services/transfer.d.ts +1 -0
- package/dist/services/transfer.js +169 -2
- package/dist/services/transfer.js.map +1 -1
- package/dist/signer/signer.d.ts +4 -4
- package/dist/signer/signer.js +6 -5
- package/dist/signer/signer.js.map +1 -1
- package/dist/spark-sdk.d.ts +252 -15
- package/dist/spark-sdk.js +369 -21
- package/dist/spark-sdk.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/jest.setup.d.ts +1 -0
- package/dist/tests/jest.setup.js +8 -0
- package/dist/tests/jest.setup.js.map +1 -0
- package/dist/tests/lightning.test.js +5 -2
- 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 +10 -7
- package/dist/tests/transfer.test.js.map +1 -1
- package/dist/tests/tree-creation.test.js +1 -1
- package/dist/tests/tree-creation.test.js.map +1 -1
- package/package.json +3 -1
- package/dist/graphql/objects/WalletUser.d.ts +0 -21
- package/dist/graphql/objects/WalletUser.js +0 -48
- package/dist/graphql/objects/WalletUser.js.map +0 -1
package/dist/spark-sdk.js
CHANGED
|
@@ -15,14 +15,21 @@ import { TransferService } from "./services/transfer.js";
|
|
|
15
15
|
import { validateMnemonic } from "@scure/bip39";
|
|
16
16
|
import { wordlist } from "@scure/bip39/wordlists/english";
|
|
17
17
|
import { Mutex } from "async-mutex";
|
|
18
|
+
import bitcoin from "bitcoinjs-lib";
|
|
18
19
|
import { TreeCreationService, } from "./services/tree-creation.js";
|
|
19
20
|
import { applyAdaptorToSignature, generateAdaptorFromSignature, generateSignatureFromExistingAdaptor, } from "./utils/adaptor-signature.js";
|
|
20
21
|
import { computeTaprootKeyNoScript, getSigHashFromTx, getTxFromRawTxBytes, getTxFromRawTxHex, getTxId, } from "./utils/bitcoin.js";
|
|
21
22
|
import { Network } from "./utils/network.js";
|
|
22
23
|
import { calculateAvailableTokenAmount, checkIfSelectedLeavesAreAvailable, } from "./utils/token-transactions.js";
|
|
24
|
+
import { getNextTransactionSequence } from "./utils/transaction.js";
|
|
23
25
|
import { initWasm } from "./utils/wasm-wrapper.js";
|
|
24
26
|
// Add this constant at the file level
|
|
25
27
|
const MAX_TOKEN_LEAVES = 100;
|
|
28
|
+
/**
|
|
29
|
+
* The SparkWallet class is the primary interface for interacting with the Spark network.
|
|
30
|
+
* It provides methods for creating and managing wallets, handling deposits, executing transfers,
|
|
31
|
+
* and interacting with the Lightning Network.
|
|
32
|
+
*/
|
|
26
33
|
export class SparkWallet {
|
|
27
34
|
config;
|
|
28
35
|
connectionManager;
|
|
@@ -139,21 +146,49 @@ export class SparkWallet {
|
|
|
139
146
|
this.leaves = await this.getLeaves();
|
|
140
147
|
}
|
|
141
148
|
async syncWallet() {
|
|
142
|
-
await
|
|
143
|
-
|
|
144
|
-
|
|
149
|
+
await Promise.all([
|
|
150
|
+
this.claimTransfers(),
|
|
151
|
+
this.claimDeposits(),
|
|
152
|
+
this.syncTokenLeaves(),
|
|
153
|
+
]);
|
|
145
154
|
this.leaves = await this.getLeaves();
|
|
146
|
-
await this
|
|
155
|
+
await this.#refreshTimelockNodes();
|
|
156
|
+
// await this.optimizeLeaves();
|
|
147
157
|
}
|
|
148
158
|
isInitialized() {
|
|
149
159
|
return this.sspClient !== null && this.wasmModule !== null;
|
|
150
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* Gets the identity public key of the wallet.
|
|
163
|
+
*
|
|
164
|
+
* @returns {Promise<string>} The identity public key as a hex string.
|
|
165
|
+
*/
|
|
151
166
|
async getIdentityPublicKey() {
|
|
152
167
|
return bytesToHex(await this.config.signer.getIdentityPublicKey());
|
|
153
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* Gets the Spark address of the wallet.
|
|
171
|
+
*
|
|
172
|
+
* @returns {Promise<string>} The Spark address as a hex string.
|
|
173
|
+
*/
|
|
154
174
|
async getSparkAddress() {
|
|
155
175
|
return bytesToHex(await this.config.signer.getIdentityPublicKey());
|
|
156
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* Initializes the wallet using either a mnemonic phrase or a raw seed.
|
|
179
|
+
* initWallet will also claim any pending incoming lightning payment, spark transfer,
|
|
180
|
+
* or bitcoin deposit.
|
|
181
|
+
*
|
|
182
|
+
* @param {Uint8Array | string} [mnemonicOrSeed] - (Optional) Either:
|
|
183
|
+
* - A BIP-39 mnemonic phrase as string
|
|
184
|
+
* - A raw seed as Uint8Array or hex string
|
|
185
|
+
* If not provided, generates a new mnemonic and uses it to create a new wallet
|
|
186
|
+
*
|
|
187
|
+
* @returns {Promise<Object>} Object containing:
|
|
188
|
+
* - mnemonic: The mnemonic if one was generated (undefined for raw seed)
|
|
189
|
+
* - balance: The wallet's initial balance in satoshis
|
|
190
|
+
* - tokenBalance: Map of token balances and leaf counts
|
|
191
|
+
*/
|
|
157
192
|
async initWallet(mnemonicOrSeed) {
|
|
158
193
|
const returnMnemonic = !mnemonicOrSeed;
|
|
159
194
|
if (!mnemonicOrSeed) {
|
|
@@ -185,15 +220,31 @@ export class SparkWallet {
|
|
|
185
220
|
};
|
|
186
221
|
}
|
|
187
222
|
async initWalletFromMnemonic(mnemonic) {
|
|
188
|
-
const identityPublicKey = await this.config.signer.createSparkWalletFromMnemonic(mnemonic);
|
|
223
|
+
const identityPublicKey = await this.config.signer.createSparkWalletFromMnemonic(mnemonic, this.config.getNetwork());
|
|
189
224
|
await this.initializeWallet(identityPublicKey);
|
|
190
225
|
return identityPublicKey;
|
|
191
226
|
}
|
|
227
|
+
/**
|
|
228
|
+
* Initializes a wallet from a seed.
|
|
229
|
+
*
|
|
230
|
+
* @param {Uint8Array | string} seed - The seed to initialize the wallet from
|
|
231
|
+
* @returns {Promise<string>} The identity public key
|
|
232
|
+
* @private
|
|
233
|
+
*/
|
|
192
234
|
async initWalletFromSeed(seed) {
|
|
193
|
-
const identityPublicKey = await this.config.signer.createSparkWalletFromSeed(seed);
|
|
235
|
+
const identityPublicKey = await this.config.signer.createSparkWalletFromSeed(seed, this.config.getNetwork());
|
|
194
236
|
await this.initializeWallet(identityPublicKey);
|
|
195
237
|
return identityPublicKey;
|
|
196
238
|
}
|
|
239
|
+
/**
|
|
240
|
+
* Requests a swap of leaves to optimize wallet structure.
|
|
241
|
+
*
|
|
242
|
+
* @param {Object} params - Parameters for the leaves swap
|
|
243
|
+
* @param {number} [params.targetAmount] - Target amount for the swap
|
|
244
|
+
* @param {TreeNode[]} [params.leaves] - Specific leaves to swap
|
|
245
|
+
* @returns {Promise<Object>} The completed swap response
|
|
246
|
+
* @private
|
|
247
|
+
*/
|
|
197
248
|
async requestLeavesSwap({ targetAmount, leaves, }) {
|
|
198
249
|
if (targetAmount && targetAmount <= 0) {
|
|
199
250
|
throw new Error("targetAmount must be positive");
|
|
@@ -320,9 +371,26 @@ export class SparkWallet {
|
|
|
320
371
|
throw new Error(`Failed to request leaves swap: ${e}`);
|
|
321
372
|
}
|
|
322
373
|
}
|
|
374
|
+
/**
|
|
375
|
+
* Gets all transfers for the wallet.
|
|
376
|
+
*
|
|
377
|
+
* @param {number} [limit=20] - Maximum number of transfers to return
|
|
378
|
+
* @param {number} [offset=0] - Offset for pagination
|
|
379
|
+
* @returns {Promise<QueryAllTransfersResponse>} Response containing the list of transfers
|
|
380
|
+
*/
|
|
323
381
|
async getAllTransfers(limit = 20, offset = 0) {
|
|
324
382
|
return await this.transferService.queryAllTransfers(limit, offset);
|
|
325
383
|
}
|
|
384
|
+
/**
|
|
385
|
+
* Gets the current balance of the wallet.
|
|
386
|
+
* You can use the forceRefetch option to synchronize your wallet and claim any
|
|
387
|
+
* pending incoming lightning payment, spark transfer, or bitcoin deposit before returning the balance.
|
|
388
|
+
*
|
|
389
|
+
* @param {boolean} [forceRefetch=false] - Synchronizes the wallet before returning the balance
|
|
390
|
+
* @returns {Promise<Object>} Object containing:
|
|
391
|
+
* - balance: The wallet's current balance in satoshis
|
|
392
|
+
* - tokenBalances: Map of token balances and leaf counts
|
|
393
|
+
*/
|
|
326
394
|
async getBalance(forceRefetch = false) {
|
|
327
395
|
if (forceRefetch) {
|
|
328
396
|
await Promise.all([
|
|
@@ -330,7 +398,6 @@ export class SparkWallet {
|
|
|
330
398
|
this.claimDeposits(),
|
|
331
399
|
this.syncTokenLeaves(),
|
|
332
400
|
]);
|
|
333
|
-
await this.syncTokenLeaves();
|
|
334
401
|
this.leaves = await this.getLeaves();
|
|
335
402
|
}
|
|
336
403
|
const tokenBalances = new Map();
|
|
@@ -347,9 +414,23 @@ export class SparkWallet {
|
|
|
347
414
|
};
|
|
348
415
|
}
|
|
349
416
|
// ***** Deposit Flow *****
|
|
417
|
+
/**
|
|
418
|
+
* Generates a new deposit address for receiving bitcoin funds.
|
|
419
|
+
* Note that this function returns a bitcoin address, not a spark address.
|
|
420
|
+
* For Layer 1 Bitcoin deposits, Spark generates Pay to Taproot (P2TR) addresses.
|
|
421
|
+
* These addresses start with "bc1p" and can be used to receive Bitcoin from any wallet.
|
|
422
|
+
*
|
|
423
|
+
* @returns {Promise<string>} A Bitcoin address for depositing funds
|
|
424
|
+
*/
|
|
350
425
|
async getDepositAddress() {
|
|
351
426
|
return await this.generateDepositAddress();
|
|
352
427
|
}
|
|
428
|
+
/**
|
|
429
|
+
* Generates a deposit address for receiving funds.
|
|
430
|
+
*
|
|
431
|
+
* @returns {Promise<string>} A deposit address
|
|
432
|
+
* @private
|
|
433
|
+
*/
|
|
353
434
|
async generateDepositAddress() {
|
|
354
435
|
const signingPubkey = await this.config.signer.getDepositSigningKey();
|
|
355
436
|
const address = await this.depositService.generateDepositAddress({
|
|
@@ -360,6 +441,13 @@ export class SparkWallet {
|
|
|
360
441
|
}
|
|
361
442
|
return address.depositAddress.address;
|
|
362
443
|
}
|
|
444
|
+
/**
|
|
445
|
+
* Finalizes a deposit to the wallet.
|
|
446
|
+
*
|
|
447
|
+
* @param {DepositParams} params - Parameters for finalizing the deposit
|
|
448
|
+
* @returns {Promise<TreeNode[] | undefined>} The nodes created from the deposit
|
|
449
|
+
* @private
|
|
450
|
+
*/
|
|
363
451
|
async finalizeDeposit({ signingPubKey, verifyingKey, depositTx, vout, }) {
|
|
364
452
|
const response = await this.depositService.createTreeRoot({
|
|
365
453
|
signingPubKey,
|
|
@@ -369,6 +457,12 @@ export class SparkWallet {
|
|
|
369
457
|
});
|
|
370
458
|
return await this.transferDepositToSelf(response.nodes, signingPubKey);
|
|
371
459
|
}
|
|
460
|
+
/**
|
|
461
|
+
* Claims any pending deposits to the wallet.
|
|
462
|
+
*
|
|
463
|
+
* @returns {Promise<TreeNode[]>} The nodes created from the claimed deposits
|
|
464
|
+
* @private
|
|
465
|
+
*/
|
|
372
466
|
async claimDeposits() {
|
|
373
467
|
const sparkClient = await this.connectionManager.createSparkClient(this.config.getCoordinatorAddress());
|
|
374
468
|
const identityPublicKey = await this.config.signer.getIdentityPublicKey();
|
|
@@ -394,14 +488,27 @@ export class SparkWallet {
|
|
|
394
488
|
}
|
|
395
489
|
return depositNodes;
|
|
396
490
|
}
|
|
491
|
+
/**
|
|
492
|
+
* Queries the mempool for transactions associated with an address.
|
|
493
|
+
*
|
|
494
|
+
* @param {string} address - The address to query
|
|
495
|
+
* @returns {Promise<{depositTx: Transaction, vout: number} | null>} Transaction details or null if none found
|
|
496
|
+
* @private
|
|
497
|
+
*/
|
|
397
498
|
async queryMempoolTxs(address) {
|
|
398
|
-
const
|
|
499
|
+
const network = getNetworkFromAddress(address) || this.config.getNetwork();
|
|
500
|
+
const baseUrl = network === BitcoinNetwork.REGTEST
|
|
501
|
+
? "https://regtest-mempool.dev.dev.sparkinfra.net/api"
|
|
502
|
+
: "https://mempool.space/docs/api/rest";
|
|
399
503
|
const auth = btoa("spark-sdk:mCMk1JqlBNtetUNy");
|
|
504
|
+
const headers = {
|
|
505
|
+
"Content-Type": "application/json",
|
|
506
|
+
};
|
|
507
|
+
if (network === BitcoinNetwork.REGTEST) {
|
|
508
|
+
headers["Authorization"] = `Basic ${auth}`;
|
|
509
|
+
}
|
|
400
510
|
const response = await fetch(`${baseUrl}/address/${address}/txs`, {
|
|
401
|
-
headers
|
|
402
|
-
Authorization: `Basic ${auth}`,
|
|
403
|
-
"Content-Type": "application/json",
|
|
404
|
-
},
|
|
511
|
+
headers,
|
|
405
512
|
});
|
|
406
513
|
const addressTxs = await response.json();
|
|
407
514
|
if (addressTxs && addressTxs.length > 0) {
|
|
@@ -411,10 +518,7 @@ export class SparkWallet {
|
|
|
411
518
|
return null;
|
|
412
519
|
}
|
|
413
520
|
const txResponse = await fetch(`${baseUrl}/tx/${latestTx.txid}/hex`, {
|
|
414
|
-
headers
|
|
415
|
-
Authorization: `Basic ${auth}`,
|
|
416
|
-
"Content-Type": "application/json",
|
|
417
|
-
},
|
|
521
|
+
headers,
|
|
418
522
|
});
|
|
419
523
|
const txHex = await txResponse.text();
|
|
420
524
|
const depositTx = getTxFromRawTxHex(txHex);
|
|
@@ -425,6 +529,14 @@ export class SparkWallet {
|
|
|
425
529
|
}
|
|
426
530
|
return null;
|
|
427
531
|
}
|
|
532
|
+
/**
|
|
533
|
+
* Transfers deposit to self to claim ownership.
|
|
534
|
+
*
|
|
535
|
+
* @param {TreeNode[]} leaves - The leaves to transfer
|
|
536
|
+
* @param {Uint8Array} signingPubKey - The signing public key
|
|
537
|
+
* @returns {Promise<TreeNode[] | undefined>} The nodes resulting from the transfer
|
|
538
|
+
* @private
|
|
539
|
+
*/
|
|
428
540
|
async transferDepositToSelf(leaves, signingPubKey) {
|
|
429
541
|
const leafKeyTweaks = await Promise.all(leaves.map(async (leaf) => ({
|
|
430
542
|
leaf,
|
|
@@ -440,12 +552,27 @@ export class SparkWallet {
|
|
|
440
552
|
return;
|
|
441
553
|
}
|
|
442
554
|
// ***** Transfer Flow *****
|
|
555
|
+
/**
|
|
556
|
+
* Sends a transfer to another Spark user.
|
|
557
|
+
*
|
|
558
|
+
* @param {Object} params - Parameters for the transfer
|
|
559
|
+
* @param {string} params.receiverSparkAddress - The recipient's Spark address
|
|
560
|
+
* @param {number} params.amountSats - Amount to send in satoshis
|
|
561
|
+
* @returns {Promise<Transfer>} The completed transfer details
|
|
562
|
+
*/
|
|
443
563
|
async sendSparkTransfer({ receiverSparkAddress, amountSats, }) {
|
|
444
564
|
return await this._sendTransfer({
|
|
445
565
|
receiverPubKey: receiverSparkAddress,
|
|
446
566
|
amount: amountSats,
|
|
447
567
|
});
|
|
448
568
|
}
|
|
569
|
+
/**
|
|
570
|
+
* Internal method to send a transfer.
|
|
571
|
+
*
|
|
572
|
+
* @param {SendTransferParams} params - Parameters for the transfer
|
|
573
|
+
* @returns {Promise<Transfer>} The completed transfer details
|
|
574
|
+
* @private
|
|
575
|
+
*/
|
|
449
576
|
async _sendTransfer({ amount, receiverPubKey, leaves, expiryTime = new Date(Date.now() + 10 * 60 * 1000), }) {
|
|
450
577
|
return await this.sendTransferMutex.runExclusive(async () => {
|
|
451
578
|
let leavesToSend = [];
|
|
@@ -460,6 +587,7 @@ export class SparkWallet {
|
|
|
460
587
|
else {
|
|
461
588
|
throw new Error("Must provide amount or leaves");
|
|
462
589
|
}
|
|
590
|
+
await this.#refreshTimelockNodes();
|
|
463
591
|
const leafKeyTweaks = await Promise.all(leavesToSend.map(async (leaf) => ({
|
|
464
592
|
leaf,
|
|
465
593
|
signingPubKey: await this.config.signer.generatePublicKey(sha256(leaf.id)),
|
|
@@ -471,6 +599,83 @@ export class SparkWallet {
|
|
|
471
599
|
return transfer;
|
|
472
600
|
});
|
|
473
601
|
}
|
|
602
|
+
/**
|
|
603
|
+
* Internal method to refresh timelock nodes.
|
|
604
|
+
*
|
|
605
|
+
* @param {string} nodeId - The optional ID of the node to refresh. If not provided, all nodes will be checked.
|
|
606
|
+
* @returns {Promise<void>}
|
|
607
|
+
* @private
|
|
608
|
+
*/
|
|
609
|
+
async #refreshTimelockNodes(nodeId) {
|
|
610
|
+
const nodesToRefresh = [];
|
|
611
|
+
const nodeIds = [];
|
|
612
|
+
if (nodeId) {
|
|
613
|
+
for (const node of this.leaves) {
|
|
614
|
+
if (node.id === nodeId) {
|
|
615
|
+
nodesToRefresh.push(node);
|
|
616
|
+
nodeIds.push(node.id);
|
|
617
|
+
break;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
if (nodesToRefresh.length === 0) {
|
|
621
|
+
throw new Error(`node ${nodeId} not found`);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
else {
|
|
625
|
+
for (const node of this.leaves) {
|
|
626
|
+
const refundTx = getTxFromRawTxBytes(node.refundTx);
|
|
627
|
+
const nextSequence = getNextTransactionSequence(refundTx.getInput(0).sequence);
|
|
628
|
+
const needRefresh = nextSequence <= 0;
|
|
629
|
+
if (needRefresh) {
|
|
630
|
+
nodesToRefresh.push(node);
|
|
631
|
+
nodeIds.push(node.id);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
if (nodesToRefresh.length === 0) {
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
const sparkClient = await this.connectionManager.createSparkClient(this.config.getCoordinatorAddress());
|
|
639
|
+
const nodesResp = await sparkClient.query_nodes({
|
|
640
|
+
source: {
|
|
641
|
+
$case: "nodeIds",
|
|
642
|
+
nodeIds: {
|
|
643
|
+
nodeIds,
|
|
644
|
+
},
|
|
645
|
+
},
|
|
646
|
+
includeParents: true,
|
|
647
|
+
});
|
|
648
|
+
const nodesMap = new Map();
|
|
649
|
+
for (const node of Object.values(nodesResp.nodes)) {
|
|
650
|
+
nodesMap.set(node.id, node);
|
|
651
|
+
}
|
|
652
|
+
for (const node of nodesToRefresh) {
|
|
653
|
+
if (!node.parentNodeId) {
|
|
654
|
+
throw new Error(`node ${node.id} has no parent`);
|
|
655
|
+
}
|
|
656
|
+
const parentNode = nodesMap.get(node.parentNodeId);
|
|
657
|
+
if (!parentNode) {
|
|
658
|
+
throw new Error(`parent node ${node.parentNodeId} not found`);
|
|
659
|
+
}
|
|
660
|
+
const { nodes } = await this.transferService.refreshTimelockNodes([node], parentNode, await this.config.signer.generatePublicKey(sha256(node.id)));
|
|
661
|
+
if (nodes.length !== 1) {
|
|
662
|
+
throw new Error(`expected 1 node, got ${nodes.length}`);
|
|
663
|
+
}
|
|
664
|
+
const newNode = nodes[0];
|
|
665
|
+
if (!newNode) {
|
|
666
|
+
throw new Error("Failed to refresh timelock node");
|
|
667
|
+
}
|
|
668
|
+
this.leaves = this.leaves.filter((leaf) => leaf.id !== node.id);
|
|
669
|
+
this.leaves.push(newNode);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Claims a specific transfer.
|
|
674
|
+
*
|
|
675
|
+
* @param {Transfer} transfer - The transfer to claim
|
|
676
|
+
* @returns {Promise<Object>} The claim result
|
|
677
|
+
* @private
|
|
678
|
+
*/
|
|
474
679
|
async claimTransfer(transfer) {
|
|
475
680
|
return await this.claimTransferMutex.runExclusive(async () => {
|
|
476
681
|
const leafPubKeyMap = await this.transferService.verifyPendingTransfer(transfer);
|
|
@@ -487,9 +692,18 @@ export class SparkWallet {
|
|
|
487
692
|
}
|
|
488
693
|
}
|
|
489
694
|
}
|
|
490
|
-
|
|
695
|
+
const response = await this.transferService.claimTransfer(transfer, leavesToClaim);
|
|
696
|
+
this.leaves.push(...response.nodes);
|
|
697
|
+
await this.#refreshTimelockNodes();
|
|
698
|
+
return response;
|
|
491
699
|
});
|
|
492
700
|
}
|
|
701
|
+
/**
|
|
702
|
+
* Claims all pending transfers.
|
|
703
|
+
*
|
|
704
|
+
* @returns {Promise<boolean>} True if any transfers were claimed
|
|
705
|
+
* @private
|
|
706
|
+
*/
|
|
493
707
|
async claimTransfers() {
|
|
494
708
|
const transfers = await this.transferService.queryPendingTransfers();
|
|
495
709
|
let claimed = false;
|
|
@@ -506,6 +720,12 @@ export class SparkWallet {
|
|
|
506
720
|
}
|
|
507
721
|
return claimed;
|
|
508
722
|
}
|
|
723
|
+
/**
|
|
724
|
+
* Cancels all sender-initiated transfers.
|
|
725
|
+
*
|
|
726
|
+
* @returns {Promise<void>}
|
|
727
|
+
* @private
|
|
728
|
+
*/
|
|
509
729
|
async cancelAllSenderInitiatedTransfers() {
|
|
510
730
|
const transfers = await this.transferService.queryPendingTransfersBySender();
|
|
511
731
|
for (const transfer of transfers.transfers) {
|
|
@@ -515,6 +735,14 @@ export class SparkWallet {
|
|
|
515
735
|
}
|
|
516
736
|
}
|
|
517
737
|
// ***** Lightning Flow *****
|
|
738
|
+
/**
|
|
739
|
+
* Creates a Lightning invoice for receiving payments.
|
|
740
|
+
*
|
|
741
|
+
* @param {Object} params - Parameters for the lightning invoice
|
|
742
|
+
* @param {number} params.amountSats - Amount in satoshis
|
|
743
|
+
* @param {string} params.memo - Description for the invoice
|
|
744
|
+
* @returns {Promise<string>} BOLT11 encoded invoice
|
|
745
|
+
*/
|
|
518
746
|
async createLightningInvoice({ amountSats, memo, }) {
|
|
519
747
|
const expirySeconds = 60 * 60 * 24 * 30;
|
|
520
748
|
if (!this.sspClient) {
|
|
@@ -544,6 +772,13 @@ export class SparkWallet {
|
|
|
544
772
|
invoiceCreator: requestLightningInvoice,
|
|
545
773
|
});
|
|
546
774
|
}
|
|
775
|
+
/**
|
|
776
|
+
* Pays a Lightning invoice.
|
|
777
|
+
*
|
|
778
|
+
* @param {Object} params - Parameters for paying the invoice
|
|
779
|
+
* @param {string} params.invoice - The BOLT11-encoded Lightning invoice to pay
|
|
780
|
+
* @returns {Promise<LightningSendRequest>} The Lightning payment request details
|
|
781
|
+
*/
|
|
547
782
|
async payLightningInvoice({ invoice }) {
|
|
548
783
|
if (!this.sspClient) {
|
|
549
784
|
throw new Error("SSP client not initialized");
|
|
@@ -561,6 +796,7 @@ export class SparkWallet {
|
|
|
561
796
|
}
|
|
562
797
|
// fetch leaves for amount
|
|
563
798
|
const leaves = await this.selectLeaves(amountSats);
|
|
799
|
+
await this.#refreshTimelockNodes();
|
|
564
800
|
const leavesToSend = await Promise.all(leaves.map(async (leaf) => ({
|
|
565
801
|
leaf,
|
|
566
802
|
signingPubKey: await this.config.signer.generatePublicKey(sha256(leaf.id)),
|
|
@@ -588,12 +824,26 @@ export class SparkWallet {
|
|
|
588
824
|
this.leaves = this.leaves.filter((leaf) => !leavesToRemove.has(leaf.id));
|
|
589
825
|
return sspResponse;
|
|
590
826
|
}
|
|
827
|
+
/**
|
|
828
|
+
* Gets fee estimate for receiving Lightning payments.
|
|
829
|
+
*
|
|
830
|
+
* @param {LightningReceiveFeeEstimateInput} params - Input parameters for fee estimation
|
|
831
|
+
* @returns {Promise<LightningReceiveFeeEstimateOutput | null>} Fee estimate for receiving Lightning payments
|
|
832
|
+
* @private
|
|
833
|
+
*/
|
|
591
834
|
async getLightningReceiveFeeEstimate({ amountSats, network, }) {
|
|
592
835
|
if (!this.sspClient) {
|
|
593
836
|
throw new Error("SSP client not initialized");
|
|
594
837
|
}
|
|
595
838
|
return await this.sspClient.getLightningReceiveFeeEstimate(amountSats, network);
|
|
596
839
|
}
|
|
840
|
+
/**
|
|
841
|
+
* Gets fee estimate for sending Lightning payments.
|
|
842
|
+
*
|
|
843
|
+
* @param {LightningSendFeeEstimateInput} params - Input parameters for fee estimation
|
|
844
|
+
* @returns {Promise<LightningSendFeeEstimateOutput | null>} Fee estimate for sending Lightning payments
|
|
845
|
+
* @private
|
|
846
|
+
*/
|
|
597
847
|
async getLightningSendFeeEstimate({ encodedInvoice, }) {
|
|
598
848
|
if (!this.sspClient) {
|
|
599
849
|
throw new Error("SSP client not initialized");
|
|
@@ -601,16 +851,53 @@ export class SparkWallet {
|
|
|
601
851
|
return await this.sspClient.getLightningSendFeeEstimate(encodedInvoice);
|
|
602
852
|
}
|
|
603
853
|
// ***** Tree Creation Flow *****
|
|
854
|
+
/**
|
|
855
|
+
* Generates a deposit address for a tree.
|
|
856
|
+
*
|
|
857
|
+
* @param {number} vout - The vout index
|
|
858
|
+
* @param {Uint8Array} parentSigningPubKey - The parent signing public key
|
|
859
|
+
* @param {Transaction} [parentTx] - Optional parent transaction
|
|
860
|
+
* @param {TreeNode} [parentNode] - Optional parent node
|
|
861
|
+
* @returns {Promise<Object>} Deposit address information
|
|
862
|
+
* @private
|
|
863
|
+
*/
|
|
604
864
|
async generateDepositAddressForTree(vout, parentSigningPubKey, parentTx, parentNode) {
|
|
605
865
|
return await this.treeCreationService.generateDepositAddressForTree(vout, parentSigningPubKey, parentTx, parentNode);
|
|
606
866
|
}
|
|
867
|
+
/**
|
|
868
|
+
* Creates a tree structure.
|
|
869
|
+
*
|
|
870
|
+
* @param {number} vout - The vout index
|
|
871
|
+
* @param {DepositAddressTree} root - The root of the tree
|
|
872
|
+
* @param {boolean} createLeaves - Whether to create leaves
|
|
873
|
+
* @param {Transaction} [parentTx] - Optional parent transaction
|
|
874
|
+
* @param {TreeNode} [parentNode] - Optional parent node
|
|
875
|
+
* @returns {Promise<Object>} The created tree
|
|
876
|
+
* @private
|
|
877
|
+
*/
|
|
607
878
|
async createTree(vout, root, createLeaves, parentTx, parentNode) {
|
|
608
879
|
return await this.treeCreationService.createTree(vout, root, createLeaves, parentTx, parentNode);
|
|
609
880
|
}
|
|
610
881
|
// ***** Cooperative Exit Flow *****
|
|
882
|
+
/**
|
|
883
|
+
* Initiates a withdrawal to move funds from the Spark network to an on-chain Bitcoin address.
|
|
884
|
+
*
|
|
885
|
+
* @param {Object} params - Parameters for the withdrawal
|
|
886
|
+
* @param {string} params.onchainAddress - The Bitcoin address where the funds should be sent
|
|
887
|
+
* @param {number} [params.targetAmountSats] - The amount in satoshis to withdraw. If not specified, attempts to withdraw all available funds
|
|
888
|
+
* @returns {Promise<CoopExitRequest | null | undefined>} The withdrawal request details, or null/undefined if the request cannot be completed
|
|
889
|
+
*/
|
|
611
890
|
async withdraw({ onchainAddress, targetAmountSats, }) {
|
|
612
891
|
return this.coopExit(onchainAddress, targetAmountSats);
|
|
613
892
|
}
|
|
893
|
+
/**
|
|
894
|
+
* Internal method to perform a cooperative exit (withdrawal).
|
|
895
|
+
*
|
|
896
|
+
* @param {string} onchainAddress - The Bitcoin address where the funds should be sent
|
|
897
|
+
* @param {number} [targetAmountSats] - The amount in satoshis to withdraw
|
|
898
|
+
* @returns {Promise<Object | null | undefined>} The exit request details
|
|
899
|
+
* @private
|
|
900
|
+
*/
|
|
614
901
|
async coopExit(onchainAddress, targetAmountSats) {
|
|
615
902
|
let leavesToSend = [];
|
|
616
903
|
if (targetAmountSats) {
|
|
@@ -621,7 +908,6 @@ export class SparkWallet {
|
|
|
621
908
|
...leaf,
|
|
622
909
|
}));
|
|
623
910
|
}
|
|
624
|
-
const pubkey = await this.config.signer.getIdentityPublicKey();
|
|
625
911
|
const leafKeyTweaks = await Promise.all(leavesToSend.map(async (leaf) => ({
|
|
626
912
|
leaf,
|
|
627
913
|
signingPubKey: await this.config.signer.generatePublicKey(sha256(leaf.id)),
|
|
@@ -635,18 +921,22 @@ export class SparkWallet {
|
|
|
635
921
|
throw new Error("Failed to request coop exit");
|
|
636
922
|
}
|
|
637
923
|
const connectorTx = getTxFromRawTxHex(coopExitRequest.rawConnectorTransaction);
|
|
638
|
-
const coopExitTxId =
|
|
924
|
+
const coopExitTxId = connectorTx.getInput(0).txid;
|
|
925
|
+
const connectorTxId = getTxId(connectorTx);
|
|
926
|
+
if (!coopExitTxId) {
|
|
927
|
+
throw new Error("Failed to get coop exit tx id");
|
|
928
|
+
}
|
|
639
929
|
const connectorOutputs = [];
|
|
640
930
|
for (let i = 0; i < connectorTx.outputsLength - 1; i++) {
|
|
641
931
|
connectorOutputs.push({
|
|
642
|
-
txid: hexToBytes(
|
|
932
|
+
txid: hexToBytes(connectorTxId),
|
|
643
933
|
index: i,
|
|
644
934
|
});
|
|
645
935
|
}
|
|
646
936
|
const sspPubIdentityKey = await this.config.signer.getSspIdentityPublicKey(this.config.getNetwork());
|
|
647
937
|
const transfer = await this.coopExitService.getConnectorRefundSignatures({
|
|
648
938
|
leaves: leafKeyTweaks,
|
|
649
|
-
exitTxId:
|
|
939
|
+
exitTxId: coopExitTxId,
|
|
650
940
|
connectorOutputs,
|
|
651
941
|
receiverPubKey: sspPubIdentityKey,
|
|
652
942
|
});
|
|
@@ -656,6 +946,13 @@ export class SparkWallet {
|
|
|
656
946
|
});
|
|
657
947
|
return completeResponse;
|
|
658
948
|
}
|
|
949
|
+
/**
|
|
950
|
+
* Gets fee estimate for cooperative exit (on-chain withdrawal).
|
|
951
|
+
*
|
|
952
|
+
* @param {CoopExitFeeEstimateInput} params - Input parameters for fee estimation
|
|
953
|
+
* @returns {Promise<CoopExitFeeEstimateOutput | null>} Fee estimate for the withdrawal
|
|
954
|
+
* @private
|
|
955
|
+
*/
|
|
659
956
|
async getCoopExitFeeEstimate({ leafExternalIds, withdrawalAddress, }) {
|
|
660
957
|
if (!this.sspClient) {
|
|
661
958
|
throw new Error("SSP client not initialized");
|
|
@@ -666,6 +963,12 @@ export class SparkWallet {
|
|
|
666
963
|
});
|
|
667
964
|
}
|
|
668
965
|
// ***** Token Flow *****
|
|
966
|
+
/**
|
|
967
|
+
* Synchronizes token leaves for the wallet.
|
|
968
|
+
*
|
|
969
|
+
* @returns {Promise<void>}
|
|
970
|
+
* @private
|
|
971
|
+
*/
|
|
669
972
|
async syncTokenLeaves() {
|
|
670
973
|
this.tokenLeaves.clear();
|
|
671
974
|
const trackedPublicKeys = await this.config.signer.getTrackedPublicKeys();
|
|
@@ -685,6 +988,12 @@ export class SparkWallet {
|
|
|
685
988
|
});
|
|
686
989
|
this.tokenLeaves = groupedLeaves;
|
|
687
990
|
}
|
|
991
|
+
/**
|
|
992
|
+
* Gets all token balances.
|
|
993
|
+
*
|
|
994
|
+
* @returns {Promise<Map<string, { balance: bigint, leafCount: number }>>} Map of token balances and leaf counts
|
|
995
|
+
* @private
|
|
996
|
+
*/
|
|
688
997
|
async getAllTokenBalances() {
|
|
689
998
|
await this.syncTokenLeaves();
|
|
690
999
|
const balances = new Map();
|
|
@@ -696,6 +1005,15 @@ export class SparkWallet {
|
|
|
696
1005
|
}
|
|
697
1006
|
return balances;
|
|
698
1007
|
}
|
|
1008
|
+
/**
|
|
1009
|
+
* Transfers tokens to another user.
|
|
1010
|
+
*
|
|
1011
|
+
* @param {string} tokenPublicKey - The public key of the token to transfer
|
|
1012
|
+
* @param {bigint} tokenAmount - The amount of tokens to transfer
|
|
1013
|
+
* @param {string} recipientPublicKey - The recipient's public key
|
|
1014
|
+
* @param {LeafWithPreviousTransactionData[]} [selectedLeaves] - Optional specific leaves to use for the transfer
|
|
1015
|
+
* @returns {Promise<string>} The transaction ID of the token transfer
|
|
1016
|
+
*/
|
|
699
1017
|
async transferTokens(tokenPublicKey, tokenAmount, recipientPublicKey, selectedLeaves) {
|
|
700
1018
|
await this.syncTokenLeaves();
|
|
701
1019
|
if (!this.tokenLeaves.has(tokenPublicKey)) {
|
|
@@ -717,8 +1035,38 @@ export class SparkWallet {
|
|
|
717
1035
|
const tokenTransaction = await this.tokenTransactionService.constructTransferTokenTransaction(selectedLeaves, recipientPublicKeyBytes, tokenPublicKeyBytes, tokenAmount);
|
|
718
1036
|
return await this.tokenTransactionService.broadcastTokenTransaction(tokenTransaction, selectedLeaves.map((leaf) => leaf.leaf.ownerPublicKey), selectedLeaves.map((leaf) => leaf.leaf.revocationPublicKey));
|
|
719
1037
|
}
|
|
1038
|
+
/**
|
|
1039
|
+
* Selects token leaves for a transfer.
|
|
1040
|
+
*
|
|
1041
|
+
* @param {string} tokenPublicKey - The public key of the token
|
|
1042
|
+
* @param {bigint} tokenAmount - The amount of tokens to select leaves for
|
|
1043
|
+
* @returns {LeafWithPreviousTransactionData[]} The selected leaves
|
|
1044
|
+
* @private
|
|
1045
|
+
*/
|
|
720
1046
|
selectTokenLeaves(tokenPublicKey, tokenAmount) {
|
|
721
1047
|
return this.tokenTransactionService.selectTokenLeaves(this.tokenLeaves.get(tokenPublicKey), tokenAmount);
|
|
722
1048
|
}
|
|
723
1049
|
}
|
|
1050
|
+
/**
|
|
1051
|
+
* Utility function to determine the network from a Bitcoin address.
|
|
1052
|
+
*
|
|
1053
|
+
* @param {string} address - The Bitcoin address
|
|
1054
|
+
* @returns {BitcoinNetwork | null} The detected network or null if not detected
|
|
1055
|
+
*/
|
|
1056
|
+
function getNetworkFromAddress(address) {
|
|
1057
|
+
try {
|
|
1058
|
+
const decoded = bitcoin.address.fromBech32(address);
|
|
1059
|
+
// HRP (human-readable part) determines the network
|
|
1060
|
+
if (decoded.prefix === "bc") {
|
|
1061
|
+
return BitcoinNetwork.MAINNET;
|
|
1062
|
+
}
|
|
1063
|
+
else if (decoded.prefix === "bcrt") {
|
|
1064
|
+
return BitcoinNetwork.REGTEST;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
catch (err) {
|
|
1068
|
+
throw new Error("Invalid Bitcoin address");
|
|
1069
|
+
}
|
|
1070
|
+
return null;
|
|
1071
|
+
}
|
|
724
1072
|
//# sourceMappingURL=spark-sdk.js.map
|