@buildonspark/spark-sdk 0.1.38 → 0.1.40
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 +12 -0
- package/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +1 -1
- package/android/build/intermediates/library_jni/debug/copyDebugJniLibsProjectOnly/jni/arm64-v8a/libspark_frost.so +0 -0
- package/android/build/intermediates/library_jni/debug/copyDebugJniLibsProjectOnly/jni/arm64-v8a/libuniffi_spark_frost.so +0 -0
- package/android/build/intermediates/library_jni/debug/copyDebugJniLibsProjectOnly/jni/armeabi-v7a/libspark_frost.so +0 -0
- package/android/build/intermediates/library_jni/debug/copyDebugJniLibsProjectOnly/jni/armeabi-v7a/libuniffi_spark_frost.so +0 -0
- package/android/build/intermediates/library_jni/debug/copyDebugJniLibsProjectOnly/jni/x86/libspark_frost.so +0 -0
- package/android/build/intermediates/library_jni/debug/copyDebugJniLibsProjectOnly/jni/x86/libuniffi_spark_frost.so +0 -0
- package/android/build/intermediates/library_jni/debug/copyDebugJniLibsProjectOnly/jni/x86_64/libspark_frost.so +0 -0
- package/android/build/intermediates/library_jni/debug/copyDebugJniLibsProjectOnly/jni/x86_64/libuniffi_spark_frost.so +0 -0
- package/android/build/intermediates/merged_jni_libs/debug/mergeDebugJniLibFolders/out/arm64-v8a/libspark_frost.so +0 -0
- package/android/build/intermediates/merged_jni_libs/debug/mergeDebugJniLibFolders/out/arm64-v8a/libuniffi_spark_frost.so +0 -0
- package/android/build/intermediates/merged_jni_libs/debug/mergeDebugJniLibFolders/out/armeabi-v7a/libspark_frost.so +0 -0
- package/android/build/intermediates/merged_jni_libs/debug/mergeDebugJniLibFolders/out/armeabi-v7a/libuniffi_spark_frost.so +0 -0
- package/android/build/intermediates/merged_jni_libs/debug/mergeDebugJniLibFolders/out/x86/libspark_frost.so +0 -0
- package/android/build/intermediates/merged_jni_libs/debug/mergeDebugJniLibFolders/out/x86/libuniffi_spark_frost.so +0 -0
- package/android/build/intermediates/merged_jni_libs/debug/mergeDebugJniLibFolders/out/x86_64/libspark_frost.so +0 -0
- package/android/build/intermediates/merged_jni_libs/debug/mergeDebugJniLibFolders/out/x86_64/libuniffi_spark_frost.so +0 -0
- package/android/build/intermediates/merged_native_libs/debug/mergeDebugNativeLibs/out/lib/arm64-v8a/libspark_frost.so +0 -0
- package/android/build/intermediates/merged_native_libs/debug/mergeDebugNativeLibs/out/lib/arm64-v8a/libuniffi_spark_frost.so +0 -0
- package/android/build/intermediates/merged_native_libs/debug/mergeDebugNativeLibs/out/lib/armeabi-v7a/libspark_frost.so +0 -0
- package/android/build/intermediates/merged_native_libs/debug/mergeDebugNativeLibs/out/lib/armeabi-v7a/libuniffi_spark_frost.so +0 -0
- package/android/build/intermediates/merged_native_libs/debug/mergeDebugNativeLibs/out/lib/x86/libspark_frost.so +0 -0
- package/android/build/intermediates/merged_native_libs/debug/mergeDebugNativeLibs/out/lib/x86/libuniffi_spark_frost.so +0 -0
- package/android/build/intermediates/merged_native_libs/debug/mergeDebugNativeLibs/out/lib/x86_64/libspark_frost.so +0 -0
- package/android/build/intermediates/merged_native_libs/debug/mergeDebugNativeLibs/out/lib/x86_64/libuniffi_spark_frost.so +0 -0
- package/dist/{RequestLightningSendInput-B4JdzclX.d.ts → RequestLightningSendInput-CJtcHOnu.d.ts} +1 -1
- package/dist/{RequestLightningSendInput-39_zGri6.d.cts → RequestLightningSendInput-DfmfqzZo.d.cts} +1 -1
- package/dist/address/index.d.cts +1 -1
- package/dist/address/index.d.ts +1 -1
- package/dist/address/index.js +2 -2
- package/dist/{chunk-W3EC5XSA.js → chunk-5MNQB2T4.js} +2 -2
- package/dist/chunk-ED3ZAFDI.js +784 -0
- package/dist/{chunk-VJTDG4BQ.js → chunk-HK6LPV6Z.js} +10 -1
- package/dist/{chunk-7WRK6WNJ.js → chunk-LHT4QTFK.js} +556 -41
- package/dist/{chunk-RAPBVYJY.js → chunk-RFCXPGDM.js} +26 -4
- package/dist/{chunk-DI7QXUQJ.js → chunk-W2VXS35Y.js} +4 -4
- package/dist/graphql/objects/index.d.cts +5 -4
- package/dist/graphql/objects/index.d.ts +5 -4
- package/dist/{index-CxAi2L8y.d.ts → index-BDEYgYxP.d.ts} +42 -4
- package/dist/{index-Dm17Ggfe.d.cts → index-CLdtdMU4.d.cts} +42 -4
- package/dist/index.cjs +1069 -40
- package/dist/index.d.cts +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +33 -17
- package/dist/index.node.cjs +1069 -40
- package/dist/index.node.d.cts +6 -6
- package/dist/index.node.d.ts +6 -6
- package/dist/index.node.js +33 -17
- package/dist/native/index.cjs +1069 -40
- package/dist/native/index.d.cts +108 -5
- package/dist/native/index.d.ts +108 -5
- package/dist/native/index.js +1065 -40
- package/dist/{network-GFGEHkS4.d.cts → network-B10hBoHp.d.cts} +8 -1
- package/dist/{network-DobHpaV6.d.ts → network-CCgyIsGl.d.ts} +8 -1
- package/dist/services/config.cjs +29 -12
- package/dist/services/config.d.cts +4 -4
- package/dist/services/config.d.ts +4 -4
- package/dist/services/config.js +5 -5
- package/dist/services/connection.d.cts +4 -4
- package/dist/services/connection.d.ts +4 -4
- package/dist/services/connection.js +2 -2
- package/dist/services/index.cjs +30 -13
- package/dist/services/index.d.cts +4 -4
- package/dist/services/index.d.ts +4 -4
- package/dist/services/index.js +8 -8
- package/dist/services/lrc-connection.d.cts +4 -4
- package/dist/services/lrc-connection.d.ts +4 -4
- package/dist/services/lrc-connection.js +1 -1
- package/dist/services/token-transactions.cjs +1 -1
- package/dist/services/token-transactions.d.cts +4 -4
- package/dist/services/token-transactions.d.ts +4 -4
- package/dist/services/token-transactions.js +3 -3
- package/dist/services/wallet-config.d.cts +4 -4
- package/dist/services/wallet-config.d.ts +4 -4
- package/dist/signer/signer.cjs +23 -6
- package/dist/signer/signer.d.cts +3 -2
- package/dist/signer/signer.d.ts +3 -2
- package/dist/signer/signer.js +1 -1
- package/dist/{signer-DFGw9RRp.d.ts → signer-C5h1DpjF.d.ts} +4 -1
- package/dist/{signer-C1t40Wus.d.cts → signer-CYwn7h9U.d.cts} +4 -1
- package/dist/types/index.d.cts +4 -3
- package/dist/types/index.d.ts +4 -3
- package/dist/utils/index.cjs +891 -2
- package/dist/utils/index.d.cts +62 -6
- package/dist/utils/index.d.ts +62 -6
- package/dist/utils/index.js +23 -7
- package/package.json +1 -1
- package/src/services/deposit.ts +23 -5
- package/src/services/token-transactions.ts +1 -1
- package/src/services/transfer.ts +218 -11
- package/src/services/tree-creation.ts +29 -14
- package/src/signer/signer.ts +47 -5
- package/src/spark-wallet/spark-wallet.ts +430 -4
- package/src/tests/integration/swap.test.ts +225 -0
- package/src/tests/integration/tree-creation.test.ts +5 -1
- package/src/utils/index.ts +1 -0
- package/src/utils/mempool.ts +26 -1
- package/src/utils/network.ts +15 -0
- package/src/utils/transaction.ts +22 -2
- package/src/utils/unilateral-exit.ts +729 -0
- package/dist/chunk-E5SL7XTO.js +0 -301
- package/dist/{chunk-LIP2K6KR.js → chunk-2CDJZQN4.js} +3 -3
- package/dist/{chunk-RGWBSZIO.js → chunk-I4JI6TYN.js} +4 -4
package/dist/native/index.js
CHANGED
|
@@ -211,6 +211,7 @@ var NativeSparkFrost = class {
|
|
|
211
211
|
import {
|
|
212
212
|
bytesToHex as bytesToHex2,
|
|
213
213
|
bytesToNumberBE as bytesToNumberBE2,
|
|
214
|
+
equalBytes as equalBytes2,
|
|
214
215
|
hexToBytes
|
|
215
216
|
} from "@noble/curves/abstract/utils";
|
|
216
217
|
import { schnorr as schnorr2, secp256k1 as secp256k15 } from "@noble/curves/secp256k1";
|
|
@@ -738,11 +739,13 @@ function decodeBytesToSigningCommitment(bytes) {
|
|
|
738
739
|
}
|
|
739
740
|
|
|
740
741
|
// src/signer/signer.ts
|
|
741
|
-
import {
|
|
742
|
-
import {
|
|
743
|
-
|
|
742
|
+
import { privateAdd, privateNegate } from "@bitcoinerlab/secp256k1";
|
|
743
|
+
import {
|
|
744
|
+
fromPrivateKey,
|
|
745
|
+
PARITY,
|
|
746
|
+
Receipt
|
|
747
|
+
} from "@buildonspark/lrc20-sdk";
|
|
744
748
|
import { sha256 } from "@noble/hashes/sha2";
|
|
745
|
-
import { fromPrivateKey } from "@buildonspark/lrc20-sdk";
|
|
746
749
|
var sparkFrostModule = void 0;
|
|
747
750
|
var getSparkFrostModule = async () => {
|
|
748
751
|
if (isReactNative) {
|
|
@@ -1228,6 +1231,25 @@ var DefaultSparkSigner = class {
|
|
|
1228
1231
|
const receiptProof = privateAdd(privateKey, pxhPubkey);
|
|
1229
1232
|
return Buffer.from(receiptProof);
|
|
1230
1233
|
}
|
|
1234
|
+
signTransactionIndex(tx, index, publicKey) {
|
|
1235
|
+
let privateKey;
|
|
1236
|
+
if (equalBytes2(publicKey, this.identityKey?.publicKey ?? new Uint8Array())) {
|
|
1237
|
+
privateKey = this.identityKey?.privateKey;
|
|
1238
|
+
} else if (equalBytes2(publicKey, this.depositKey?.publicKey ?? new Uint8Array())) {
|
|
1239
|
+
privateKey = this.depositKey?.privateKey;
|
|
1240
|
+
} else {
|
|
1241
|
+
privateKey = hexToBytes(
|
|
1242
|
+
this.publicKeyToPrivateKeyMap.get(bytesToHex2(publicKey)) ?? ""
|
|
1243
|
+
);
|
|
1244
|
+
}
|
|
1245
|
+
if (!privateKey) {
|
|
1246
|
+
throw new ValidationError("Private key not found for public key", {
|
|
1247
|
+
field: "privateKey",
|
|
1248
|
+
value: bytesToHex2(publicKey)
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1251
|
+
tx.signIdx(privateKey, index);
|
|
1252
|
+
}
|
|
1231
1253
|
};
|
|
1232
1254
|
|
|
1233
1255
|
// src/signer/signer.react-native.ts
|
|
@@ -1298,7 +1320,7 @@ import { isNode as isNode4, mapCurrencyAmount } from "@lightsparkdev/core";
|
|
|
1298
1320
|
import {
|
|
1299
1321
|
bytesToHex as bytesToHex12,
|
|
1300
1322
|
bytesToNumberBE as bytesToNumberBE7,
|
|
1301
|
-
equalBytes as
|
|
1323
|
+
equalBytes as equalBytes5,
|
|
1302
1324
|
hexToBytes as hexToBytes13
|
|
1303
1325
|
} from "@noble/curves/abstract/utils";
|
|
1304
1326
|
import { secp256k1 as secp256k115 } from "@noble/curves/secp256k1";
|
|
@@ -16313,6 +16335,14 @@ function getNetworkFromAddress(address2) {
|
|
|
16313
16335
|
}
|
|
16314
16336
|
return null;
|
|
16315
16337
|
}
|
|
16338
|
+
function getNetworkFromString(network) {
|
|
16339
|
+
const net = (network ?? "REGTEST").toUpperCase();
|
|
16340
|
+
if (net === "MAINNET") return 0 /* MAINNET */;
|
|
16341
|
+
if (net === "TESTNET") return 1 /* TESTNET */;
|
|
16342
|
+
if (net === "SIGNET") return 2 /* SIGNET */;
|
|
16343
|
+
if (net === "LOCAL") return 4 /* LOCAL */;
|
|
16344
|
+
return 3 /* REGTEST */;
|
|
16345
|
+
}
|
|
16316
16346
|
|
|
16317
16347
|
// src/tests/isHermeticTest.ts
|
|
16318
16348
|
import { isNode } from "@lightsparkdev/core";
|
|
@@ -17864,8 +17894,20 @@ function getTxIdNoReverse(tx) {
|
|
|
17864
17894
|
// src/utils/transaction.ts
|
|
17865
17895
|
import { Transaction as Transaction2 } from "@scure/btc-signer";
|
|
17866
17896
|
var TIME_LOCK_INTERVAL = 100;
|
|
17897
|
+
var ESTIMATED_TX_SIZE = 191;
|
|
17898
|
+
var DEFAULT_SATS_PER_VBYTE = 5;
|
|
17899
|
+
var DEFAULT_FEE_SATS = ESTIMATED_TX_SIZE * DEFAULT_SATS_PER_VBYTE;
|
|
17900
|
+
function maybeApplyFee(amount) {
|
|
17901
|
+
if (amount > BigInt(DEFAULT_FEE_SATS)) {
|
|
17902
|
+
return amount - BigInt(DEFAULT_FEE_SATS);
|
|
17903
|
+
}
|
|
17904
|
+
return amount;
|
|
17905
|
+
}
|
|
17867
17906
|
function createRefundTx(sequence, nodeOutPoint, amountSats, receivingPubkey, network) {
|
|
17868
|
-
const newRefundTx = new Transaction2({
|
|
17907
|
+
const newRefundTx = new Transaction2({
|
|
17908
|
+
version: 3,
|
|
17909
|
+
allowUnknownOutputs: true
|
|
17910
|
+
});
|
|
17869
17911
|
newRefundTx.addInput({
|
|
17870
17912
|
...nodeOutPoint,
|
|
17871
17913
|
sequence
|
|
@@ -17894,7 +17936,7 @@ function getNextTransactionSequence(currSequence, forRefresh) {
|
|
|
17894
17936
|
needRefresh: true
|
|
17895
17937
|
};
|
|
17896
17938
|
}
|
|
17897
|
-
if (nextTimelock
|
|
17939
|
+
if (nextTimelock < 0) {
|
|
17898
17940
|
throw new ValidationError("timelock interval is less than or equal to 0", {
|
|
17899
17941
|
field: "nextTimelock",
|
|
17900
17942
|
value: nextTimelock
|
|
@@ -17916,7 +17958,7 @@ function getEphemeralAnchorOutput() {
|
|
|
17916
17958
|
// src/services/transfer.ts
|
|
17917
17959
|
import {
|
|
17918
17960
|
bytesToHex as bytesToHex6,
|
|
17919
|
-
equalBytes as
|
|
17961
|
+
equalBytes as equalBytes3,
|
|
17920
17962
|
hexToBytes as hexToBytes5,
|
|
17921
17963
|
numberToBytesBE as numberToBytesBE3
|
|
17922
17964
|
} from "@noble/curves/abstract/utils";
|
|
@@ -18008,6 +18050,32 @@ var BaseTransferService = class {
|
|
|
18008
18050
|
}
|
|
18009
18051
|
return updatedTransfer;
|
|
18010
18052
|
}
|
|
18053
|
+
async deliverTransferPackage(transfer, leaves, refundSignatureMap) {
|
|
18054
|
+
const keyTweakInputMap = await this.prepareSendTransferKeyTweaks(
|
|
18055
|
+
transfer.id,
|
|
18056
|
+
transfer.receiverIdentityPublicKey,
|
|
18057
|
+
leaves,
|
|
18058
|
+
refundSignatureMap
|
|
18059
|
+
);
|
|
18060
|
+
const transferPackage = await this.prepareTransferPackage(
|
|
18061
|
+
transfer.id,
|
|
18062
|
+
keyTweakInputMap,
|
|
18063
|
+
leaves,
|
|
18064
|
+
transfer.receiverIdentityPublicKey
|
|
18065
|
+
);
|
|
18066
|
+
const sparkClient = await this.connectionManager.createSparkClient(
|
|
18067
|
+
this.config.getCoordinatorAddress()
|
|
18068
|
+
);
|
|
18069
|
+
const response = await sparkClient.finalize_transfer_with_transfer_package({
|
|
18070
|
+
transferId: transfer.id,
|
|
18071
|
+
ownerIdentityPublicKey: await this.config.signer.getIdentityPublicKey(),
|
|
18072
|
+
transferPackage
|
|
18073
|
+
});
|
|
18074
|
+
if (!response.transfer) {
|
|
18075
|
+
throw new ValidationError("No transfer response from operator");
|
|
18076
|
+
}
|
|
18077
|
+
return response.transfer;
|
|
18078
|
+
}
|
|
18011
18079
|
async sendTransferWithKeyTweaks(leaves, receiverIdentityPubkey) {
|
|
18012
18080
|
const transferID = uuidv7();
|
|
18013
18081
|
const keyTweakInputMap = await this.prepareSendTransferKeyTweaks(
|
|
@@ -18279,7 +18347,7 @@ var BaseTransferService = class {
|
|
|
18279
18347
|
return void 0;
|
|
18280
18348
|
}
|
|
18281
18349
|
compareTransfers(transfer1, transfer2) {
|
|
18282
|
-
return transfer1.id === transfer2.id &&
|
|
18350
|
+
return transfer1.id === transfer2.id && equalBytes3(
|
|
18283
18351
|
transfer1.senderIdentityPublicKey,
|
|
18284
18352
|
transfer2.senderIdentityPublicKey
|
|
18285
18353
|
) && transfer1.status === transfer2.status && transfer1.totalValue === transfer2.totalValue && transfer1.expiryTime?.getTime() === transfer2.expiryTime?.getTime() && transfer1.leaves.length === transfer2.leaves.length;
|
|
@@ -18765,9 +18833,20 @@ var TransferService = class extends BaseTransferService {
|
|
|
18765
18833
|
if (!input) {
|
|
18766
18834
|
throw Error("Could not fetch tx input");
|
|
18767
18835
|
}
|
|
18768
|
-
const newTx = new Transaction3({ allowUnknownOutputs: true });
|
|
18769
|
-
|
|
18770
|
-
|
|
18836
|
+
const newTx = new Transaction3({ version: 3, allowUnknownOutputs: true });
|
|
18837
|
+
const originalOutput = nodeTx.getOutput(0);
|
|
18838
|
+
if (!originalOutput) {
|
|
18839
|
+
throw Error("Could not get original output");
|
|
18840
|
+
}
|
|
18841
|
+
newTx.addOutput({
|
|
18842
|
+
script: originalOutput.script,
|
|
18843
|
+
amount: originalOutput.amount
|
|
18844
|
+
});
|
|
18845
|
+
for (let j = 1; j < nodeTx.outputsLength; j++) {
|
|
18846
|
+
const additionalOutput = nodeTx.getOutput(j);
|
|
18847
|
+
if (additionalOutput) {
|
|
18848
|
+
newTx.addOutput(additionalOutput);
|
|
18849
|
+
}
|
|
18771
18850
|
}
|
|
18772
18851
|
if (i === 0) {
|
|
18773
18852
|
const currSequence = input.sequence;
|
|
@@ -18794,9 +18873,23 @@ var TransferService = class extends BaseTransferService {
|
|
|
18794
18873
|
throw Error("leaf does not have refund tx");
|
|
18795
18874
|
}
|
|
18796
18875
|
const refundTx = getTxFromRawTxBytes(leaf?.refundTx);
|
|
18797
|
-
const newRefundTx = new Transaction3({
|
|
18798
|
-
|
|
18799
|
-
|
|
18876
|
+
const newRefundTx = new Transaction3({
|
|
18877
|
+
version: 3,
|
|
18878
|
+
allowUnknownOutputs: true
|
|
18879
|
+
});
|
|
18880
|
+
const originalRefundOutput = refundTx.getOutput(0);
|
|
18881
|
+
if (!originalRefundOutput) {
|
|
18882
|
+
throw Error("Could not get original refund output");
|
|
18883
|
+
}
|
|
18884
|
+
newRefundTx.addOutput({
|
|
18885
|
+
script: originalRefundOutput.script,
|
|
18886
|
+
amount: originalRefundOutput.amount
|
|
18887
|
+
});
|
|
18888
|
+
for (let j = 1; j < refundTx.outputsLength; j++) {
|
|
18889
|
+
const additionalOutput = refundTx.getOutput(j);
|
|
18890
|
+
if (additionalOutput) {
|
|
18891
|
+
newRefundTx.addOutput(additionalOutput);
|
|
18892
|
+
}
|
|
18800
18893
|
}
|
|
18801
18894
|
const refundTxInput = refundTx.getInput(0);
|
|
18802
18895
|
if (!refundTxInput) {
|
|
@@ -18905,10 +18998,11 @@ var TransferService = class extends BaseTransferService {
|
|
|
18905
18998
|
nodeTxSignature: leafSignature,
|
|
18906
18999
|
refundTxSignature: refundSignature
|
|
18907
19000
|
});
|
|
18908
|
-
|
|
19001
|
+
const result = await sparkClient.finalize_node_signatures({
|
|
18909
19002
|
intent: 3 /* REFRESH */,
|
|
18910
19003
|
nodeSignatures
|
|
18911
19004
|
});
|
|
19005
|
+
return result;
|
|
18912
19006
|
}
|
|
18913
19007
|
async extendTimelock(node, signingPubKey) {
|
|
18914
19008
|
const nodeTx = getTxFromRawTxBytes(node.nodeTx);
|
|
@@ -18919,9 +19013,20 @@ var TransferService = class extends BaseTransferService {
|
|
|
18919
19013
|
index: 0
|
|
18920
19014
|
};
|
|
18921
19015
|
const { nextSequence: newNodeSequence } = getNextTransactionSequence(refundSequence);
|
|
18922
|
-
const newNodeTx = new Transaction3({
|
|
19016
|
+
const newNodeTx = new Transaction3({
|
|
19017
|
+
version: 3,
|
|
19018
|
+
allowUnknownOutputs: true
|
|
19019
|
+
});
|
|
18923
19020
|
newNodeTx.addInput({ ...newNodeOutPoint, sequence: newNodeSequence });
|
|
18924
|
-
|
|
19021
|
+
const originalOutput = nodeTx.getOutput(0);
|
|
19022
|
+
if (!originalOutput) {
|
|
19023
|
+
throw Error("Could not get original node output");
|
|
19024
|
+
}
|
|
19025
|
+
newNodeTx.addOutput({
|
|
19026
|
+
script: originalOutput.script,
|
|
19027
|
+
amount: originalOutput.amount
|
|
19028
|
+
// feeReducedAmount,
|
|
19029
|
+
});
|
|
18925
19030
|
newNodeTx.addOutput(getEphemeralAnchorOutput());
|
|
18926
19031
|
const newRefundOutPoint = {
|
|
18927
19032
|
txid: hexToBytes5(getTxId(newNodeTx)),
|
|
@@ -18935,11 +19040,16 @@ var TransferService = class extends BaseTransferService {
|
|
|
18935
19040
|
initialSequence(),
|
|
18936
19041
|
newRefundOutPoint,
|
|
18937
19042
|
amountSats,
|
|
19043
|
+
// feeReducedRefundAmount,
|
|
18938
19044
|
signingPubKey,
|
|
18939
19045
|
this.config.getNetwork()
|
|
18940
19046
|
);
|
|
18941
19047
|
const nodeSighash = getSigHashFromTx(newNodeTx, 0, nodeTx.getOutput(0));
|
|
18942
|
-
const refundSighash = getSigHashFromTx(
|
|
19048
|
+
const refundSighash = getSigHashFromTx(
|
|
19049
|
+
newRefundTx,
|
|
19050
|
+
0,
|
|
19051
|
+
newNodeTx.getOutput(0)
|
|
19052
|
+
);
|
|
18943
19053
|
const newNodeSigningJob = {
|
|
18944
19054
|
signingPublicKey: signingPubKey,
|
|
18945
19055
|
rawTx: newNodeTx.toBytes(),
|
|
@@ -19013,6 +19123,94 @@ var TransferService = class extends BaseTransferService {
|
|
|
19013
19123
|
]
|
|
19014
19124
|
});
|
|
19015
19125
|
}
|
|
19126
|
+
async refreshTimelockRefundTx(node, signingPubKey) {
|
|
19127
|
+
const nodeTx = getTxFromRawTxBytes(node.nodeTx);
|
|
19128
|
+
const refundTx = getTxFromRawTxBytes(node.refundTx);
|
|
19129
|
+
const currSequence = refundTx.getInput(0).sequence || 0;
|
|
19130
|
+
const { nextSequence } = getNextTransactionSequence(currSequence);
|
|
19131
|
+
const newRefundTx = new Transaction3({
|
|
19132
|
+
version: 3,
|
|
19133
|
+
allowUnknownOutputs: true
|
|
19134
|
+
});
|
|
19135
|
+
const originalRefundOutput = refundTx.getOutput(0);
|
|
19136
|
+
if (!originalRefundOutput) {
|
|
19137
|
+
throw Error("Could not get original refund output");
|
|
19138
|
+
}
|
|
19139
|
+
newRefundTx.addOutput({
|
|
19140
|
+
script: originalRefundOutput.script,
|
|
19141
|
+
amount: originalRefundOutput.amount
|
|
19142
|
+
});
|
|
19143
|
+
for (let j = 1; j < refundTx.outputsLength; j++) {
|
|
19144
|
+
const additionalOutput = refundTx.getOutput(j);
|
|
19145
|
+
if (additionalOutput) {
|
|
19146
|
+
newRefundTx.addOutput(additionalOutput);
|
|
19147
|
+
}
|
|
19148
|
+
}
|
|
19149
|
+
const refundTxInput = refundTx.getInput(0);
|
|
19150
|
+
if (!refundTxInput) {
|
|
19151
|
+
throw Error("refund tx doesn't have input");
|
|
19152
|
+
}
|
|
19153
|
+
newRefundTx.addInput({
|
|
19154
|
+
...refundTxInput,
|
|
19155
|
+
sequence: nextSequence
|
|
19156
|
+
});
|
|
19157
|
+
const refundSigningJob = {
|
|
19158
|
+
signingPublicKey: signingPubKey,
|
|
19159
|
+
rawTx: newRefundTx.toBytes(),
|
|
19160
|
+
signingNonceCommitment: await this.config.signer.getRandomSigningCommitment()
|
|
19161
|
+
};
|
|
19162
|
+
const sparkClient = await this.connectionManager.createSparkClient(
|
|
19163
|
+
this.config.getCoordinatorAddress()
|
|
19164
|
+
);
|
|
19165
|
+
const response = await sparkClient.refresh_timelock({
|
|
19166
|
+
leafId: node.id,
|
|
19167
|
+
ownerIdentityPublicKey: await this.config.signer.getIdentityPublicKey(),
|
|
19168
|
+
signingJobs: [refundSigningJob]
|
|
19169
|
+
});
|
|
19170
|
+
if (response.signingResults.length !== 1) {
|
|
19171
|
+
throw Error(
|
|
19172
|
+
`Expected 1 signing result, got ${response.signingResults.length}`
|
|
19173
|
+
);
|
|
19174
|
+
}
|
|
19175
|
+
const signingResult = response.signingResults[0];
|
|
19176
|
+
if (!signingResult || !refundSigningJob.signingNonceCommitment) {
|
|
19177
|
+
throw Error("Signing result or nonce commitment does not exist");
|
|
19178
|
+
}
|
|
19179
|
+
const rawTx = getTxFromRawTxBytes(refundSigningJob.rawTx);
|
|
19180
|
+
const txOut = nodeTx.getOutput(0);
|
|
19181
|
+
const rawTxSighash = getSigHashFromTx(rawTx, 0, txOut);
|
|
19182
|
+
const userSignature = await this.config.signer.signFrost({
|
|
19183
|
+
message: rawTxSighash,
|
|
19184
|
+
privateAsPubKey: signingPubKey,
|
|
19185
|
+
publicKey: signingPubKey,
|
|
19186
|
+
verifyingKey: signingResult.verifyingKey,
|
|
19187
|
+
selfCommitment: refundSigningJob.signingNonceCommitment,
|
|
19188
|
+
statechainCommitments: signingResult.signingResult?.signingNonceCommitments,
|
|
19189
|
+
adaptorPubKey: new Uint8Array()
|
|
19190
|
+
});
|
|
19191
|
+
const signature = await this.config.signer.aggregateFrost({
|
|
19192
|
+
message: rawTxSighash,
|
|
19193
|
+
statechainSignatures: signingResult.signingResult?.signatureShares,
|
|
19194
|
+
statechainPublicKeys: signingResult.signingResult?.publicKeys,
|
|
19195
|
+
verifyingKey: signingResult.verifyingKey,
|
|
19196
|
+
statechainCommitments: signingResult.signingResult?.signingNonceCommitments,
|
|
19197
|
+
selfCommitment: refundSigningJob.signingNonceCommitment,
|
|
19198
|
+
publicKey: signingPubKey,
|
|
19199
|
+
selfSignature: userSignature,
|
|
19200
|
+
adaptorPubKey: new Uint8Array()
|
|
19201
|
+
});
|
|
19202
|
+
const result = await sparkClient.finalize_node_signatures({
|
|
19203
|
+
intent: 3 /* REFRESH */,
|
|
19204
|
+
nodeSignatures: [
|
|
19205
|
+
{
|
|
19206
|
+
nodeId: node.id,
|
|
19207
|
+
nodeTxSignature: new Uint8Array(),
|
|
19208
|
+
refundTxSignature: signature
|
|
19209
|
+
}
|
|
19210
|
+
]
|
|
19211
|
+
});
|
|
19212
|
+
return result;
|
|
19213
|
+
}
|
|
19016
19214
|
};
|
|
19017
19215
|
|
|
19018
19216
|
// src/services/coop-exit.ts
|
|
@@ -19180,7 +19378,7 @@ import { sha256 as sha2568 } from "@noble/hashes/sha2";
|
|
|
19180
19378
|
import { hexToBytes as hexToBytes6 } from "@noble/hashes/utils";
|
|
19181
19379
|
import * as btc3 from "@scure/btc-signer";
|
|
19182
19380
|
import { p2tr as p2tr2, Transaction as Transaction5 } from "@scure/btc-signer";
|
|
19183
|
-
import { equalBytes as
|
|
19381
|
+
import { equalBytes as equalBytes4 } from "@scure/btc-signer/utils";
|
|
19184
19382
|
|
|
19185
19383
|
// src/utils/proof.ts
|
|
19186
19384
|
import { sha256 as sha2567 } from "@noble/hashes/sha2";
|
|
@@ -19314,7 +19512,7 @@ var DepositService = class {
|
|
|
19314
19512
|
depositTx,
|
|
19315
19513
|
vout
|
|
19316
19514
|
}) {
|
|
19317
|
-
const rootTx = new Transaction5();
|
|
19515
|
+
const rootTx = new Transaction5({ version: 3 });
|
|
19318
19516
|
const output = depositTx.getOutput(vout);
|
|
19319
19517
|
if (!output) {
|
|
19320
19518
|
throw new ValidationError("Invalid deposit transaction output", {
|
|
@@ -19332,17 +19530,19 @@ var DepositService = class {
|
|
|
19332
19530
|
expected: "Output with script and amount"
|
|
19333
19531
|
});
|
|
19334
19532
|
}
|
|
19533
|
+
let outputAmount = amount;
|
|
19335
19534
|
rootTx.addInput({
|
|
19336
19535
|
txid: getTxId(depositTx),
|
|
19337
19536
|
index: vout
|
|
19338
19537
|
});
|
|
19339
19538
|
rootTx.addOutput({
|
|
19340
19539
|
script,
|
|
19341
|
-
amount
|
|
19540
|
+
amount: outputAmount
|
|
19342
19541
|
});
|
|
19542
|
+
rootTx.addOutput(getEphemeralAnchorOutput());
|
|
19343
19543
|
const rootNonceCommitment = await this.config.signer.getRandomSigningCommitment();
|
|
19344
19544
|
const rootTxSighash = getSigHashFromTx(rootTx, 0, output);
|
|
19345
|
-
const refundTx = new Transaction5();
|
|
19545
|
+
const refundTx = new Transaction5({ version: 3 });
|
|
19346
19546
|
const sequence = 1 << 30 | INITIAL_TIME_LOCK2;
|
|
19347
19547
|
refundTx.addInput({
|
|
19348
19548
|
txid: getTxId(rootTx),
|
|
@@ -19357,10 +19557,11 @@ var DepositService = class {
|
|
|
19357
19557
|
const refundPkScript = btc3.OutScript.encode(refundAddress);
|
|
19358
19558
|
refundTx.addOutput({
|
|
19359
19559
|
script: refundPkScript,
|
|
19360
|
-
amount
|
|
19560
|
+
amount: outputAmount
|
|
19361
19561
|
});
|
|
19562
|
+
refundTx.addOutput(getEphemeralAnchorOutput());
|
|
19362
19563
|
const refundNonceCommitment = await this.config.signer.getRandomSigningCommitment();
|
|
19363
|
-
const refundTxSighash = getSigHashFromTx(refundTx, 0,
|
|
19564
|
+
const refundTxSighash = getSigHashFromTx(refundTx, 0, rootTx.getOutput(0));
|
|
19364
19565
|
const sparkClient = await this.connectionManager.createSparkClient(
|
|
19365
19566
|
this.config.getCoordinatorAddress()
|
|
19366
19567
|
);
|
|
@@ -19420,7 +19621,7 @@ var DepositService = class {
|
|
|
19420
19621
|
}
|
|
19421
19622
|
);
|
|
19422
19623
|
}
|
|
19423
|
-
if (!
|
|
19624
|
+
if (!equalBytes4(treeResp.rootNodeSignatureShares.verifyingKey, verifyingKey)) {
|
|
19424
19625
|
throw new ValidationError("Verifying key mismatch", {
|
|
19425
19626
|
field: "verifyingKey",
|
|
19426
19627
|
value: treeResp.rootNodeSignatureShares.verifyingKey,
|
|
@@ -23862,7 +24063,7 @@ function isValidPublicKey(publicKey) {
|
|
|
23862
24063
|
}
|
|
23863
24064
|
|
|
23864
24065
|
// src/services/token-transactions.ts
|
|
23865
|
-
var MAX_TOKEN_OUTPUTS =
|
|
24066
|
+
var MAX_TOKEN_OUTPUTS = 500;
|
|
23866
24067
|
var TokenTransactionService = class {
|
|
23867
24068
|
config;
|
|
23868
24069
|
connectionManager;
|
|
@@ -24426,6 +24627,12 @@ import { hexToBytes as hexToBytes10 } from "@noble/curves/abstract/utils";
|
|
|
24426
24627
|
import { sha256 as sha25612 } from "@noble/hashes/sha2";
|
|
24427
24628
|
import { Address as Address4, OutScript as OutScript3, Transaction as Transaction7 } from "@scure/btc-signer";
|
|
24428
24629
|
var INITIAL_TIME_LOCK3 = 2e3;
|
|
24630
|
+
function maybeApplyFee3(amount) {
|
|
24631
|
+
if (amount > BigInt(DEFAULT_FEE_SATS)) {
|
|
24632
|
+
return amount - BigInt(DEFAULT_FEE_SATS);
|
|
24633
|
+
}
|
|
24634
|
+
return amount;
|
|
24635
|
+
}
|
|
24429
24636
|
var TreeCreationService = class {
|
|
24430
24637
|
config;
|
|
24431
24638
|
connectionManager;
|
|
@@ -24624,7 +24831,7 @@ var TreeCreationService = class {
|
|
|
24624
24831
|
refundTxSigningJob: void 0,
|
|
24625
24832
|
children: []
|
|
24626
24833
|
};
|
|
24627
|
-
const tx = new Transaction7();
|
|
24834
|
+
const tx = new Transaction7({ version: 3 });
|
|
24628
24835
|
tx.addInput({
|
|
24629
24836
|
txid: getTxId(parentTx),
|
|
24630
24837
|
index: vout
|
|
@@ -24636,6 +24843,7 @@ var TreeCreationService = class {
|
|
|
24636
24843
|
tx.addOutput({
|
|
24637
24844
|
script: parentTxOut.script,
|
|
24638
24845
|
amount: parentTxOut.amount
|
|
24846
|
+
// maybeApplyFee(parentTxOut.amount),
|
|
24639
24847
|
});
|
|
24640
24848
|
tx.addOutput(getEphemeralAnchorOutput());
|
|
24641
24849
|
const signingNonceCommitment = await this.config.signer.getRandomSigningCommitment();
|
|
@@ -24652,7 +24860,7 @@ var TreeCreationService = class {
|
|
|
24652
24860
|
refundTxSigningJob: void 0,
|
|
24653
24861
|
children: []
|
|
24654
24862
|
};
|
|
24655
|
-
const childTx = new Transaction7();
|
|
24863
|
+
const childTx = new Transaction7({ version: 3 });
|
|
24656
24864
|
childTx.addInput({
|
|
24657
24865
|
txid: getTxId(tx),
|
|
24658
24866
|
index: 0,
|
|
@@ -24661,6 +24869,7 @@ var TreeCreationService = class {
|
|
|
24661
24869
|
childTx.addOutput({
|
|
24662
24870
|
script: parentTxOut.script,
|
|
24663
24871
|
amount: parentTxOut.amount
|
|
24872
|
+
// maybeApplyFee(parentTxOut.amount),
|
|
24664
24873
|
});
|
|
24665
24874
|
childTx.addOutput(getEphemeralAnchorOutput());
|
|
24666
24875
|
const childSigningNonceCommitment = await this.config.signer.getRandomSigningCommitment();
|
|
@@ -24671,7 +24880,7 @@ var TreeCreationService = class {
|
|
|
24671
24880
|
};
|
|
24672
24881
|
childCreationNode.nodeTxSigningCommitment = childSigningNonceCommitment;
|
|
24673
24882
|
childCreationNode.nodeTxSigningJob = childSigningJob;
|
|
24674
|
-
const refundTx = new Transaction7();
|
|
24883
|
+
const refundTx = new Transaction7({ version: 3 });
|
|
24675
24884
|
refundTx.addInput({
|
|
24676
24885
|
txid: getTxId(childTx),
|
|
24677
24886
|
index: 0,
|
|
@@ -24687,8 +24896,9 @@ var TreeCreationService = class {
|
|
|
24687
24896
|
const refundPkScript = OutScript3.encode(refundAddress);
|
|
24688
24897
|
refundTx.addOutput({
|
|
24689
24898
|
script: refundPkScript,
|
|
24690
|
-
amount: parentTxOut.amount
|
|
24899
|
+
amount: maybeApplyFee3(parentTxOut.amount)
|
|
24691
24900
|
});
|
|
24901
|
+
refundTx.addOutput(getEphemeralAnchorOutput());
|
|
24692
24902
|
const refundSigningNonceCommitment = await this.config.signer.getRandomSigningCommitment();
|
|
24693
24903
|
const refundSigningJob = {
|
|
24694
24904
|
signingPublicKey: node.signingPublicKey,
|
|
@@ -24705,7 +24915,7 @@ var TreeCreationService = class {
|
|
|
24705
24915
|
if (!parentTxOutput?.script || !parentTxOutput?.amount) {
|
|
24706
24916
|
throw new Error("parentTxOutput is undefined");
|
|
24707
24917
|
}
|
|
24708
|
-
const rootNodeTx = new Transaction7();
|
|
24918
|
+
const rootNodeTx = new Transaction7({ version: 3 });
|
|
24709
24919
|
rootNodeTx.addInput({
|
|
24710
24920
|
txid: getTxId(parentTx),
|
|
24711
24921
|
index: vout
|
|
@@ -24720,10 +24930,10 @@ var TreeCreationService = class {
|
|
|
24720
24930
|
rootNodeTx.addOutput({
|
|
24721
24931
|
script: childPkScript,
|
|
24722
24932
|
amount: parentTxOutput.amount / 2n
|
|
24933
|
+
// feeAdjustedAmount / 2n,
|
|
24723
24934
|
});
|
|
24724
24935
|
}
|
|
24725
|
-
|
|
24726
|
-
rootNodeTx.addOutput(anchor);
|
|
24936
|
+
rootNodeTx.addOutput(getEphemeralAnchorOutput());
|
|
24727
24937
|
const rootNodeSigningCommitment = await this.config.signer.getRandomSigningCommitment();
|
|
24728
24938
|
const rootNodeSigningJob = {
|
|
24729
24939
|
signingPublicKey: root.signingPublicKey,
|
|
@@ -25410,7 +25620,7 @@ var SparkWallet = class _SparkWallet extends EventEmitter {
|
|
|
25410
25620
|
try {
|
|
25411
25621
|
if (event?.$case === "transfer" && event.transfer.transfer && event.transfer.transfer.type !== 40 /* COUNTER_SWAP */) {
|
|
25412
25622
|
const { senderIdentityPublicKey, receiverIdentityPublicKey } = event.transfer.transfer;
|
|
25413
|
-
if (event.transfer.transfer && !
|
|
25623
|
+
if (event.transfer.transfer && !equalBytes5(senderIdentityPublicKey, receiverIdentityPublicKey)) {
|
|
25414
25624
|
await this.claimTransfer({
|
|
25415
25625
|
transfer: event.transfer.transfer,
|
|
25416
25626
|
emit: true,
|
|
@@ -25572,10 +25782,10 @@ var SparkWallet = class _SparkWallet extends EventEmitter {
|
|
|
25572
25782
|
leavesToIgnore.add(nodeId);
|
|
25573
25783
|
continue;
|
|
25574
25784
|
}
|
|
25575
|
-
if (leaf.status !== operatorLeaf.status || !leaf.signingKeyshare || !operatorLeaf.signingKeyshare || !
|
|
25785
|
+
if (leaf.status !== operatorLeaf.status || !leaf.signingKeyshare || !operatorLeaf.signingKeyshare || !equalBytes5(
|
|
25576
25786
|
leaf.signingKeyshare.publicKey,
|
|
25577
25787
|
operatorLeaf.signingKeyshare.publicKey
|
|
25578
|
-
) || !
|
|
25788
|
+
) || !equalBytes5(leaf.nodeTx, operatorLeaf.nodeTx) || !equalBytes5(leaf.refundTx, operatorLeaf.refundTx)) {
|
|
25579
25789
|
leavesToIgnore.add(nodeId);
|
|
25580
25790
|
}
|
|
25581
25791
|
}
|
|
@@ -25583,7 +25793,7 @@ var SparkWallet = class _SparkWallet extends EventEmitter {
|
|
|
25583
25793
|
}
|
|
25584
25794
|
}
|
|
25585
25795
|
const verifyKey = (pubkey1, pubkey2, verifyingKey) => {
|
|
25586
|
-
return
|
|
25796
|
+
return equalBytes5(addPublicKeys(pubkey1, pubkey2), verifyingKey);
|
|
25587
25797
|
};
|
|
25588
25798
|
for (const [id, leaf] of Object.entries(leaves.nodes)) {
|
|
25589
25799
|
if (!verifyKey(
|
|
@@ -25978,7 +26188,7 @@ var SparkWallet = class _SparkWallet extends EventEmitter {
|
|
|
25978
26188
|
adaptorPrivateKey
|
|
25979
26189
|
);
|
|
25980
26190
|
}
|
|
25981
|
-
await this.transferService.
|
|
26191
|
+
await this.transferService.deliverTransferPackage(
|
|
25982
26192
|
transfer,
|
|
25983
26193
|
leafKeyTweaks,
|
|
25984
26194
|
signatureMap
|
|
@@ -26751,7 +26961,7 @@ var SparkWallet = class _SparkWallet extends EventEmitter {
|
|
|
26751
26961
|
this.config.getNetworkType()
|
|
26752
26962
|
);
|
|
26753
26963
|
const signerIdentityPublicKey = await this.config.signer.getIdentityPublicKey();
|
|
26754
|
-
const isSelfTransfer =
|
|
26964
|
+
const isSelfTransfer = equalBytes5(
|
|
26755
26965
|
signerIdentityPublicKey,
|
|
26756
26966
|
hexToBytes13(receiverAddress)
|
|
26757
26967
|
);
|
|
@@ -27663,6 +27873,112 @@ var SparkWallet = class _SparkWallet extends EventEmitter {
|
|
|
27663
27873
|
}
|
|
27664
27874
|
return this.config.signer.validateMessageWithIdentityKey(hash, signature);
|
|
27665
27875
|
}
|
|
27876
|
+
/**
|
|
27877
|
+
* Signs a transaction with wallet keys.
|
|
27878
|
+
*
|
|
27879
|
+
* @param {string} txHex - The transaction hex to sign
|
|
27880
|
+
* @param {string} keyType - The type of key to use for signing ("identity", "deposit", or "auto-detect")
|
|
27881
|
+
* @returns {Promise<string>} The signed transaction hex
|
|
27882
|
+
*/
|
|
27883
|
+
async signTransaction(txHex, keyType = "auto-detect") {
|
|
27884
|
+
try {
|
|
27885
|
+
const tx = Transaction9.fromRaw(hexToBytes13(txHex));
|
|
27886
|
+
let publicKey;
|
|
27887
|
+
switch (keyType.toLowerCase()) {
|
|
27888
|
+
case "identity":
|
|
27889
|
+
publicKey = await this.config.signer.getIdentityPublicKey();
|
|
27890
|
+
break;
|
|
27891
|
+
case "deposit":
|
|
27892
|
+
publicKey = await this.config.signer.getDepositSigningKey();
|
|
27893
|
+
break;
|
|
27894
|
+
case "auto-detect":
|
|
27895
|
+
default:
|
|
27896
|
+
const detectedKey = await this.detectKeyForTransaction(tx);
|
|
27897
|
+
if (detectedKey) {
|
|
27898
|
+
publicKey = detectedKey.publicKey;
|
|
27899
|
+
} else {
|
|
27900
|
+
publicKey = await this.config.signer.getIdentityPublicKey();
|
|
27901
|
+
}
|
|
27902
|
+
break;
|
|
27903
|
+
}
|
|
27904
|
+
let inputsSigned = 0;
|
|
27905
|
+
for (let i = 0; i < tx.inputsLength; i++) {
|
|
27906
|
+
const input = tx.getInput(i);
|
|
27907
|
+
if (!input?.witnessUtxo?.script) {
|
|
27908
|
+
continue;
|
|
27909
|
+
}
|
|
27910
|
+
const script = input.witnessUtxo.script;
|
|
27911
|
+
if (script.length === 1 && script[0] === 81) {
|
|
27912
|
+
continue;
|
|
27913
|
+
}
|
|
27914
|
+
const identityScript = getP2TRScriptFromPublicKey(
|
|
27915
|
+
publicKey,
|
|
27916
|
+
this.config.getNetwork()
|
|
27917
|
+
);
|
|
27918
|
+
if (bytesToHex12(script) === bytesToHex12(identityScript)) {
|
|
27919
|
+
try {
|
|
27920
|
+
this.config.signer.signTransactionIndex(tx, i, publicKey);
|
|
27921
|
+
inputsSigned++;
|
|
27922
|
+
} catch (error) {
|
|
27923
|
+
throw new ValidationError(`Failed to sign input ${i}: ${error}`, {
|
|
27924
|
+
field: "input",
|
|
27925
|
+
value: i
|
|
27926
|
+
});
|
|
27927
|
+
}
|
|
27928
|
+
}
|
|
27929
|
+
}
|
|
27930
|
+
if (inputsSigned === 0) {
|
|
27931
|
+
throw new Error(
|
|
27932
|
+
"No inputs were signed. Check that the transaction contains inputs controlled by this wallet."
|
|
27933
|
+
);
|
|
27934
|
+
}
|
|
27935
|
+
tx.finalize();
|
|
27936
|
+
const signedTxHex = tx.hex;
|
|
27937
|
+
return signedTxHex;
|
|
27938
|
+
} catch (error) {
|
|
27939
|
+
console.error("\u274C Error signing transaction:", error);
|
|
27940
|
+
throw error;
|
|
27941
|
+
}
|
|
27942
|
+
}
|
|
27943
|
+
/**
|
|
27944
|
+
* Helper method to auto-detect which key should be used for signing a transaction.
|
|
27945
|
+
*/
|
|
27946
|
+
async detectKeyForTransaction(tx) {
|
|
27947
|
+
try {
|
|
27948
|
+
const identityPubKey = await this.config.signer.getIdentityPublicKey();
|
|
27949
|
+
const depositPubKey = await this.config.signer.getDepositSigningKey();
|
|
27950
|
+
for (let i = 0; i < tx.inputsLength; i++) {
|
|
27951
|
+
const input = tx.getInput(i);
|
|
27952
|
+
if (input?.witnessUtxo?.script) {
|
|
27953
|
+
const script = input.witnessUtxo.script;
|
|
27954
|
+
const identityScript = getP2TRScriptFromPublicKey(
|
|
27955
|
+
identityPubKey,
|
|
27956
|
+
this.config.getNetwork()
|
|
27957
|
+
);
|
|
27958
|
+
const depositScript = getP2TRScriptFromPublicKey(
|
|
27959
|
+
depositPubKey,
|
|
27960
|
+
this.config.getNetwork()
|
|
27961
|
+
);
|
|
27962
|
+
if (bytesToHex12(script) === bytesToHex12(identityScript)) {
|
|
27963
|
+
return {
|
|
27964
|
+
publicKey: identityPubKey,
|
|
27965
|
+
keyType: "identity"
|
|
27966
|
+
};
|
|
27967
|
+
}
|
|
27968
|
+
if (bytesToHex12(script) === bytesToHex12(depositScript)) {
|
|
27969
|
+
return {
|
|
27970
|
+
publicKey: depositPubKey,
|
|
27971
|
+
keyType: "deposit"
|
|
27972
|
+
};
|
|
27973
|
+
}
|
|
27974
|
+
}
|
|
27975
|
+
}
|
|
27976
|
+
return null;
|
|
27977
|
+
} catch (error) {
|
|
27978
|
+
console.warn("Error during key auto-detection:", error);
|
|
27979
|
+
return null;
|
|
27980
|
+
}
|
|
27981
|
+
}
|
|
27666
27982
|
/**
|
|
27667
27983
|
* Get a Lightning receive request by ID.
|
|
27668
27984
|
*
|
|
@@ -27693,6 +28009,246 @@ var SparkWallet = class _SparkWallet extends EventEmitter {
|
|
|
27693
28009
|
const sspClient = this.getSspClient();
|
|
27694
28010
|
return await sspClient.getCoopExitRequest(id);
|
|
27695
28011
|
}
|
|
28012
|
+
/**
|
|
28013
|
+
* Check the remaining timelock on a given node.
|
|
28014
|
+
*
|
|
28015
|
+
* @param {string} nodeId - The ID of the node to check
|
|
28016
|
+
* @returns {Promise<{nodeTimelock: number, refundTimelock: number}>} The remaining timelocks in blocks for both node and refund transactions
|
|
28017
|
+
*/
|
|
28018
|
+
async checkTimelock(nodeId) {
|
|
28019
|
+
const sparkClient = await this.connectionManager.createSparkClient(
|
|
28020
|
+
this.config.getCoordinatorAddress()
|
|
28021
|
+
);
|
|
28022
|
+
try {
|
|
28023
|
+
const response = await sparkClient.query_nodes({
|
|
28024
|
+
source: {
|
|
28025
|
+
$case: "nodeIds",
|
|
28026
|
+
nodeIds: {
|
|
28027
|
+
nodeIds: [nodeId]
|
|
28028
|
+
}
|
|
28029
|
+
},
|
|
28030
|
+
includeParents: false,
|
|
28031
|
+
network: NetworkToProto[this.config.getNetwork()]
|
|
28032
|
+
});
|
|
28033
|
+
const node = response.nodes[nodeId];
|
|
28034
|
+
if (!node) {
|
|
28035
|
+
throw new ValidationError("Node not found", {
|
|
28036
|
+
field: "nodeId",
|
|
28037
|
+
value: nodeId
|
|
28038
|
+
});
|
|
28039
|
+
}
|
|
28040
|
+
const isRootNode = !node.parentNodeId;
|
|
28041
|
+
if (!node.nodeTx || node.nodeTx.length === 0) {
|
|
28042
|
+
throw new ValidationError(
|
|
28043
|
+
`Node transaction data is missing or empty for ${isRootNode ? "root" : "non-root"} node`,
|
|
28044
|
+
{
|
|
28045
|
+
field: "nodeTx",
|
|
28046
|
+
value: node.nodeTx?.length || 0
|
|
28047
|
+
}
|
|
28048
|
+
);
|
|
28049
|
+
}
|
|
28050
|
+
if (!node.refundTx || node.refundTx.length === 0) {
|
|
28051
|
+
throw new ValidationError(
|
|
28052
|
+
`Refund transaction data is missing or empty for ${isRootNode ? "root" : "non-root"} node`,
|
|
28053
|
+
{
|
|
28054
|
+
field: "refundTx",
|
|
28055
|
+
value: node.refundTx?.length || 0
|
|
28056
|
+
}
|
|
28057
|
+
);
|
|
28058
|
+
}
|
|
28059
|
+
let nodeTx, refundTx;
|
|
28060
|
+
try {
|
|
28061
|
+
nodeTx = getTxFromRawTxBytes(node.nodeTx);
|
|
28062
|
+
} catch (error) {
|
|
28063
|
+
throw new ValidationError(
|
|
28064
|
+
`Failed to parse node transaction for ${isRootNode ? "root" : "non-root"} node: ${error instanceof Error ? error.message : String(error)}`,
|
|
28065
|
+
{
|
|
28066
|
+
field: "nodeTx",
|
|
28067
|
+
value: node.nodeTx.length
|
|
28068
|
+
}
|
|
28069
|
+
);
|
|
28070
|
+
}
|
|
28071
|
+
try {
|
|
28072
|
+
refundTx = getTxFromRawTxBytes(node.refundTx);
|
|
28073
|
+
} catch (error) {
|
|
28074
|
+
throw new ValidationError(
|
|
28075
|
+
`Failed to parse refund transaction for ${isRootNode ? "root" : "non-root"} node: ${error instanceof Error ? error.message : String(error)}`,
|
|
28076
|
+
{
|
|
28077
|
+
field: "refundTx",
|
|
28078
|
+
value: node.refundTx.length
|
|
28079
|
+
}
|
|
28080
|
+
);
|
|
28081
|
+
}
|
|
28082
|
+
const nodeInput = nodeTx.getInput(0);
|
|
28083
|
+
if (!nodeInput) {
|
|
28084
|
+
throw new ValidationError(
|
|
28085
|
+
`Node transaction has no inputs for ${isRootNode ? "root" : "non-root"} node`,
|
|
28086
|
+
{
|
|
28087
|
+
field: "nodeInput",
|
|
28088
|
+
value: nodeTx.inputsLength
|
|
28089
|
+
}
|
|
28090
|
+
);
|
|
28091
|
+
}
|
|
28092
|
+
if (!nodeInput.sequence) {
|
|
28093
|
+
throw new ValidationError(
|
|
28094
|
+
`Node transaction has no sequence for ${isRootNode ? "root" : "non-root"} node`,
|
|
28095
|
+
{
|
|
28096
|
+
field: "sequence",
|
|
28097
|
+
value: nodeInput.sequence
|
|
28098
|
+
}
|
|
28099
|
+
);
|
|
28100
|
+
}
|
|
28101
|
+
const refundInput = refundTx.getInput(0);
|
|
28102
|
+
if (!refundInput) {
|
|
28103
|
+
throw new ValidationError(
|
|
28104
|
+
`Refund transaction has no inputs for ${isRootNode ? "root" : "non-root"} node`,
|
|
28105
|
+
{
|
|
28106
|
+
field: "refundInput",
|
|
28107
|
+
value: refundTx.inputsLength
|
|
28108
|
+
}
|
|
28109
|
+
);
|
|
28110
|
+
}
|
|
28111
|
+
if (!refundInput.sequence) {
|
|
28112
|
+
throw new ValidationError(
|
|
28113
|
+
`Refund transaction has no sequence for ${isRootNode ? "root" : "non-root"} node`,
|
|
28114
|
+
{
|
|
28115
|
+
field: "sequence",
|
|
28116
|
+
value: refundInput.sequence
|
|
28117
|
+
}
|
|
28118
|
+
);
|
|
28119
|
+
}
|
|
28120
|
+
const nodeTimelock = nodeInput.sequence & 65535;
|
|
28121
|
+
const refundTimelock = refundInput.sequence & 65535;
|
|
28122
|
+
return {
|
|
28123
|
+
nodeTimelock,
|
|
28124
|
+
refundTimelock
|
|
28125
|
+
};
|
|
28126
|
+
} catch (error) {
|
|
28127
|
+
throw new NetworkError(
|
|
28128
|
+
`Failed to check timelock for node ${nodeId}`,
|
|
28129
|
+
{
|
|
28130
|
+
method: "query_nodes"
|
|
28131
|
+
},
|
|
28132
|
+
error
|
|
28133
|
+
);
|
|
28134
|
+
}
|
|
28135
|
+
}
|
|
28136
|
+
/**
|
|
28137
|
+
* Refresh the timelock of a specific node.
|
|
28138
|
+
*
|
|
28139
|
+
* @param {string} nodeId - The ID of the node to refresh
|
|
28140
|
+
* @returns {Promise<void>} Promise that resolves when the timelock is refreshed
|
|
28141
|
+
*/
|
|
28142
|
+
async testOnly_expireTimelock(nodeId) {
|
|
28143
|
+
const sparkClient = await this.connectionManager.createSparkClient(
|
|
28144
|
+
this.config.getCoordinatorAddress()
|
|
28145
|
+
);
|
|
28146
|
+
try {
|
|
28147
|
+
const response = await sparkClient.query_nodes({
|
|
28148
|
+
source: {
|
|
28149
|
+
$case: "nodeIds",
|
|
28150
|
+
nodeIds: {
|
|
28151
|
+
nodeIds: [nodeId]
|
|
28152
|
+
}
|
|
28153
|
+
},
|
|
28154
|
+
includeParents: true
|
|
28155
|
+
});
|
|
28156
|
+
const node = response.nodes[nodeId];
|
|
28157
|
+
if (!node) {
|
|
28158
|
+
throw new ValidationError("Node not found", {
|
|
28159
|
+
field: "nodeId",
|
|
28160
|
+
value: nodeId
|
|
28161
|
+
});
|
|
28162
|
+
}
|
|
28163
|
+
if (!node.parentNodeId) {
|
|
28164
|
+
throw new ValidationError("Node has no parent", {
|
|
28165
|
+
field: "parentNodeId",
|
|
28166
|
+
value: node.parentNodeId
|
|
28167
|
+
});
|
|
28168
|
+
}
|
|
28169
|
+
const parentNode = response.nodes[node.parentNodeId];
|
|
28170
|
+
if (!parentNode) {
|
|
28171
|
+
throw new ValidationError("Parent node not found", {
|
|
28172
|
+
field: "parentNodeId",
|
|
28173
|
+
value: node.parentNodeId
|
|
28174
|
+
});
|
|
28175
|
+
}
|
|
28176
|
+
const signingPubKey = await this.config.signer.generatePublicKey(
|
|
28177
|
+
sha25613(node.id)
|
|
28178
|
+
);
|
|
28179
|
+
const result = await this.transferService.refreshTimelockNodes(
|
|
28180
|
+
[node],
|
|
28181
|
+
parentNode,
|
|
28182
|
+
signingPubKey
|
|
28183
|
+
);
|
|
28184
|
+
const leafIndex = this.leaves.findIndex((leaf) => leaf.id === node.id);
|
|
28185
|
+
if (leafIndex !== -1 && result.nodes.length > 0) {
|
|
28186
|
+
const newNode = result.nodes[0];
|
|
28187
|
+
if (newNode) {
|
|
28188
|
+
this.leaves[leafIndex] = newNode;
|
|
28189
|
+
}
|
|
28190
|
+
}
|
|
28191
|
+
} catch (error) {
|
|
28192
|
+
throw new NetworkError(
|
|
28193
|
+
"Failed to refresh timelock",
|
|
28194
|
+
{
|
|
28195
|
+
method: "refresh_timelock"
|
|
28196
|
+
},
|
|
28197
|
+
error
|
|
28198
|
+
);
|
|
28199
|
+
}
|
|
28200
|
+
}
|
|
28201
|
+
/**
|
|
28202
|
+
* Refresh the timelock of a specific node's refund transaction only.
|
|
28203
|
+
*
|
|
28204
|
+
* @param {string} nodeId - The ID of the node whose refund transaction to refresh
|
|
28205
|
+
* @returns {Promise<void>} Promise that resolves when the refund timelock is refreshed
|
|
28206
|
+
*/
|
|
28207
|
+
async testOnly_expireTimelockRefundTx(nodeId) {
|
|
28208
|
+
const sparkClient = await this.connectionManager.createSparkClient(
|
|
28209
|
+
this.config.getCoordinatorAddress()
|
|
28210
|
+
);
|
|
28211
|
+
try {
|
|
28212
|
+
const response = await sparkClient.query_nodes({
|
|
28213
|
+
source: {
|
|
28214
|
+
$case: "nodeIds",
|
|
28215
|
+
nodeIds: {
|
|
28216
|
+
nodeIds: [nodeId]
|
|
28217
|
+
}
|
|
28218
|
+
},
|
|
28219
|
+
includeParents: false
|
|
28220
|
+
});
|
|
28221
|
+
const node = response.nodes[nodeId];
|
|
28222
|
+
if (!node) {
|
|
28223
|
+
throw new ValidationError("Node not found", {
|
|
28224
|
+
field: "nodeId",
|
|
28225
|
+
value: nodeId
|
|
28226
|
+
});
|
|
28227
|
+
}
|
|
28228
|
+
const signingPubKey = await this.config.signer.generatePublicKey(
|
|
28229
|
+
sha25613(node.id)
|
|
28230
|
+
);
|
|
28231
|
+
const result = await this.transferService.refreshTimelockRefundTx(
|
|
28232
|
+
node,
|
|
28233
|
+
signingPubKey
|
|
28234
|
+
);
|
|
28235
|
+
const leafIndex = this.leaves.findIndex((leaf) => leaf.id === node.id);
|
|
28236
|
+
if (leafIndex !== -1 && result.nodes.length > 0) {
|
|
28237
|
+
const newNode = result.nodes[0];
|
|
28238
|
+
if (newNode) {
|
|
28239
|
+
this.leaves[leafIndex] = newNode;
|
|
28240
|
+
}
|
|
28241
|
+
}
|
|
28242
|
+
} catch (error) {
|
|
28243
|
+
throw new NetworkError(
|
|
28244
|
+
"Failed to refresh refund timelock",
|
|
28245
|
+
{
|
|
28246
|
+
method: "refresh_timelock_refund_tx"
|
|
28247
|
+
},
|
|
28248
|
+
error
|
|
28249
|
+
);
|
|
28250
|
+
}
|
|
28251
|
+
}
|
|
27696
28252
|
cleanup() {
|
|
27697
28253
|
if (this.claimTransfersInterval) {
|
|
27698
28254
|
clearInterval(this.claimTransfersInterval);
|
|
@@ -27775,9 +28331,471 @@ async function getLatestDepositTxId(address2) {
|
|
|
27775
28331
|
}
|
|
27776
28332
|
return null;
|
|
27777
28333
|
}
|
|
28334
|
+
async function isTxBroadcast(txid, baseUrl, network) {
|
|
28335
|
+
const headers = {};
|
|
28336
|
+
if (network === 3 /* REGTEST */) {
|
|
28337
|
+
const auth = btoa(
|
|
28338
|
+
`${ELECTRS_CREDENTIALS.username}:${ELECTRS_CREDENTIALS.password}`
|
|
28339
|
+
);
|
|
28340
|
+
headers["Authorization"] = `Basic ${auth}`;
|
|
28341
|
+
}
|
|
28342
|
+
const response = await fetch(`${baseUrl}/tx/${txid}`, {
|
|
28343
|
+
headers
|
|
28344
|
+
});
|
|
28345
|
+
const tx = await response.json();
|
|
28346
|
+
if (tx.error) {
|
|
28347
|
+
return false;
|
|
28348
|
+
}
|
|
28349
|
+
return true;
|
|
28350
|
+
}
|
|
28351
|
+
|
|
28352
|
+
// src/utils/unilateral-exit.ts
|
|
28353
|
+
import { bytesToHex as bytesToHex13, hexToBytes as hexToBytes14 } from "@noble/curves/abstract/utils";
|
|
28354
|
+
import { ripemd160 } from "@noble/hashes/legacy";
|
|
28355
|
+
import { sha256 as sha25614 } from "@noble/hashes/sha2";
|
|
28356
|
+
import * as btc5 from "@scure/btc-signer";
|
|
28357
|
+
function isEphemeralAnchorOutput(script, amount) {
|
|
28358
|
+
return Boolean(
|
|
28359
|
+
amount === 0n && script && // Pattern 1: Bare OP_TRUE (single byte 0x51)
|
|
28360
|
+
(script.length === 1 && script[0] === 81 || // Pattern 2: Push OP_TRUE (two bytes 0x01 0x51) - MALFORMED but we detect it
|
|
28361
|
+
script.length === 2 && script[0] === 1 && script[1] === 81 || // Pattern 3: Bitcoin v29 ephemeral anchor script (7 bytes: 015152014e0173)
|
|
28362
|
+
script.length === 7 && script[0] === 1 && script[1] === 81 && script[2] === 82 && script[3] === 1 && script[4] === 78 && script[5] === 1 && script[6] === 115 || // Pattern 4: Bitcoin ephemeral anchor OP_1 + push 2 bytes (4 bytes: 51024e73)
|
|
28363
|
+
script.length === 4 && script[0] === 81 && script[1] === 2 && script[2] === 78 && script[3] === 115)
|
|
28364
|
+
);
|
|
28365
|
+
}
|
|
28366
|
+
async function constructUnilateralExitTxs(nodeHexStrings, sparkClient, network) {
|
|
28367
|
+
const result = [];
|
|
28368
|
+
const nodes = nodeHexStrings.map((hex) => TreeNode.decode(hexToBytes14(hex)));
|
|
28369
|
+
const nodeMap = /* @__PURE__ */ new Map();
|
|
28370
|
+
for (const node of nodes) {
|
|
28371
|
+
nodeMap.set(node.id, node);
|
|
28372
|
+
}
|
|
28373
|
+
for (const node of nodes) {
|
|
28374
|
+
const transactions = [];
|
|
28375
|
+
const chain = [];
|
|
28376
|
+
let currentNode = node;
|
|
28377
|
+
while (currentNode) {
|
|
28378
|
+
chain.unshift(currentNode);
|
|
28379
|
+
if (currentNode.parentNodeId) {
|
|
28380
|
+
let parentNode = nodeMap.get(currentNode.parentNodeId);
|
|
28381
|
+
if (!parentNode && sparkClient) {
|
|
28382
|
+
try {
|
|
28383
|
+
const response = await sparkClient.query_nodes({
|
|
28384
|
+
source: {
|
|
28385
|
+
$case: "nodeIds",
|
|
28386
|
+
nodeIds: {
|
|
28387
|
+
nodeIds: [currentNode.parentNodeId]
|
|
28388
|
+
}
|
|
28389
|
+
},
|
|
28390
|
+
includeParents: true,
|
|
28391
|
+
network: network || 0
|
|
28392
|
+
// Default to mainnet if not provided
|
|
28393
|
+
});
|
|
28394
|
+
parentNode = response.nodes[currentNode.parentNodeId];
|
|
28395
|
+
if (parentNode) {
|
|
28396
|
+
nodeMap.set(currentNode.parentNodeId, parentNode);
|
|
28397
|
+
}
|
|
28398
|
+
} catch (error) {
|
|
28399
|
+
console.warn(
|
|
28400
|
+
`Failed to query parent node ${currentNode.parentNodeId}: ${error}`
|
|
28401
|
+
);
|
|
28402
|
+
break;
|
|
28403
|
+
}
|
|
28404
|
+
}
|
|
28405
|
+
if (parentNode) {
|
|
28406
|
+
currentNode = parentNode;
|
|
28407
|
+
} else {
|
|
28408
|
+
if (!sparkClient) {
|
|
28409
|
+
console.warn(
|
|
28410
|
+
`Parent node ${currentNode.parentNodeId} not found. Provide a sparkClient to fetch missing parents.`
|
|
28411
|
+
);
|
|
28412
|
+
} else {
|
|
28413
|
+
console.warn(
|
|
28414
|
+
`Parent node ${currentNode.parentNodeId} not found in database. Chain may be incomplete.`
|
|
28415
|
+
);
|
|
28416
|
+
}
|
|
28417
|
+
break;
|
|
28418
|
+
}
|
|
28419
|
+
} else {
|
|
28420
|
+
break;
|
|
28421
|
+
}
|
|
28422
|
+
}
|
|
28423
|
+
for (const chainNode of chain) {
|
|
28424
|
+
const nodeTx = bytesToHex13(chainNode.nodeTx);
|
|
28425
|
+
transactions.push(nodeTx);
|
|
28426
|
+
if (chainNode.id === node.id) {
|
|
28427
|
+
const refundTx = bytesToHex13(chainNode.refundTx);
|
|
28428
|
+
transactions.push(refundTx);
|
|
28429
|
+
}
|
|
28430
|
+
}
|
|
28431
|
+
result.push({
|
|
28432
|
+
leafId: node.id,
|
|
28433
|
+
transactions
|
|
28434
|
+
});
|
|
28435
|
+
}
|
|
28436
|
+
return result;
|
|
28437
|
+
}
|
|
28438
|
+
async function constructUnilateralExitFeeBumpPackages(nodeHexStrings, utxos, feeRate, electrsUrl, sparkClient, network) {
|
|
28439
|
+
const result = [];
|
|
28440
|
+
const availableUtxos = [...utxos].sort((a, b) => {
|
|
28441
|
+
if (a.value > b.value) return -1;
|
|
28442
|
+
if (a.value < b.value) return 1;
|
|
28443
|
+
return 0;
|
|
28444
|
+
});
|
|
28445
|
+
const nodes = [];
|
|
28446
|
+
for (let i = 0; i < nodeHexStrings.length; i++) {
|
|
28447
|
+
const hex = nodeHexStrings[i];
|
|
28448
|
+
try {
|
|
28449
|
+
if (!hex || hex.length === 0) {
|
|
28450
|
+
throw new Error(`Node hex string at index ${i} is empty`);
|
|
28451
|
+
}
|
|
28452
|
+
if (hex.startsWith("03000000") || hex.startsWith("02000000") || hex.startsWith("01000000")) {
|
|
28453
|
+
throw new Error(
|
|
28454
|
+
`Node hex string at index ${i} appears to be a raw transaction hex, not a TreeNode protobuf. Use 'leafidtohex' command to convert node IDs to proper hex strings.`
|
|
28455
|
+
);
|
|
28456
|
+
}
|
|
28457
|
+
const nodeBytes = hexToBytes14(hex);
|
|
28458
|
+
const node = TreeNode.decode(nodeBytes);
|
|
28459
|
+
if (!node.id) {
|
|
28460
|
+
throw new Error(
|
|
28461
|
+
`Decoded TreeNode at index ${i} is missing required 'id' field`
|
|
28462
|
+
);
|
|
28463
|
+
}
|
|
28464
|
+
if (!node.nodeTx || node.nodeTx.length === 0) {
|
|
28465
|
+
throw new Error(
|
|
28466
|
+
`Decoded TreeNode at index ${i} is missing required 'nodeTx' field`
|
|
28467
|
+
);
|
|
28468
|
+
}
|
|
28469
|
+
nodes.push(node);
|
|
28470
|
+
} catch (decodeError) {
|
|
28471
|
+
throw new Error(
|
|
28472
|
+
`Failed to decode TreeNode hex string at index ${i}: ${decodeError}. Make sure you're providing TreeNode protobuf hex strings, not raw transaction hex. Use 'leafidtohex' command to get proper hex strings.`
|
|
28473
|
+
);
|
|
28474
|
+
}
|
|
28475
|
+
}
|
|
28476
|
+
const nodeMap = /* @__PURE__ */ new Map();
|
|
28477
|
+
for (const node of nodes) {
|
|
28478
|
+
nodeMap.set(node.id, node);
|
|
28479
|
+
}
|
|
28480
|
+
const broadcastTxs = /* @__PURE__ */ new Map();
|
|
28481
|
+
for (const node of nodes) {
|
|
28482
|
+
const txPackages = [];
|
|
28483
|
+
let previousFeeBumpTx;
|
|
28484
|
+
const chain = [];
|
|
28485
|
+
let currentNode = node;
|
|
28486
|
+
while (currentNode) {
|
|
28487
|
+
chain.unshift(currentNode);
|
|
28488
|
+
if (currentNode.parentNodeId) {
|
|
28489
|
+
let parentNode = nodeMap.get(currentNode.parentNodeId);
|
|
28490
|
+
if (!parentNode && sparkClient) {
|
|
28491
|
+
try {
|
|
28492
|
+
const response = await sparkClient.query_nodes({
|
|
28493
|
+
source: {
|
|
28494
|
+
$case: "nodeIds",
|
|
28495
|
+
nodeIds: {
|
|
28496
|
+
nodeIds: [currentNode.parentNodeId]
|
|
28497
|
+
}
|
|
28498
|
+
},
|
|
28499
|
+
includeParents: true,
|
|
28500
|
+
network: network || 0
|
|
28501
|
+
// Default to mainnet if not provided
|
|
28502
|
+
});
|
|
28503
|
+
parentNode = response.nodes[currentNode.parentNodeId];
|
|
28504
|
+
if (parentNode) {
|
|
28505
|
+
nodeMap.set(currentNode.parentNodeId, parentNode);
|
|
28506
|
+
}
|
|
28507
|
+
} catch (error) {
|
|
28508
|
+
console.warn(
|
|
28509
|
+
`Failed to query parent node ${currentNode.parentNodeId}: ${error}`
|
|
28510
|
+
);
|
|
28511
|
+
break;
|
|
28512
|
+
}
|
|
28513
|
+
}
|
|
28514
|
+
if (parentNode) {
|
|
28515
|
+
currentNode = parentNode;
|
|
28516
|
+
} else {
|
|
28517
|
+
if (!sparkClient) {
|
|
28518
|
+
console.warn(
|
|
28519
|
+
`Parent node ${currentNode.parentNodeId} not found. Provide a sparkClient to fetch missing parents.`
|
|
28520
|
+
);
|
|
28521
|
+
} else {
|
|
28522
|
+
console.warn(
|
|
28523
|
+
`Parent node ${currentNode.parentNodeId} not found in database. Chain may be incomplete.`
|
|
28524
|
+
);
|
|
28525
|
+
}
|
|
28526
|
+
break;
|
|
28527
|
+
}
|
|
28528
|
+
} else {
|
|
28529
|
+
break;
|
|
28530
|
+
}
|
|
28531
|
+
}
|
|
28532
|
+
for (const chainNode of chain) {
|
|
28533
|
+
let nodeTxHex = bytesToHex13(chainNode.nodeTx);
|
|
28534
|
+
try {
|
|
28535
|
+
const txObj = getTxFromRawTxHex(nodeTxHex);
|
|
28536
|
+
const txid = getTxId(txObj);
|
|
28537
|
+
if (broadcastTxs.get(txid)) {
|
|
28538
|
+
continue;
|
|
28539
|
+
}
|
|
28540
|
+
broadcastTxs.set(txid, true);
|
|
28541
|
+
const isBroadcast = await isTxBroadcast(txid, electrsUrl, network);
|
|
28542
|
+
if (isBroadcast) {
|
|
28543
|
+
continue;
|
|
28544
|
+
} else {
|
|
28545
|
+
}
|
|
28546
|
+
let anchorOutputScriptHex;
|
|
28547
|
+
for (let i = txObj.outputsLength - 1; i >= 0; i--) {
|
|
28548
|
+
const output = txObj.getOutput(i);
|
|
28549
|
+
if (output?.amount === 0n && output.script) {
|
|
28550
|
+
anchorOutputScriptHex = bytesToHex13(output.script);
|
|
28551
|
+
break;
|
|
28552
|
+
}
|
|
28553
|
+
}
|
|
28554
|
+
} catch (parseError) {
|
|
28555
|
+
console.error(
|
|
28556
|
+
`\u274C Error parsing nodeTx for anchor check (node ${chainNode.id}): ${parseError}`
|
|
28557
|
+
);
|
|
28558
|
+
console.log(
|
|
28559
|
+
` This may indicate a corrupted transaction in the TreeNode.`
|
|
28560
|
+
);
|
|
28561
|
+
console.log(` Transaction hex: ${nodeTxHex}`);
|
|
28562
|
+
console.log(
|
|
28563
|
+
` Attempting to continue with original hex, but fee bump may fail.`
|
|
28564
|
+
);
|
|
28565
|
+
}
|
|
28566
|
+
const {
|
|
28567
|
+
feeBumpPsbt: nodeFeeBumpPsbt,
|
|
28568
|
+
usedUtxos,
|
|
28569
|
+
correctedParentTx
|
|
28570
|
+
} = constructFeeBumpTx(nodeTxHex, availableUtxos, feeRate, void 0);
|
|
28571
|
+
const feeBumpTx = btc5.Transaction.fromPSBT(hexToBytes14(nodeFeeBumpPsbt));
|
|
28572
|
+
var feeBumpOut = feeBumpTx.outputsLength === 1 ? feeBumpTx.getOutput(0) : null;
|
|
28573
|
+
var feeBumpOutPubKey = null;
|
|
28574
|
+
for (const usedUtxo of usedUtxos) {
|
|
28575
|
+
if (feeBumpOut && bytesToHex13(feeBumpOut.script) == usedUtxo.script) {
|
|
28576
|
+
feeBumpOutPubKey = usedUtxo.publicKey;
|
|
28577
|
+
}
|
|
28578
|
+
const index = availableUtxos.findIndex(
|
|
28579
|
+
(u) => u.txid === usedUtxo.txid && u.vout === usedUtxo.vout
|
|
28580
|
+
);
|
|
28581
|
+
if (index !== -1) {
|
|
28582
|
+
availableUtxos.splice(index, 1);
|
|
28583
|
+
}
|
|
28584
|
+
}
|
|
28585
|
+
if (feeBumpOut)
|
|
28586
|
+
availableUtxos.unshift({
|
|
28587
|
+
txid: getTxId(feeBumpTx),
|
|
28588
|
+
vout: 0,
|
|
28589
|
+
value: feeBumpOut.amount,
|
|
28590
|
+
script: bytesToHex13(feeBumpOut.script),
|
|
28591
|
+
publicKey: feeBumpOutPubKey
|
|
28592
|
+
});
|
|
28593
|
+
const finalNodeTx = correctedParentTx || nodeTxHex;
|
|
28594
|
+
txPackages.push({ tx: finalNodeTx, feeBumpPsbt: nodeFeeBumpPsbt });
|
|
28595
|
+
if (chainNode.id === node.id) {
|
|
28596
|
+
let refundTxHex = bytesToHex13(chainNode.refundTx);
|
|
28597
|
+
try {
|
|
28598
|
+
const txObj = getTxFromRawTxHex(refundTxHex);
|
|
28599
|
+
let anchorOutputScriptHex;
|
|
28600
|
+
for (let i = txObj.outputsLength - 1; i >= 0; i--) {
|
|
28601
|
+
const output = txObj.getOutput(i);
|
|
28602
|
+
if (output?.amount === 0n && output.script) {
|
|
28603
|
+
anchorOutputScriptHex = bytesToHex13(output.script);
|
|
28604
|
+
break;
|
|
28605
|
+
}
|
|
28606
|
+
}
|
|
28607
|
+
} catch (parseError) {
|
|
28608
|
+
console.error(
|
|
28609
|
+
`\u274C Error parsing refundTx for anchor check (node ${chainNode.id}): ${parseError}`
|
|
28610
|
+
);
|
|
28611
|
+
console.log(
|
|
28612
|
+
` This may indicate a corrupted refund transaction in the TreeNode.`
|
|
28613
|
+
);
|
|
28614
|
+
console.log(` Refund transaction hex: ${refundTxHex}`);
|
|
28615
|
+
console.log(
|
|
28616
|
+
` Attempting to continue with original refund hex, but this transaction may be invalid.`
|
|
28617
|
+
);
|
|
28618
|
+
}
|
|
28619
|
+
const refundFeeBump = constructFeeBumpTx(
|
|
28620
|
+
refundTxHex,
|
|
28621
|
+
availableUtxos,
|
|
28622
|
+
feeRate,
|
|
28623
|
+
void 0
|
|
28624
|
+
);
|
|
28625
|
+
txPackages.push({
|
|
28626
|
+
tx: refundTxHex,
|
|
28627
|
+
feeBumpPsbt: refundFeeBump.feeBumpPsbt
|
|
28628
|
+
});
|
|
28629
|
+
}
|
|
28630
|
+
}
|
|
28631
|
+
result.push({
|
|
28632
|
+
leafId: node.id,
|
|
28633
|
+
txPackages
|
|
28634
|
+
});
|
|
28635
|
+
}
|
|
28636
|
+
return result;
|
|
28637
|
+
}
|
|
28638
|
+
function hash160(data) {
|
|
28639
|
+
const sha256Hash = sha25614(data);
|
|
28640
|
+
return ripemd160(sha256Hash);
|
|
28641
|
+
}
|
|
28642
|
+
function constructFeeBumpTx(txHex, utxos, feeRate, previousFeeBumpTx) {
|
|
28643
|
+
if (!txHex || txHex.length === 0) {
|
|
28644
|
+
throw new Error("Transaction hex string is empty or undefined");
|
|
28645
|
+
}
|
|
28646
|
+
if (utxos.length === 0) {
|
|
28647
|
+
throw new Error("No UTXOs available for fee bump");
|
|
28648
|
+
}
|
|
28649
|
+
let correctedTxHex = txHex;
|
|
28650
|
+
let parentTx;
|
|
28651
|
+
try {
|
|
28652
|
+
parentTx = getTxFromRawTxHex(correctedTxHex);
|
|
28653
|
+
if (!parentTx) {
|
|
28654
|
+
throw new Error("getTxFromRawTxHex returned null/undefined");
|
|
28655
|
+
}
|
|
28656
|
+
} catch (parseError) {
|
|
28657
|
+
throw new Error(
|
|
28658
|
+
`Failed to parse parent transaction hex: ${parseError}. Transaction hex: ${correctedTxHex}`
|
|
28659
|
+
);
|
|
28660
|
+
}
|
|
28661
|
+
try {
|
|
28662
|
+
const outputsLength = parentTx.outputsLength;
|
|
28663
|
+
const inputsLength = parentTx.inputsLength;
|
|
28664
|
+
if (typeof outputsLength !== "number" || outputsLength < 0) {
|
|
28665
|
+
throw new Error(
|
|
28666
|
+
"Invalid transaction: outputsLength is not a valid number"
|
|
28667
|
+
);
|
|
28668
|
+
}
|
|
28669
|
+
if (typeof inputsLength !== "number" || inputsLength < 0) {
|
|
28670
|
+
throw new Error(
|
|
28671
|
+
"Invalid transaction: inputsLength is not a valid number"
|
|
28672
|
+
);
|
|
28673
|
+
}
|
|
28674
|
+
} catch (validationError) {
|
|
28675
|
+
throw new Error(
|
|
28676
|
+
`Transaction validation failed: ${validationError}. This may indicate a corrupted or malformed transaction.`
|
|
28677
|
+
);
|
|
28678
|
+
}
|
|
28679
|
+
const parentTxIdFromLib = parentTx.id;
|
|
28680
|
+
let ephemeralAnchorIndex = -1;
|
|
28681
|
+
for (let i = 0; i < parentTx.outputsLength; i++) {
|
|
28682
|
+
const output = parentTx.getOutput(i);
|
|
28683
|
+
const isEphemeralAnchor = isEphemeralAnchorOutput(
|
|
28684
|
+
output?.script,
|
|
28685
|
+
output?.amount
|
|
28686
|
+
);
|
|
28687
|
+
if (isEphemeralAnchor) {
|
|
28688
|
+
ephemeralAnchorIndex = i;
|
|
28689
|
+
break;
|
|
28690
|
+
}
|
|
28691
|
+
}
|
|
28692
|
+
if (ephemeralAnchorIndex === -1) {
|
|
28693
|
+
throw new Error(
|
|
28694
|
+
"No ephemeral anchor output found in parent transaction. Expected a 0-value output with OP_TRUE script (0x51), malformed OP_TRUE (0x0151), Bitcoin v29 ephemeral anchor script (015152014e0173), or Bitcoin OP_1 + push 2 bytes script (51024e73)."
|
|
28695
|
+
);
|
|
28696
|
+
}
|
|
28697
|
+
const ephemeralAnchorOutput = parentTx.getOutput(ephemeralAnchorIndex);
|
|
28698
|
+
if (!ephemeralAnchorOutput)
|
|
28699
|
+
throw new Error("No ephemeral anchor output found");
|
|
28700
|
+
if (!ephemeralAnchorOutput.script)
|
|
28701
|
+
throw new Error("No script found in ephemeral anchor output");
|
|
28702
|
+
if (utxos.length === 0) {
|
|
28703
|
+
throw new Error("No UTXOs available for fee bump");
|
|
28704
|
+
}
|
|
28705
|
+
const builder = new btc5.Transaction({
|
|
28706
|
+
version: 3,
|
|
28707
|
+
allowUnknown: true,
|
|
28708
|
+
allowLegacyWitnessUtxo: true
|
|
28709
|
+
});
|
|
28710
|
+
let totalValue = 0n;
|
|
28711
|
+
const processedUtxos = [];
|
|
28712
|
+
for (let i = 0; i < utxos.length; i++) {
|
|
28713
|
+
const fundingUtxo = utxos[i];
|
|
28714
|
+
if (!fundingUtxo) {
|
|
28715
|
+
throw new Error(`UTXO at index ${i} is undefined`);
|
|
28716
|
+
}
|
|
28717
|
+
const pubKeyHash = hash160(hexToBytes14(fundingUtxo.publicKey));
|
|
28718
|
+
const scriptToUse = new Uint8Array([0, 20, ...pubKeyHash]);
|
|
28719
|
+
const providedScript = hexToBytes14(fundingUtxo.script);
|
|
28720
|
+
if (bytesToHex13(scriptToUse) !== bytesToHex13(providedScript)) {
|
|
28721
|
+
throw new Error(
|
|
28722
|
+
`\u274C Derived script doesn't match provided script for UTXO ${i + 1}.`
|
|
28723
|
+
);
|
|
28724
|
+
}
|
|
28725
|
+
builder.addInput({
|
|
28726
|
+
txid: fundingUtxo.txid,
|
|
28727
|
+
index: fundingUtxo.vout,
|
|
28728
|
+
sequence: 4294967295,
|
|
28729
|
+
witnessUtxo: {
|
|
28730
|
+
script: scriptToUse,
|
|
28731
|
+
// Always P2WPKH
|
|
28732
|
+
amount: fundingUtxo.value
|
|
28733
|
+
}
|
|
28734
|
+
});
|
|
28735
|
+
totalValue += fundingUtxo.value;
|
|
28736
|
+
processedUtxos.push({
|
|
28737
|
+
utxo: fundingUtxo,
|
|
28738
|
+
p2wpkhScript: scriptToUse
|
|
28739
|
+
});
|
|
28740
|
+
}
|
|
28741
|
+
builder.addInput({
|
|
28742
|
+
txid: parentTxIdFromLib,
|
|
28743
|
+
index: ephemeralAnchorIndex,
|
|
28744
|
+
sequence: 4294967295,
|
|
28745
|
+
witnessUtxo: {
|
|
28746
|
+
script: ephemeralAnchorOutput.script,
|
|
28747
|
+
// Use the original script directly (not P2WSH wrapped)
|
|
28748
|
+
amount: 0n
|
|
28749
|
+
}
|
|
28750
|
+
});
|
|
28751
|
+
const fee = 1500n;
|
|
28752
|
+
const remainingValue = totalValue - fee;
|
|
28753
|
+
if (remainingValue <= 0n) {
|
|
28754
|
+
throw new Error(
|
|
28755
|
+
`Insufficient funds for fee bump. Required fee: ${fee} sats, Available: ${totalValue} sats`
|
|
28756
|
+
);
|
|
28757
|
+
}
|
|
28758
|
+
if (processedUtxos.length === 0) {
|
|
28759
|
+
throw new Error("No processed UTXOs available for change output");
|
|
28760
|
+
}
|
|
28761
|
+
const firstProcessedUtxo = processedUtxos[0];
|
|
28762
|
+
if (!firstProcessedUtxo) {
|
|
28763
|
+
throw new Error("First processed UTXO is undefined");
|
|
28764
|
+
}
|
|
28765
|
+
builder.addOutput({
|
|
28766
|
+
script: firstProcessedUtxo.p2wpkhScript,
|
|
28767
|
+
amount: remainingValue
|
|
28768
|
+
});
|
|
28769
|
+
for (let i = 0; i < processedUtxos.length; i++) {
|
|
28770
|
+
const processed = processedUtxos[i];
|
|
28771
|
+
if (!processed) {
|
|
28772
|
+
throw new Error(`Processed UTXO at index ${i} is undefined`);
|
|
28773
|
+
}
|
|
28774
|
+
try {
|
|
28775
|
+
builder.updateInput(i, {
|
|
28776
|
+
witnessScript: processed.p2wpkhScript
|
|
28777
|
+
});
|
|
28778
|
+
builder.signIdx;
|
|
28779
|
+
} catch (error) {
|
|
28780
|
+
throw new Error(`Failed to handle funding UTXO input ${i + 1}: ${error}`);
|
|
28781
|
+
}
|
|
28782
|
+
}
|
|
28783
|
+
let psbtHex;
|
|
28784
|
+
try {
|
|
28785
|
+
psbtHex = bytesToHex13(builder.toPSBT());
|
|
28786
|
+
} catch (error) {
|
|
28787
|
+
throw new Error(`Failed to extract transaction: ${error}`);
|
|
28788
|
+
}
|
|
28789
|
+
return {
|
|
28790
|
+
feeBumpPsbt: psbtHex,
|
|
28791
|
+
usedUtxos: utxos,
|
|
28792
|
+
correctedParentTx: correctedTxHex !== txHex ? correctedTxHex : void 0
|
|
28793
|
+
};
|
|
28794
|
+
}
|
|
27778
28795
|
export {
|
|
27779
28796
|
AuthenticationError,
|
|
27780
28797
|
ConfigurationError,
|
|
28798
|
+
DEFAULT_FEE_SATS,
|
|
27781
28799
|
ReactNativeSparkSigner as DefaultSparkSigner,
|
|
27782
28800
|
InternalValidationError,
|
|
27783
28801
|
LRC_WALLET_NETWORK,
|
|
@@ -27801,6 +28819,9 @@ export {
|
|
|
27801
28819
|
collectResponses,
|
|
27802
28820
|
computeTaprootKeyNoScript,
|
|
27803
28821
|
computerLagrangeCoefficients,
|
|
28822
|
+
constructFeeBumpTx,
|
|
28823
|
+
constructUnilateralExitFeeBumpPackages,
|
|
28824
|
+
constructUnilateralExitTxs,
|
|
27804
28825
|
createRefundTx,
|
|
27805
28826
|
createSigningCommitment,
|
|
27806
28827
|
createSigningNonce,
|
|
@@ -27819,6 +28840,7 @@ export {
|
|
|
27819
28840
|
getLatestDepositTxId,
|
|
27820
28841
|
getNetwork,
|
|
27821
28842
|
getNetworkFromAddress,
|
|
28843
|
+
getNetworkFromString,
|
|
27822
28844
|
getNextTransactionSequence,
|
|
27823
28845
|
getP2TRAddressFromPkScript,
|
|
27824
28846
|
getP2TRAddressFromPublicKey,
|
|
@@ -27834,7 +28856,10 @@ export {
|
|
|
27834
28856
|
getTxFromRawTxHex,
|
|
27835
28857
|
getTxId,
|
|
27836
28858
|
getTxIdNoReverse,
|
|
28859
|
+
isEphemeralAnchorOutput,
|
|
28860
|
+
isTxBroadcast,
|
|
27837
28861
|
lastKeyWithTarget,
|
|
28862
|
+
maybeApplyFee,
|
|
27838
28863
|
modInverse,
|
|
27839
28864
|
proofOfPossessionMessageHashForDepositAddress,
|
|
27840
28865
|
recoverSecret,
|