@buildonspark/spark-sdk 0.1.39 → 0.1.41
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 +14 -0
- package/README.md +1 -1
- package/dist/{RequestLightningSendInput-39_zGri6.d.cts → RequestLightningSendInput-DXcLoiCe.d.cts} +10 -2
- package/dist/{RequestLightningSendInput-B4JdzclX.d.ts → RequestLightningSendInput-mXUWn_cp.d.ts} +10 -2
- package/dist/address/index.cjs +138 -6
- package/dist/address/index.d.cts +18 -6
- package/dist/address/index.d.ts +18 -6
- package/dist/address/index.js +5 -2
- package/dist/{chunk-FWQPAPXK.js → chunk-2ZXXLPG2.js} +1 -1
- package/dist/{chunk-S7KD6DDL.js → chunk-6YVPOQ2A.js} +41 -20
- package/dist/{chunk-ZUVYYR5T.js → chunk-7EFSUADA.js} +1 -0
- package/dist/{chunk-NS4UZRQ7.js → chunk-ABZA6R5S.js} +1 -1
- package/dist/{chunk-57XLH3ZR.js → chunk-ATEHMLKP.js} +23 -23
- package/dist/{chunk-VJTDG4BQ.js → chunk-HK6LPV6Z.js} +10 -1
- package/dist/{chunk-W3EC5XSA.js → chunk-J5W5Q2ZP.js} +337 -72
- package/dist/{chunk-TKYOYOYJ.js → chunk-KKSU7OZO.js} +653 -76
- package/dist/chunk-L3EHBOUX.js +0 -0
- package/dist/{chunk-C5LTJBI7.js → chunk-M6A4KFIG.js} +125 -226
- package/dist/{chunk-A74XSEW3.js → chunk-MIVX3GHD.js} +1 -1
- package/dist/{chunk-RGWBSZIO.js → chunk-ROKY5KS4.js} +23 -3
- package/dist/{chunk-LIP2K6KR.js → chunk-TM4TOEOX.js} +26 -8
- package/dist/{chunk-RAPBVYJY.js → chunk-UKT6OFLO.js} +125 -35
- package/dist/chunk-VA7MV4MZ.js +1073 -0
- package/dist/chunk-YEZDPUFY.js +840 -0
- package/dist/{chunk-DI7QXUQJ.js → chunk-ZXDE2XMU.js} +8 -5
- package/dist/graphql/objects/index.cjs +6 -3
- package/dist/graphql/objects/index.d.cts +6 -5
- package/dist/graphql/objects/index.d.ts +6 -5
- package/dist/graphql/objects/index.js +1 -1
- package/dist/{index-DEo_hdN3.d.cts → index-CFh4uWzi.d.cts} +60 -6
- package/dist/{index-BVY0yH_H.d.ts → index-OSDtPMmC.d.ts} +60 -6
- package/dist/index.cjs +3316 -954
- package/dist/index.d.cts +9 -8
- package/dist/index.d.ts +9 -8
- package/dist/index.js +48 -26
- package/dist/index.node.cjs +3316 -954
- package/dist/index.node.d.cts +9 -8
- package/dist/index.node.d.ts +9 -8
- package/dist/index.node.js +48 -26
- package/dist/native/index.cjs +3323 -961
- package/dist/native/index.d.cts +542 -260
- package/dist/native/index.d.ts +542 -260
- package/dist/native/index.js +3192 -838
- package/dist/{network-DobHpaV6.d.ts → network-BF2GYPye.d.ts} +9 -2
- package/dist/{network-GFGEHkS4.d.cts → network-BiwBmoOg.d.cts} +9 -2
- 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 +125 -226
- package/dist/proto/spark.d.cts +1 -1
- package/dist/proto/spark.d.ts +1 -1
- package/dist/proto/spark.js +3 -5
- package/dist/proto/spark_token.cjs +1364 -0
- package/dist/proto/spark_token.d.cts +209 -0
- package/dist/proto/spark_token.d.ts +209 -0
- package/dist/proto/spark_token.js +32 -0
- package/dist/{sdk-types-BuVMn2rX.d.cts → sdk-types-CfhdFnsA.d.cts} +1 -1
- package/dist/{sdk-types-BeI6DM_M.d.ts → sdk-types-MnQrHolg.d.ts} +1 -1
- package/dist/services/config.cjs +64 -40
- package/dist/services/config.d.cts +6 -5
- package/dist/services/config.d.ts +6 -5
- package/dist/services/config.js +7 -7
- package/dist/services/connection.cjs +1108 -306
- package/dist/services/connection.d.cts +10 -5
- package/dist/services/connection.d.ts +10 -5
- package/dist/services/connection.js +3 -2
- package/dist/services/index.cjs +1702 -488
- package/dist/services/index.d.cts +6 -5
- package/dist/services/index.d.ts +6 -5
- package/dist/services/index.js +16 -14
- package/dist/services/lrc-connection.d.cts +5 -5
- package/dist/services/lrc-connection.d.ts +5 -5
- package/dist/services/lrc-connection.js +3 -3
- package/dist/services/token-transactions.cjs +637 -247
- package/dist/services/token-transactions.d.cts +19 -8
- package/dist/services/token-transactions.d.ts +19 -8
- package/dist/services/token-transactions.js +5 -4
- package/dist/services/wallet-config.cjs +1 -0
- package/dist/services/wallet-config.d.cts +6 -5
- package/dist/services/wallet-config.d.ts +6 -5
- package/dist/services/wallet-config.js +1 -1
- package/dist/signer/signer.cjs +122 -35
- package/dist/signer/signer.d.cts +4 -3
- package/dist/signer/signer.d.ts +4 -3
- package/dist/signer/signer.js +8 -4
- package/dist/{signer-C1t40Wus.d.cts → signer-BhLS7SYR.d.cts} +35 -14
- package/dist/{signer-DFGw9RRp.d.ts → signer-CylxIujU.d.ts} +35 -14
- package/dist/{spark-DXYE9gMM.d.ts → spark-DjR1b3TC.d.cts} +13 -21
- package/dist/{spark-DXYE9gMM.d.cts → spark-DjR1b3TC.d.ts} +13 -21
- package/dist/types/index.cjs +130 -227
- 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/utils/index.cjs +1169 -3
- package/dist/utils/index.d.cts +66 -6
- package/dist/utils/index.d.ts +66 -6
- package/dist/utils/index.js +35 -14
- package/package.json +6 -2
- package/src/address/address.ts +41 -6
- package/src/graphql/client.ts +15 -0
- package/src/graphql/objects/Transfer.ts +7 -0
- package/src/graphql/queries/Transfer.ts +10 -0
- package/src/proto/spark.ts +215 -337
- package/src/proto/spark_token.ts +1407 -0
- package/src/services/config.ts +4 -0
- package/src/services/connection.ts +37 -1
- package/src/services/deposit.ts +23 -5
- package/src/services/token-transactions.ts +426 -75
- package/src/services/transfer.ts +182 -11
- package/src/services/tree-creation.ts +29 -14
- package/src/services/wallet-config.ts +2 -0
- package/src/signer/signer.ts +190 -48
- package/src/spark-wallet/spark-wallet.ts +510 -6
- package/src/tests/integration/transfer.test.ts +186 -214
- package/src/tests/integration/tree-creation.test.ts +5 -1
- package/src/tests/signer.test.ts +34 -0
- package/src/tests/transaction.test.ts +12 -0
- package/src/tests/xchain-address.test.ts +28 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/mempool.ts +26 -1
- package/src/utils/network.ts +15 -0
- package/src/utils/transaction.ts +51 -3
- package/src/utils/unilateral-exit.ts +729 -0
- package/src/utils/xchain-address.ts +36 -0
- package/dist/chunk-E5SL7XTO.js +0 -301
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
bytesToNumberBE,
|
|
5
5
|
equalBytes,
|
|
6
6
|
hexToBytes,
|
|
7
|
+
numberToVarBytesBE,
|
|
7
8
|
} from "@noble/curves/abstract/utils";
|
|
8
9
|
import { secp256k1 } from "@noble/curves/secp256k1";
|
|
9
10
|
import { validateMnemonic } from "@scure/bip39";
|
|
@@ -11,7 +12,7 @@ import { wordlist } from "@scure/bip39/wordlists/english";
|
|
|
11
12
|
import { Address, OutScript, Transaction } from "@scure/btc-signer";
|
|
12
13
|
import { TransactionInput } from "@scure/btc-signer/psbt";
|
|
13
14
|
import { Mutex } from "async-mutex";
|
|
14
|
-
import { uuidv7 } from "uuidv7";
|
|
15
|
+
import { uuidv7, uuidv7obj } from "uuidv7";
|
|
15
16
|
import {
|
|
16
17
|
ConfigurationError,
|
|
17
18
|
NetworkError,
|
|
@@ -34,6 +35,7 @@ import {
|
|
|
34
35
|
SwapLeaf,
|
|
35
36
|
UserLeafInput,
|
|
36
37
|
} from "../graphql/objects/index.js";
|
|
38
|
+
import GraphQLTransferObj from "../graphql/objects/Transfer.js";
|
|
37
39
|
import {
|
|
38
40
|
DepositAddressQueryResult,
|
|
39
41
|
OutputWithPreviousTransactionData,
|
|
@@ -72,6 +74,7 @@ import {
|
|
|
72
74
|
} from "../utils/adaptor-signature.js";
|
|
73
75
|
import {
|
|
74
76
|
computeTaprootKeyNoScript,
|
|
77
|
+
getP2TRScriptFromPublicKey,
|
|
75
78
|
getP2WPKHAddressFromPublicKey,
|
|
76
79
|
getSigHashFromTx,
|
|
77
80
|
getTxFromRawTxBytes,
|
|
@@ -95,6 +98,7 @@ import { EventEmitter } from "eventemitter3";
|
|
|
95
98
|
import {
|
|
96
99
|
decodeSparkAddress,
|
|
97
100
|
encodeSparkAddress,
|
|
101
|
+
isValidPublicKey,
|
|
98
102
|
SparkAddressFormat,
|
|
99
103
|
} from "../address/index.js";
|
|
100
104
|
import { isReactNative } from "../constants.js";
|
|
@@ -430,9 +434,7 @@ export class SparkWallet extends EventEmitter {
|
|
|
430
434
|
}
|
|
431
435
|
}
|
|
432
436
|
|
|
433
|
-
|
|
434
|
-
isBalanceCheck: boolean = false,
|
|
435
|
-
): Promise<TreeNode[]> {
|
|
437
|
+
public async getLeaves(isBalanceCheck: boolean = false): Promise<TreeNode[]> {
|
|
436
438
|
const leaves = await this.queryNodes({
|
|
437
439
|
source: {
|
|
438
440
|
$case: "ownerIdentityPubkey",
|
|
@@ -687,6 +689,56 @@ export class SparkWallet extends EventEmitter {
|
|
|
687
689
|
return this.sparkAddress;
|
|
688
690
|
}
|
|
689
691
|
|
|
692
|
+
public async createSparkPaymentIntent(
|
|
693
|
+
assetIdentifier?: string,
|
|
694
|
+
assetAmount: bigint = BigInt(0),
|
|
695
|
+
memo?: string,
|
|
696
|
+
): Promise<SparkAddressFormat> {
|
|
697
|
+
const MAX_UINT128 = BigInt("340282366920938463463374607431768211455");
|
|
698
|
+
if (assetAmount < 0 || assetAmount > MAX_UINT128) {
|
|
699
|
+
throw new ValidationError(
|
|
700
|
+
"Asset amount must be between 0 and MAX_UINT128",
|
|
701
|
+
{
|
|
702
|
+
field: "assetAmount",
|
|
703
|
+
value: assetAmount,
|
|
704
|
+
},
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
if (memo) {
|
|
708
|
+
const encoder = new TextEncoder();
|
|
709
|
+
const memoBytes = encoder.encode(memo);
|
|
710
|
+
if (memoBytes.length > 120) {
|
|
711
|
+
throw new ValidationError(
|
|
712
|
+
"Memo exceeds the maximum allowed byte length of 120.",
|
|
713
|
+
{
|
|
714
|
+
field: "memo",
|
|
715
|
+
value: memo,
|
|
716
|
+
expected: "less than 120 bytes",
|
|
717
|
+
},
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
if (assetIdentifier) {
|
|
722
|
+
isValidPublicKey(assetIdentifier);
|
|
723
|
+
}
|
|
724
|
+
const paymentRequest = encodeSparkAddress({
|
|
725
|
+
identityPublicKey: bytesToHex(
|
|
726
|
+
await this.config.signer.getIdentityPublicKey(),
|
|
727
|
+
),
|
|
728
|
+
network: this.config.getNetworkType(),
|
|
729
|
+
paymentIntentFields: {
|
|
730
|
+
id: uuidv7obj().bytes,
|
|
731
|
+
assetIdentifier: assetIdentifier
|
|
732
|
+
? hexToBytes(assetIdentifier)
|
|
733
|
+
: undefined,
|
|
734
|
+
assetAmount: numberToVarBytesBE(assetAmount),
|
|
735
|
+
memo: memo,
|
|
736
|
+
},
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
return paymentRequest;
|
|
740
|
+
}
|
|
741
|
+
|
|
690
742
|
/**
|
|
691
743
|
* Initializes the wallet using either a mnemonic phrase or a raw seed.
|
|
692
744
|
* initWallet will also claim any pending incoming lightning payment, spark transfer,
|
|
@@ -1967,7 +2019,7 @@ export class SparkWallet extends EventEmitter {
|
|
|
1967
2019
|
|
|
1968
2020
|
const isSelfTransfer = equalBytes(
|
|
1969
2021
|
signerIdentityPublicKey,
|
|
1970
|
-
hexToBytes(receiverAddress),
|
|
2022
|
+
hexToBytes(receiverAddress.identityPublicKey),
|
|
1971
2023
|
);
|
|
1972
2024
|
|
|
1973
2025
|
return await this.withLeaves(async () => {
|
|
@@ -1988,7 +2040,7 @@ export class SparkWallet extends EventEmitter {
|
|
|
1988
2040
|
|
|
1989
2041
|
const transfer = await this.transferService.sendTransferWithKeyTweaks(
|
|
1990
2042
|
leafKeyTweaks,
|
|
1991
|
-
hexToBytes(receiverAddress),
|
|
2043
|
+
hexToBytes(receiverAddress.identityPublicKey),
|
|
1992
2044
|
);
|
|
1993
2045
|
|
|
1994
2046
|
const leavesToRemove = new Set(leavesToSend.map((leaf) => leaf.id));
|
|
@@ -2896,6 +2948,31 @@ export class SparkWallet extends EventEmitter {
|
|
|
2896
2948
|
return feeEstimate;
|
|
2897
2949
|
}
|
|
2898
2950
|
|
|
2951
|
+
/**
|
|
2952
|
+
* Gets a transfer that has been sent by the SSP to the wallet.
|
|
2953
|
+
*
|
|
2954
|
+
* @param {string} id - The ID of the transfer
|
|
2955
|
+
* @returns {Promise<GraphQLTransferObj | null>} The transfer
|
|
2956
|
+
*/
|
|
2957
|
+
public async getTransferFromSsp(
|
|
2958
|
+
id: string,
|
|
2959
|
+
): Promise<GraphQLTransferObj | null> {
|
|
2960
|
+
const sspClient = this.getSspClient();
|
|
2961
|
+
return await sspClient.getTransfer(id);
|
|
2962
|
+
}
|
|
2963
|
+
|
|
2964
|
+
/**
|
|
2965
|
+
* Gets a transfer, that the wallet is a participant of, in the Spark network.
|
|
2966
|
+
* Only contains data about the spark->spark transfer, use getTransferFromSsp if you're
|
|
2967
|
+
* looking for information related to a lightning transfer.
|
|
2968
|
+
*
|
|
2969
|
+
* @param {string} id - The ID of the transfer
|
|
2970
|
+
* @returns {Promise<Transfer | undefined>} The transfer
|
|
2971
|
+
*/
|
|
2972
|
+
public async getTransfer(id: string): Promise<Transfer | undefined> {
|
|
2973
|
+
return await this.transferService.queryTransfer(id);
|
|
2974
|
+
}
|
|
2975
|
+
|
|
2899
2976
|
// ***** Token Flow *****
|
|
2900
2977
|
|
|
2901
2978
|
/**
|
|
@@ -3116,6 +3193,148 @@ export class SparkWallet extends EventEmitter {
|
|
|
3116
3193
|
return this.config.signer.validateMessageWithIdentityKey(hash, signature);
|
|
3117
3194
|
}
|
|
3118
3195
|
|
|
3196
|
+
/**
|
|
3197
|
+
* Signs a transaction with wallet keys.
|
|
3198
|
+
*
|
|
3199
|
+
* @param {string} txHex - The transaction hex to sign
|
|
3200
|
+
* @param {string} keyType - The type of key to use for signing ("identity", "deposit", or "auto-detect")
|
|
3201
|
+
* @returns {Promise<string>} The signed transaction hex
|
|
3202
|
+
*/
|
|
3203
|
+
public async signTransaction(
|
|
3204
|
+
txHex: string,
|
|
3205
|
+
keyType: string = "auto-detect",
|
|
3206
|
+
): Promise<string> {
|
|
3207
|
+
try {
|
|
3208
|
+
// Parse the transaction
|
|
3209
|
+
const tx = Transaction.fromRaw(hexToBytes(txHex));
|
|
3210
|
+
|
|
3211
|
+
let publicKey: Uint8Array;
|
|
3212
|
+
|
|
3213
|
+
switch (keyType.toLowerCase()) {
|
|
3214
|
+
case "identity":
|
|
3215
|
+
publicKey = await this.config.signer.getIdentityPublicKey();
|
|
3216
|
+
break;
|
|
3217
|
+
case "deposit":
|
|
3218
|
+
publicKey = await this.config.signer.getDepositSigningKey();
|
|
3219
|
+
break;
|
|
3220
|
+
case "auto-detect":
|
|
3221
|
+
default:
|
|
3222
|
+
// Try to auto-detect which key to use by examining the transaction inputs
|
|
3223
|
+
const detectedKey = await this.detectKeyForTransaction(tx);
|
|
3224
|
+
if (detectedKey) {
|
|
3225
|
+
publicKey = detectedKey.publicKey;
|
|
3226
|
+
} else {
|
|
3227
|
+
// Fallback to identity key
|
|
3228
|
+
publicKey = await this.config.signer.getIdentityPublicKey();
|
|
3229
|
+
}
|
|
3230
|
+
break;
|
|
3231
|
+
}
|
|
3232
|
+
|
|
3233
|
+
// Check each input to determine which ones need signing
|
|
3234
|
+
let inputsSigned = 0;
|
|
3235
|
+
for (let i = 0; i < tx.inputsLength; i++) {
|
|
3236
|
+
const input = tx.getInput(i);
|
|
3237
|
+
if (!input?.witnessUtxo?.script) {
|
|
3238
|
+
continue;
|
|
3239
|
+
}
|
|
3240
|
+
|
|
3241
|
+
const script = input.witnessUtxo.script;
|
|
3242
|
+
|
|
3243
|
+
// Check if this is an ephemeral anchor (OP_TRUE script)
|
|
3244
|
+
// OP_TRUE is represented as a single byte: 0x51
|
|
3245
|
+
if (script.length === 1 && script[0] === 0x51) {
|
|
3246
|
+
continue;
|
|
3247
|
+
}
|
|
3248
|
+
|
|
3249
|
+
// Check if this script matches one of our keys
|
|
3250
|
+
const identityScript = getP2TRScriptFromPublicKey(
|
|
3251
|
+
publicKey,
|
|
3252
|
+
this.config.getNetwork(),
|
|
3253
|
+
);
|
|
3254
|
+
|
|
3255
|
+
if (bytesToHex(script) === bytesToHex(identityScript)) {
|
|
3256
|
+
// Sign this specific input
|
|
3257
|
+
try {
|
|
3258
|
+
this.config.signer.signTransactionIndex(tx, i, publicKey);
|
|
3259
|
+
inputsSigned++;
|
|
3260
|
+
} catch (error) {
|
|
3261
|
+
throw new ValidationError(`Failed to sign input ${i}: ${error}`, {
|
|
3262
|
+
field: "input",
|
|
3263
|
+
value: i,
|
|
3264
|
+
});
|
|
3265
|
+
}
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3268
|
+
|
|
3269
|
+
if (inputsSigned === 0) {
|
|
3270
|
+
throw new Error(
|
|
3271
|
+
"No inputs were signed. Check that the transaction contains inputs controlled by this wallet.",
|
|
3272
|
+
);
|
|
3273
|
+
}
|
|
3274
|
+
|
|
3275
|
+
tx.finalize();
|
|
3276
|
+
|
|
3277
|
+
const signedTxHex = tx.hex;
|
|
3278
|
+
|
|
3279
|
+
return signedTxHex;
|
|
3280
|
+
} catch (error) {
|
|
3281
|
+
console.error("❌ Error signing transaction:", error);
|
|
3282
|
+
throw error;
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
|
|
3286
|
+
/**
|
|
3287
|
+
* Helper method to auto-detect which key should be used for signing a transaction.
|
|
3288
|
+
*/
|
|
3289
|
+
private async detectKeyForTransaction(tx: Transaction): Promise<{
|
|
3290
|
+
publicKey: Uint8Array;
|
|
3291
|
+
keyType: string;
|
|
3292
|
+
} | null> {
|
|
3293
|
+
try {
|
|
3294
|
+
// Get available keys
|
|
3295
|
+
const identityPubKey = await this.config.signer.getIdentityPublicKey();
|
|
3296
|
+
const depositPubKey = await this.config.signer.getDepositSigningKey();
|
|
3297
|
+
|
|
3298
|
+
// Check if any inputs reference outputs that would be controlled by our keys
|
|
3299
|
+
for (let i = 0; i < tx.inputsLength; i++) {
|
|
3300
|
+
const input = tx.getInput(i);
|
|
3301
|
+
if (input?.witnessUtxo?.script) {
|
|
3302
|
+
const script = input.witnessUtxo.script;
|
|
3303
|
+
|
|
3304
|
+
// Check if this script corresponds to one of our keys
|
|
3305
|
+
// This is a simplified check - in practice, you might need more sophisticated script analysis
|
|
3306
|
+
const identityScript = getP2TRScriptFromPublicKey(
|
|
3307
|
+
identityPubKey,
|
|
3308
|
+
this.config.getNetwork(),
|
|
3309
|
+
);
|
|
3310
|
+
const depositScript = getP2TRScriptFromPublicKey(
|
|
3311
|
+
depositPubKey,
|
|
3312
|
+
this.config.getNetwork(),
|
|
3313
|
+
);
|
|
3314
|
+
|
|
3315
|
+
if (bytesToHex(script) === bytesToHex(identityScript)) {
|
|
3316
|
+
return {
|
|
3317
|
+
publicKey: identityPubKey,
|
|
3318
|
+
keyType: "identity",
|
|
3319
|
+
};
|
|
3320
|
+
}
|
|
3321
|
+
|
|
3322
|
+
if (bytesToHex(script) === bytesToHex(depositScript)) {
|
|
3323
|
+
return {
|
|
3324
|
+
publicKey: depositPubKey,
|
|
3325
|
+
keyType: "deposit",
|
|
3326
|
+
};
|
|
3327
|
+
}
|
|
3328
|
+
}
|
|
3329
|
+
}
|
|
3330
|
+
|
|
3331
|
+
return null;
|
|
3332
|
+
} catch (error) {
|
|
3333
|
+
console.warn("Error during key auto-detection:", error);
|
|
3334
|
+
return null;
|
|
3335
|
+
}
|
|
3336
|
+
}
|
|
3337
|
+
|
|
3119
3338
|
/**
|
|
3120
3339
|
* Get a Lightning receive request by ID.
|
|
3121
3340
|
*
|
|
@@ -3153,6 +3372,291 @@ export class SparkWallet extends EventEmitter {
|
|
|
3153
3372
|
return await sspClient.getCoopExitRequest(id);
|
|
3154
3373
|
}
|
|
3155
3374
|
|
|
3375
|
+
/**
|
|
3376
|
+
* Check the remaining timelock on a given node.
|
|
3377
|
+
*
|
|
3378
|
+
* @param {string} nodeId - The ID of the node to check
|
|
3379
|
+
* @returns {Promise<{nodeTimelock: number, refundTimelock: number}>} The remaining timelocks in blocks for both node and refund transactions
|
|
3380
|
+
*/
|
|
3381
|
+
public async checkTimelock(nodeId: string): Promise<{
|
|
3382
|
+
nodeTimelock: number;
|
|
3383
|
+
refundTimelock: number;
|
|
3384
|
+
}> {
|
|
3385
|
+
const sparkClient = await this.connectionManager.createSparkClient(
|
|
3386
|
+
this.config.getCoordinatorAddress(),
|
|
3387
|
+
);
|
|
3388
|
+
|
|
3389
|
+
try {
|
|
3390
|
+
const response = await sparkClient.query_nodes({
|
|
3391
|
+
source: {
|
|
3392
|
+
$case: "nodeIds",
|
|
3393
|
+
nodeIds: {
|
|
3394
|
+
nodeIds: [nodeId],
|
|
3395
|
+
},
|
|
3396
|
+
},
|
|
3397
|
+
includeParents: false,
|
|
3398
|
+
network: NetworkToProto[this.config.getNetwork()],
|
|
3399
|
+
});
|
|
3400
|
+
|
|
3401
|
+
const node = response.nodes[nodeId];
|
|
3402
|
+
if (!node) {
|
|
3403
|
+
throw new ValidationError("Node not found", {
|
|
3404
|
+
field: "nodeId",
|
|
3405
|
+
value: nodeId,
|
|
3406
|
+
});
|
|
3407
|
+
}
|
|
3408
|
+
|
|
3409
|
+
// Check if this is a root node (no parent)
|
|
3410
|
+
const isRootNode = !node.parentNodeId;
|
|
3411
|
+
|
|
3412
|
+
// Validate transaction data exists
|
|
3413
|
+
if (!node.nodeTx || node.nodeTx.length === 0) {
|
|
3414
|
+
throw new ValidationError(
|
|
3415
|
+
`Node transaction data is missing or empty for ${isRootNode ? "root" : "non-root"} node`,
|
|
3416
|
+
{
|
|
3417
|
+
field: "nodeTx",
|
|
3418
|
+
value: node.nodeTx?.length || 0,
|
|
3419
|
+
},
|
|
3420
|
+
);
|
|
3421
|
+
}
|
|
3422
|
+
|
|
3423
|
+
if (!node.refundTx || node.refundTx.length === 0) {
|
|
3424
|
+
throw new ValidationError(
|
|
3425
|
+
`Refund transaction data is missing or empty for ${isRootNode ? "root" : "non-root"} node`,
|
|
3426
|
+
{
|
|
3427
|
+
field: "refundTx",
|
|
3428
|
+
value: node.refundTx?.length || 0,
|
|
3429
|
+
},
|
|
3430
|
+
);
|
|
3431
|
+
}
|
|
3432
|
+
|
|
3433
|
+
let nodeTx, refundTx;
|
|
3434
|
+
|
|
3435
|
+
try {
|
|
3436
|
+
// Get the node transaction to check its timelock
|
|
3437
|
+
nodeTx = getTxFromRawTxBytes(node.nodeTx);
|
|
3438
|
+
} catch (error) {
|
|
3439
|
+
throw new ValidationError(
|
|
3440
|
+
`Failed to parse node transaction for ${isRootNode ? "root" : "non-root"} node: ${error instanceof Error ? error.message : String(error)}`,
|
|
3441
|
+
{
|
|
3442
|
+
field: "nodeTx",
|
|
3443
|
+
value: node.nodeTx.length,
|
|
3444
|
+
},
|
|
3445
|
+
);
|
|
3446
|
+
}
|
|
3447
|
+
|
|
3448
|
+
try {
|
|
3449
|
+
// Get the refund transaction to check its timelock
|
|
3450
|
+
refundTx = getTxFromRawTxBytes(node.refundTx);
|
|
3451
|
+
} catch (error) {
|
|
3452
|
+
throw new ValidationError(
|
|
3453
|
+
`Failed to parse refund transaction for ${isRootNode ? "root" : "non-root"} node: ${error instanceof Error ? error.message : String(error)}`,
|
|
3454
|
+
{
|
|
3455
|
+
field: "refundTx",
|
|
3456
|
+
value: node.refundTx.length,
|
|
3457
|
+
},
|
|
3458
|
+
);
|
|
3459
|
+
}
|
|
3460
|
+
|
|
3461
|
+
const nodeInput = nodeTx.getInput(0);
|
|
3462
|
+
if (!nodeInput) {
|
|
3463
|
+
throw new ValidationError(
|
|
3464
|
+
`Node transaction has no inputs for ${isRootNode ? "root" : "non-root"} node`,
|
|
3465
|
+
{
|
|
3466
|
+
field: "nodeInput",
|
|
3467
|
+
value: nodeTx.inputsLength,
|
|
3468
|
+
},
|
|
3469
|
+
);
|
|
3470
|
+
}
|
|
3471
|
+
|
|
3472
|
+
if (!nodeInput.sequence) {
|
|
3473
|
+
throw new ValidationError(
|
|
3474
|
+
`Node transaction has no sequence for ${isRootNode ? "root" : "non-root"} node`,
|
|
3475
|
+
{
|
|
3476
|
+
field: "sequence",
|
|
3477
|
+
value: nodeInput.sequence,
|
|
3478
|
+
},
|
|
3479
|
+
);
|
|
3480
|
+
}
|
|
3481
|
+
|
|
3482
|
+
const refundInput = refundTx.getInput(0);
|
|
3483
|
+
if (!refundInput) {
|
|
3484
|
+
throw new ValidationError(
|
|
3485
|
+
`Refund transaction has no inputs for ${isRootNode ? "root" : "non-root"} node`,
|
|
3486
|
+
{
|
|
3487
|
+
field: "refundInput",
|
|
3488
|
+
value: refundTx.inputsLength,
|
|
3489
|
+
},
|
|
3490
|
+
);
|
|
3491
|
+
}
|
|
3492
|
+
|
|
3493
|
+
if (!refundInput.sequence) {
|
|
3494
|
+
throw new ValidationError(
|
|
3495
|
+
`Refund transaction has no sequence for ${isRootNode ? "root" : "non-root"} node`,
|
|
3496
|
+
{
|
|
3497
|
+
field: "sequence",
|
|
3498
|
+
value: refundInput.sequence,
|
|
3499
|
+
},
|
|
3500
|
+
);
|
|
3501
|
+
}
|
|
3502
|
+
|
|
3503
|
+
// Extract timelock from sequence (lower 16 bits)
|
|
3504
|
+
const nodeTimelock = nodeInput.sequence & 0xffff;
|
|
3505
|
+
const refundTimelock = refundInput.sequence & 0xffff;
|
|
3506
|
+
|
|
3507
|
+
return {
|
|
3508
|
+
nodeTimelock,
|
|
3509
|
+
refundTimelock,
|
|
3510
|
+
};
|
|
3511
|
+
} catch (error) {
|
|
3512
|
+
throw new NetworkError(
|
|
3513
|
+
`Failed to check timelock for node ${nodeId}`,
|
|
3514
|
+
{
|
|
3515
|
+
method: "query_nodes",
|
|
3516
|
+
},
|
|
3517
|
+
error as Error,
|
|
3518
|
+
);
|
|
3519
|
+
}
|
|
3520
|
+
}
|
|
3521
|
+
|
|
3522
|
+
/**
|
|
3523
|
+
* Refresh the timelock of a specific node.
|
|
3524
|
+
*
|
|
3525
|
+
* @param {string} nodeId - The ID of the node to refresh
|
|
3526
|
+
* @returns {Promise<void>} Promise that resolves when the timelock is refreshed
|
|
3527
|
+
*/
|
|
3528
|
+
public async testOnly_expireTimelock(nodeId: string): Promise<void> {
|
|
3529
|
+
const sparkClient = await this.connectionManager.createSparkClient(
|
|
3530
|
+
this.config.getCoordinatorAddress(),
|
|
3531
|
+
);
|
|
3532
|
+
|
|
3533
|
+
try {
|
|
3534
|
+
// First, get the node and its parent
|
|
3535
|
+
const response = await sparkClient.query_nodes({
|
|
3536
|
+
source: {
|
|
3537
|
+
$case: "nodeIds",
|
|
3538
|
+
nodeIds: {
|
|
3539
|
+
nodeIds: [nodeId],
|
|
3540
|
+
},
|
|
3541
|
+
},
|
|
3542
|
+
includeParents: true,
|
|
3543
|
+
});
|
|
3544
|
+
|
|
3545
|
+
const node = response.nodes[nodeId];
|
|
3546
|
+
if (!node) {
|
|
3547
|
+
throw new ValidationError("Node not found", {
|
|
3548
|
+
field: "nodeId",
|
|
3549
|
+
value: nodeId,
|
|
3550
|
+
});
|
|
3551
|
+
}
|
|
3552
|
+
|
|
3553
|
+
if (!node.parentNodeId) {
|
|
3554
|
+
throw new ValidationError("Node has no parent", {
|
|
3555
|
+
field: "parentNodeId",
|
|
3556
|
+
value: node.parentNodeId,
|
|
3557
|
+
});
|
|
3558
|
+
}
|
|
3559
|
+
|
|
3560
|
+
const parentNode = response.nodes[node.parentNodeId];
|
|
3561
|
+
if (!parentNode) {
|
|
3562
|
+
throw new ValidationError("Parent node not found", {
|
|
3563
|
+
field: "parentNodeId",
|
|
3564
|
+
value: node.parentNodeId,
|
|
3565
|
+
});
|
|
3566
|
+
}
|
|
3567
|
+
|
|
3568
|
+
// Generate signing public key for this node
|
|
3569
|
+
const signingPubKey = await this.config.signer.generatePublicKey(
|
|
3570
|
+
sha256(node.id),
|
|
3571
|
+
);
|
|
3572
|
+
|
|
3573
|
+
// Call the transfer service to refresh the timelock
|
|
3574
|
+
const result = await this.transferService.refreshTimelockNodes(
|
|
3575
|
+
[node],
|
|
3576
|
+
parentNode,
|
|
3577
|
+
signingPubKey,
|
|
3578
|
+
);
|
|
3579
|
+
|
|
3580
|
+
// Update the local leaves if this node is in our wallet
|
|
3581
|
+
const leafIndex = this.leaves.findIndex((leaf) => leaf.id === node.id);
|
|
3582
|
+
if (leafIndex !== -1 && result.nodes.length > 0) {
|
|
3583
|
+
const newNode = result.nodes[0];
|
|
3584
|
+
if (newNode) {
|
|
3585
|
+
this.leaves[leafIndex] = newNode;
|
|
3586
|
+
}
|
|
3587
|
+
}
|
|
3588
|
+
} catch (error) {
|
|
3589
|
+
throw new NetworkError(
|
|
3590
|
+
"Failed to refresh timelock",
|
|
3591
|
+
{
|
|
3592
|
+
method: "refresh_timelock",
|
|
3593
|
+
},
|
|
3594
|
+
error as Error,
|
|
3595
|
+
);
|
|
3596
|
+
}
|
|
3597
|
+
}
|
|
3598
|
+
|
|
3599
|
+
/**
|
|
3600
|
+
* Refresh the timelock of a specific node's refund transaction only.
|
|
3601
|
+
*
|
|
3602
|
+
* @param {string} nodeId - The ID of the node whose refund transaction to refresh
|
|
3603
|
+
* @returns {Promise<void>} Promise that resolves when the refund timelock is refreshed
|
|
3604
|
+
*/
|
|
3605
|
+
public async testOnly_expireTimelockRefundTx(nodeId: string): Promise<void> {
|
|
3606
|
+
const sparkClient = await this.connectionManager.createSparkClient(
|
|
3607
|
+
this.config.getCoordinatorAddress(),
|
|
3608
|
+
);
|
|
3609
|
+
|
|
3610
|
+
try {
|
|
3611
|
+
// Get the node
|
|
3612
|
+
const response = await sparkClient.query_nodes({
|
|
3613
|
+
source: {
|
|
3614
|
+
$case: "nodeIds",
|
|
3615
|
+
nodeIds: {
|
|
3616
|
+
nodeIds: [nodeId],
|
|
3617
|
+
},
|
|
3618
|
+
},
|
|
3619
|
+
includeParents: false,
|
|
3620
|
+
});
|
|
3621
|
+
|
|
3622
|
+
const node = response.nodes[nodeId];
|
|
3623
|
+
if (!node) {
|
|
3624
|
+
throw new ValidationError("Node not found", {
|
|
3625
|
+
field: "nodeId",
|
|
3626
|
+
value: nodeId,
|
|
3627
|
+
});
|
|
3628
|
+
}
|
|
3629
|
+
|
|
3630
|
+
// Generate signing public key for this node
|
|
3631
|
+
const signingPubKey = await this.config.signer.generatePublicKey(
|
|
3632
|
+
sha256(node.id),
|
|
3633
|
+
);
|
|
3634
|
+
|
|
3635
|
+
// Call the transfer service to refresh the refund timelock
|
|
3636
|
+
const result = await this.transferService.refreshTimelockRefundTx(
|
|
3637
|
+
node,
|
|
3638
|
+
signingPubKey,
|
|
3639
|
+
);
|
|
3640
|
+
|
|
3641
|
+
// Update the local leaves if this node is in our wallet
|
|
3642
|
+
const leafIndex = this.leaves.findIndex((leaf) => leaf.id === node.id);
|
|
3643
|
+
if (leafIndex !== -1 && result.nodes.length > 0) {
|
|
3644
|
+
const newNode = result.nodes[0];
|
|
3645
|
+
if (newNode) {
|
|
3646
|
+
this.leaves[leafIndex] = newNode;
|
|
3647
|
+
}
|
|
3648
|
+
}
|
|
3649
|
+
} catch (error) {
|
|
3650
|
+
throw new NetworkError(
|
|
3651
|
+
"Failed to refresh refund timelock",
|
|
3652
|
+
{
|
|
3653
|
+
method: "refresh_timelock_refund_tx",
|
|
3654
|
+
},
|
|
3655
|
+
error as Error,
|
|
3656
|
+
);
|
|
3657
|
+
}
|
|
3658
|
+
}
|
|
3659
|
+
|
|
3156
3660
|
private cleanup() {
|
|
3157
3661
|
if (this.claimTransfersInterval) {
|
|
3158
3662
|
clearInterval(this.claimTransfersInterval);
|