@buildonspark/spark-sdk 0.3.7 → 0.3.9
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 +15 -0
- package/dist/bare/index.cjs +8088 -7723
- package/dist/bare/index.d.cts +350 -262
- package/dist/bare/index.d.ts +350 -262
- package/dist/bare/index.js +7968 -7608
- package/dist/{chunk-J2P3KTQP.js → chunk-4YFT7DAE.js} +1 -1
- package/dist/{chunk-XWLR6G5C.js → chunk-JLF6WJ7K.js} +1 -1
- package/dist/{chunk-UYTT3C6H.js → chunk-MFCM6GUD.js} +40 -213
- package/dist/{chunk-KDEVNW7C.js → chunk-O4C4HGQL.js} +3391 -3292
- package/dist/{chunk-SRPKOCG4.js → chunk-S55NZT4P.js} +8 -10
- package/dist/{chunk-P4HYYSMU.js → chunk-WRE2T22S.js} +1 -1
- package/dist/{chunk-IC4IUEOS.js → chunk-YEBEN7XD.js} +309 -38
- package/dist/{client-Bcb7TUIp.d.cts → client-BIqiUNy4.d.cts} +2 -2
- package/dist/{client-D9T58OY8.d.ts → client-BaQf-5gD.d.ts} +2 -2
- package/dist/debug.cjs +8068 -7704
- package/dist/debug.d.cts +25 -18
- package/dist/debug.d.ts +25 -18
- package/dist/debug.js +5 -5
- package/dist/graphql/objects/index.d.cts +3 -3
- package/dist/graphql/objects/index.d.ts +3 -3
- package/dist/index.cjs +6871 -6501
- package/dist/index.d.cts +9 -8
- package/dist/index.d.ts +9 -8
- package/dist/index.js +36 -24
- package/dist/index.node.cjs +7102 -6903
- package/dist/index.node.d.cts +9 -8
- package/dist/index.node.d.ts +9 -8
- package/dist/index.node.js +35 -23
- package/dist/{logging-zkr4UlOi.d.cts → logging-CXhvuqJJ.d.cts} +45 -35
- package/dist/{logging-JIaZZIbR.d.ts → logging-DDeMLsVN.d.ts} +45 -35
- package/dist/native/{chunk-X2QXUON7.js → chunk-AFP5QR4O.js} +11 -8
- package/dist/native/index.react-native.cjs +7054 -6677
- package/dist/native/index.react-native.d.cts +180 -92
- package/dist/native/index.react-native.d.ts +180 -92
- package/dist/native/index.react-native.js +6760 -6393
- package/dist/native/{wasm-GKEDPGTM.js → wasm-D4TI35NF.js} +1 -1
- package/dist/proto/spark.cjs +309 -38
- package/dist/proto/spark.d.cts +1 -1
- package/dist/proto/spark.d.ts +1 -1
- package/dist/proto/spark.js +5 -1
- package/dist/proto/spark_token.d.cts +1 -1
- package/dist/proto/spark_token.d.ts +1 -1
- package/dist/proto/spark_token.js +2 -2
- package/dist/{spark-WA_4wcBr.d.cts → spark-DOpheE8_.d.cts} +69 -7
- package/dist/{spark-WA_4wcBr.d.ts → spark-DOpheE8_.d.ts} +69 -7
- package/dist/{spark-wallet.browser-DC3jdQPW.d.cts → spark-wallet.browser-CbYo8A_U.d.cts} +8 -8
- package/dist/{spark-wallet.browser-BwYkkOBU.d.ts → spark-wallet.browser-Cz8c4kOW.d.ts} +8 -8
- package/dist/{spark-wallet.node-CR_zNxmy.d.cts → spark-wallet.node-4WQgWwB2.d.cts} +9 -31
- package/dist/{spark-wallet.node-C9d2W-Nb.d.ts → spark-wallet.node-CmIvxtcC.d.ts} +9 -31
- package/dist/tests/test-utils.cjs +7341 -7065
- package/dist/tests/test-utils.d.cts +7 -5
- package/dist/tests/test-utils.d.ts +7 -5
- package/dist/tests/test-utils.js +7 -7
- package/dist/{token-transactions-BZoJuvuE.d.ts → token-transactions-Bu023ztN.d.ts} +2 -2
- package/dist/{token-transactions-I_OFIoNH.d.cts → token-transactions-CV8QD3I7.d.cts} +2 -2
- package/dist/types/index.cjs +307 -38
- package/dist/types/index.d.cts +2 -2
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.js +2 -2
- package/dist/{spark-wallet-CE5PYiIb.d.ts → wallet-config-Bmk2eAn8.d.ts} +310 -287
- package/dist/{spark-wallet-BuFrUWeE.d.cts → wallet-config-DQw5llqA.d.cts} +310 -287
- package/package.json +3 -3
- package/src/proto/mock.ts +0 -264
- package/src/proto/spark.ts +433 -46
- package/src/services/config.ts +5 -0
- package/src/services/connection/connection.browser.ts +27 -19
- package/src/services/connection/connection.node.ts +79 -24
- package/src/services/connection/connection.ts +395 -233
- package/src/services/coop-exit.ts +26 -107
- package/src/services/deposit.ts +12 -48
- package/src/services/lightning.ts +30 -4
- package/src/services/signing.ts +187 -37
- package/src/services/transfer.ts +553 -723
- package/src/services/wallet-config.ts +6 -0
- package/src/spark-wallet/proto-descriptors.ts +1 -1
- package/src/spark-wallet/spark-wallet.ts +132 -313
- package/src/spark-wallet/types.ts +2 -2
- package/src/spark_descriptors.pb +0 -0
- package/src/tests/connection.test.ts +537 -0
- package/src/tests/integration/connection.test.ts +39 -0
- package/src/tests/integration/lightning.test.ts +32 -16
- package/src/tests/integration/static_deposit.test.ts +13 -11
- package/src/tests/integration/transfer.test.ts +13 -1
- package/src/tests/isHermeticTest.ts +1 -1
- package/src/tests/utils/test-faucet.ts +53 -20
- package/src/utils/htlc-transactions.ts +224 -0
- package/src/utils/transaction.ts +285 -248
|
@@ -7,15 +7,9 @@ import {
|
|
|
7
7
|
LeafRefundTxSigningJob,
|
|
8
8
|
Transfer,
|
|
9
9
|
} from "../proto/spark.js";
|
|
10
|
-
import {
|
|
11
|
-
getP2TRScriptFromPublicKey,
|
|
12
|
-
getTxFromRawTxBytes,
|
|
13
|
-
} from "../utils/bitcoin.js";
|
|
10
|
+
import { getTxFromRawTxBytes } from "../utils/bitcoin.js";
|
|
14
11
|
import { Network } from "../utils/network.js";
|
|
15
|
-
import {
|
|
16
|
-
getNextTransactionSequence,
|
|
17
|
-
maybeApplyFee,
|
|
18
|
-
} from "../utils/transaction.js";
|
|
12
|
+
import { createConnectorRefundTxs } from "../utils/transaction.js";
|
|
19
13
|
import { WalletConfigService } from "./config.js";
|
|
20
14
|
import { ConnectionManager } from "./connection/connection.js";
|
|
21
15
|
import { SigningService } from "./signing.js";
|
|
@@ -80,93 +74,6 @@ export class CoopExitService extends BaseTransferService {
|
|
|
80
74
|
};
|
|
81
75
|
}
|
|
82
76
|
|
|
83
|
-
private createConnectorRefundTransactions(
|
|
84
|
-
sequence: number,
|
|
85
|
-
directSequence: number,
|
|
86
|
-
cpfpNodeOutPoint: TransactionInput,
|
|
87
|
-
directNodeOutPoint: TransactionInput | undefined,
|
|
88
|
-
connectorOutput: TransactionInput,
|
|
89
|
-
amountSats: bigint,
|
|
90
|
-
receiverPubKey: Uint8Array,
|
|
91
|
-
): {
|
|
92
|
-
cpfpRefundTx: Transaction;
|
|
93
|
-
directRefundTx?: Transaction;
|
|
94
|
-
directFromCpfpRefundTx?: Transaction;
|
|
95
|
-
} {
|
|
96
|
-
// Create CPFP refund transaction
|
|
97
|
-
const cpfpRefundTx = new Transaction();
|
|
98
|
-
if (!cpfpNodeOutPoint.txid || cpfpNodeOutPoint.index === undefined) {
|
|
99
|
-
throw new ValidationError("Invalid CPFP node outpoint", {
|
|
100
|
-
field: "cpfpNodeOutPoint",
|
|
101
|
-
value: { txid: cpfpNodeOutPoint.txid, index: cpfpNodeOutPoint.index },
|
|
102
|
-
expected: "Both txid and index must be defined",
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
cpfpRefundTx.addInput({
|
|
106
|
-
txid: cpfpNodeOutPoint.txid,
|
|
107
|
-
index: cpfpNodeOutPoint.index,
|
|
108
|
-
sequence,
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
cpfpRefundTx.addInput(connectorOutput);
|
|
112
|
-
const receiverScript = getP2TRScriptFromPublicKey(
|
|
113
|
-
receiverPubKey,
|
|
114
|
-
this.config.getNetwork(),
|
|
115
|
-
);
|
|
116
|
-
|
|
117
|
-
cpfpRefundTx.addOutput({
|
|
118
|
-
script: receiverScript,
|
|
119
|
-
amount: amountSats,
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
// Create direct refund transaction
|
|
123
|
-
let directRefundTx: Transaction | undefined;
|
|
124
|
-
let directFromCpfpRefundTx: Transaction | undefined;
|
|
125
|
-
if (directNodeOutPoint) {
|
|
126
|
-
if (!directNodeOutPoint.txid || directNodeOutPoint.index === undefined) {
|
|
127
|
-
throw new ValidationError("Invalid direct node outpoint", {
|
|
128
|
-
field: "directNodeOutPoint",
|
|
129
|
-
value: {
|
|
130
|
-
txid: directNodeOutPoint.txid,
|
|
131
|
-
index: directNodeOutPoint.index,
|
|
132
|
-
},
|
|
133
|
-
expected: "Both txid and index must be defined",
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
directRefundTx = new Transaction();
|
|
137
|
-
directRefundTx.addInput({
|
|
138
|
-
txid: directNodeOutPoint.txid,
|
|
139
|
-
index: directNodeOutPoint.index,
|
|
140
|
-
sequence: directSequence,
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
directRefundTx.addInput(connectorOutput);
|
|
144
|
-
directRefundTx.addOutput({
|
|
145
|
-
script: receiverScript,
|
|
146
|
-
amount: maybeApplyFee(amountSats),
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
directFromCpfpRefundTx = new Transaction();
|
|
150
|
-
directFromCpfpRefundTx.addInput({
|
|
151
|
-
txid: cpfpNodeOutPoint.txid,
|
|
152
|
-
index: cpfpNodeOutPoint.index,
|
|
153
|
-
sequence: directSequence,
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
directFromCpfpRefundTx.addInput(connectorOutput);
|
|
157
|
-
directFromCpfpRefundTx.addOutput({
|
|
158
|
-
script: receiverScript,
|
|
159
|
-
amount: maybeApplyFee(amountSats),
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return {
|
|
164
|
-
cpfpRefundTx,
|
|
165
|
-
directRefundTx,
|
|
166
|
-
directFromCpfpRefundTx,
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
|
|
170
77
|
private async signCoopExitRefunds(
|
|
171
78
|
leaves: LeafKeyTweak[],
|
|
172
79
|
exitTxId: Uint8Array,
|
|
@@ -213,18 +120,31 @@ export class CoopExitService extends BaseTransferService {
|
|
|
213
120
|
expected: "Valid connector output",
|
|
214
121
|
});
|
|
215
122
|
}
|
|
123
|
+
|
|
124
|
+
const nodeTx = getTxFromRawTxBytes(leaf.leaf.nodeTx);
|
|
125
|
+
|
|
126
|
+
let directNodeTx: Transaction | undefined;
|
|
127
|
+
if (leaf.leaf.directTx.length > 0) {
|
|
128
|
+
directNodeTx = getTxFromRawTxBytes(leaf.leaf.directTx);
|
|
129
|
+
}
|
|
130
|
+
|
|
216
131
|
const currentRefundTx = getTxFromRawTxBytes(leaf.leaf.refundTx);
|
|
132
|
+
if (!currentRefundTx) {
|
|
133
|
+
throw new ValidationError("Invalid refund transaction", {
|
|
134
|
+
field: "currentRefundTx",
|
|
135
|
+
value: currentRefundTx,
|
|
136
|
+
expected: "Non-null refund transaction",
|
|
137
|
+
});
|
|
138
|
+
}
|
|
217
139
|
|
|
218
|
-
const
|
|
219
|
-
if (!
|
|
140
|
+
const currentSequence = currentRefundTx.getInput(0).sequence;
|
|
141
|
+
if (!currentSequence) {
|
|
220
142
|
throw new ValidationError("Invalid refund transaction", {
|
|
221
143
|
field: "sequence",
|
|
222
144
|
value: currentRefundTx.getInput(0),
|
|
223
145
|
expected: "Non-null sequence",
|
|
224
146
|
});
|
|
225
147
|
}
|
|
226
|
-
const { nextSequence, nextDirectSequence } =
|
|
227
|
-
getNextTransactionSequence(sequence);
|
|
228
148
|
|
|
229
149
|
let currentDirectRefundTx: Transaction | undefined;
|
|
230
150
|
if (leaf.leaf.directRefundTx.length > 0) {
|
|
@@ -232,15 +152,14 @@ export class CoopExitService extends BaseTransferService {
|
|
|
232
152
|
}
|
|
233
153
|
|
|
234
154
|
const { cpfpRefundTx, directRefundTx, directFromCpfpRefundTx } =
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
currentDirectRefundTx?.getInput(0),
|
|
155
|
+
createConnectorRefundTxs({
|
|
156
|
+
nodeTx,
|
|
157
|
+
directNodeTx,
|
|
158
|
+
sequence: currentSequence,
|
|
240
159
|
connectorOutput,
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
);
|
|
160
|
+
receivingPubkey: receiverPubKey,
|
|
161
|
+
network: this.config.getNetwork(),
|
|
162
|
+
});
|
|
244
163
|
|
|
245
164
|
const signingNonceCommitment =
|
|
246
165
|
await this.config.signer.getRandomSigningCommitment();
|
package/src/services/deposit.ts
CHANGED
|
@@ -12,15 +12,13 @@ import {
|
|
|
12
12
|
StartDepositTreeCreationResponse,
|
|
13
13
|
} from "../proto/spark.js";
|
|
14
14
|
import { KeyDerivation } from "../signer/types.js";
|
|
15
|
-
import { getSigHashFromTx
|
|
15
|
+
import { getSigHashFromTx } from "../utils/bitcoin.js";
|
|
16
16
|
import { subtractPublicKeys } from "../utils/keys.js";
|
|
17
17
|
import { getNetwork } from "../utils/network.js";
|
|
18
18
|
import { proofOfPossessionMessageHashForDepositAddress } from "../utils/proof.js";
|
|
19
19
|
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
INITIAL_DIRECT_SEQUENCE,
|
|
23
|
-
INITIAL_SEQUENCE,
|
|
20
|
+
createInitialTimelockRefundTxs,
|
|
21
|
+
createRootNodeTx,
|
|
24
22
|
} from "../utils/transaction.js";
|
|
25
23
|
import { WalletConfigService } from "./config.js";
|
|
26
24
|
import { ConnectionManager } from "./connection/connection.js";
|
|
@@ -250,28 +248,10 @@ export class DepositService {
|
|
|
250
248
|
expected: "Valid output index",
|
|
251
249
|
});
|
|
252
250
|
}
|
|
253
|
-
const script = output.script;
|
|
254
|
-
const amount = output.amount;
|
|
255
|
-
if (!script || !amount) {
|
|
256
|
-
throw new ValidationError("No script or amount found in deposit tx", {
|
|
257
|
-
field: "output",
|
|
258
|
-
value: output,
|
|
259
|
-
expected: "Output with script and amount",
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
251
|
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
};
|
|
267
|
-
const depositTxOut = {
|
|
268
|
-
script,
|
|
269
|
-
amount,
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
const [cpfpRootTx, directRootTx] = createRootTx(
|
|
273
|
-
depositOutPoint,
|
|
274
|
-
depositTxOut,
|
|
252
|
+
const { nodeTx: cpfpRootTx, directNodeTx: directRootTx } = createRootNodeTx(
|
|
253
|
+
depositTx,
|
|
254
|
+
vout,
|
|
275
255
|
);
|
|
276
256
|
|
|
277
257
|
// Create nonce commitments for root transactions
|
|
@@ -287,14 +267,10 @@ export class DepositService {
|
|
|
287
267
|
const signingPubKey =
|
|
288
268
|
await this.config.signer.getPublicKeyFromDerivation(keyDerivation);
|
|
289
269
|
|
|
290
|
-
// Create refund transactions (CPFP and direct)
|
|
291
270
|
const { cpfpRefundTx, directRefundTx, directFromCpfpRefundTx } =
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
input: { txid: hexToBytes(getTxId(cpfpRootTx)), index: 0 },
|
|
296
|
-
directInput: { txid: hexToBytes(getTxId(directRootTx)), index: 0 },
|
|
297
|
-
amountSats: amount,
|
|
271
|
+
createInitialTimelockRefundTxs({
|
|
272
|
+
nodeTx: cpfpRootTx,
|
|
273
|
+
directNodeTx: directRootTx,
|
|
298
274
|
receivingPubkey: signingPubKey,
|
|
299
275
|
network: this.config.getNetwork(),
|
|
300
276
|
});
|
|
@@ -675,16 +651,7 @@ export class DepositService {
|
|
|
675
651
|
});
|
|
676
652
|
}
|
|
677
653
|
|
|
678
|
-
const
|
|
679
|
-
txid: hexToBytes(getTxId(depositTx)),
|
|
680
|
-
index: vout,
|
|
681
|
-
};
|
|
682
|
-
const depositTxOut = {
|
|
683
|
-
script,
|
|
684
|
-
amount,
|
|
685
|
-
};
|
|
686
|
-
|
|
687
|
-
const [cpfpRootTx, _] = createRootTx(depositOutPoint, depositTxOut);
|
|
654
|
+
const { nodeTx: cpfpRootTx } = createRootNodeTx(depositTx, vout);
|
|
688
655
|
|
|
689
656
|
// Create nonce commitments for root transactions
|
|
690
657
|
const cpfpRootNonceCommitment =
|
|
@@ -696,11 +663,8 @@ export class DepositService {
|
|
|
696
663
|
const signingPubKey =
|
|
697
664
|
await this.config.signer.getPublicKeyFromDerivation(keyDerivation);
|
|
698
665
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
sequence: INITIAL_SEQUENCE,
|
|
702
|
-
input: { txid: hexToBytes(getTxId(cpfpRootTx)), index: 0 },
|
|
703
|
-
amountSats: amount,
|
|
666
|
+
const { cpfpRefundTx } = createInitialTimelockRefundTxs({
|
|
667
|
+
nodeTx: cpfpRootTx,
|
|
704
668
|
receivingPubkey: signingPubKey,
|
|
705
669
|
network: this.config.getNetwork(),
|
|
706
670
|
});
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
ProvidePreimageResponse,
|
|
16
16
|
QueryUserSignedRefundsResponse,
|
|
17
17
|
Transfer,
|
|
18
|
+
StartTransferRequest,
|
|
18
19
|
UserSignedRefund,
|
|
19
20
|
} from "../proto/spark.js";
|
|
20
21
|
import { getTxFromRawTxBytes } from "../utils/bitcoin.js";
|
|
@@ -51,6 +52,9 @@ export type SwapNodesForPreimageParams = {
|
|
|
51
52
|
isInboundPayment: boolean;
|
|
52
53
|
feeSats?: number;
|
|
53
54
|
amountSatsToSend?: number;
|
|
55
|
+
startTransferRequest?: StartTransferRequest;
|
|
56
|
+
expiryTime?: Date;
|
|
57
|
+
transferID?: string;
|
|
54
58
|
};
|
|
55
59
|
|
|
56
60
|
export class LightningService {
|
|
@@ -175,6 +179,19 @@ export class LightningService {
|
|
|
175
179
|
return invoice;
|
|
176
180
|
}
|
|
177
181
|
|
|
182
|
+
/**
|
|
183
|
+
* Swap nodes for preimage
|
|
184
|
+
* @param leaves - The leaves to swap for preimage
|
|
185
|
+
* @param receiverIdentityPubkey - The receiver identity public key
|
|
186
|
+
* @param paymentHash - The payment hash
|
|
187
|
+
* @param invoiceString - The invoice string
|
|
188
|
+
* @param isInboundPayment - Whether the payment is inbound
|
|
189
|
+
* @param feeSats - The fee in sats
|
|
190
|
+
* @param amountSatsToSend - The amount in sats to send
|
|
191
|
+
* @param expiryTime - The expiry time
|
|
192
|
+
* @param startTransferRequest - The start transfer request, do not populate if is inbound payment
|
|
193
|
+
* @param transferID - The transfer ID, do not populate if is inbound payment
|
|
194
|
+
*/
|
|
178
195
|
async swapNodesForPreimage({
|
|
179
196
|
leaves,
|
|
180
197
|
receiverIdentityPubkey,
|
|
@@ -183,6 +200,9 @@ export class LightningService {
|
|
|
183
200
|
isInboundPayment,
|
|
184
201
|
feeSats = 0,
|
|
185
202
|
amountSatsToSend,
|
|
203
|
+
expiryTime,
|
|
204
|
+
startTransferRequest,
|
|
205
|
+
transferID,
|
|
186
206
|
}: SwapNodesForPreimageParams): Promise<InitiatePreimageSwapResponse> {
|
|
187
207
|
const sparkClient = await this.connectionManager.createSparkClient(
|
|
188
208
|
this.config.getCoordinatorAddress(),
|
|
@@ -222,7 +242,7 @@ export class LightningService {
|
|
|
222
242
|
signingCommitments.signingCommitments.slice(2 * leaves.length),
|
|
223
243
|
);
|
|
224
244
|
|
|
225
|
-
const transferId = uuidv7();
|
|
245
|
+
const transferId = transferID ? transferID : uuidv7();
|
|
226
246
|
let bolt11String = "";
|
|
227
247
|
let amountSats: number = 0;
|
|
228
248
|
if (invoiceString) {
|
|
@@ -267,6 +287,7 @@ export class LightningService {
|
|
|
267
287
|
: InitiatePreimageSwapRequest_Reason.REASON_SEND;
|
|
268
288
|
|
|
269
289
|
let response: InitiatePreimageSwapResponse;
|
|
290
|
+
// TODO(LIG-8126): Remove transfer inputs once SDK upgrade is complete
|
|
270
291
|
try {
|
|
271
292
|
response = await sparkClient.initiate_preimage_swap_v2({
|
|
272
293
|
paymentHash,
|
|
@@ -282,13 +303,18 @@ export class LightningService {
|
|
|
282
303
|
ownerIdentityPublicKey:
|
|
283
304
|
await this.config.signer.getIdentityPublicKey(),
|
|
284
305
|
leavesToSend: cpfpLeafSigningJobs,
|
|
285
|
-
directLeavesToSend:
|
|
286
|
-
|
|
306
|
+
directLeavesToSend: startTransferRequest
|
|
307
|
+
? undefined
|
|
308
|
+
: directLeafSigningJobs,
|
|
309
|
+
directFromCpfpLeavesToSend: startTransferRequest
|
|
310
|
+
? undefined
|
|
311
|
+
: directFromCpfpLeafSigningJobs,
|
|
287
312
|
receiverIdentityPublicKey: receiverIdentityPubkey,
|
|
288
|
-
expiryTime
|
|
313
|
+
expiryTime,
|
|
289
314
|
},
|
|
290
315
|
receiverIdentityPublicKey: receiverIdentityPubkey,
|
|
291
316
|
feeSats,
|
|
317
|
+
transferRequest: startTransferRequest,
|
|
292
318
|
});
|
|
293
319
|
} catch (error) {
|
|
294
320
|
throw new NetworkError(
|
package/src/services/signing.ts
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
import { hexToBytes } from "@noble/curves/utils";
|
|
2
1
|
import { Transaction } from "@scure/btc-signer";
|
|
3
|
-
import { TransactionInput } from "@scure/btc-signer/psbt";
|
|
4
2
|
import { ValidationError } from "../errors/types.js";
|
|
5
3
|
import { SigningCommitment } from "../proto/common.js";
|
|
6
4
|
import {
|
|
7
5
|
RequestedSigningCommitments,
|
|
8
6
|
UserSignedTxSigningJob,
|
|
9
7
|
} from "../proto/spark.js";
|
|
8
|
+
import { getSigHashFromTx, getTxFromRawTxBytes } from "../utils/bitcoin.js";
|
|
9
|
+
import { createRefundTxsForLightning } from "../utils/htlc-transactions.js";
|
|
10
|
+
import { getNetwork } from "../utils/network.js";
|
|
10
11
|
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
} from "../utils/bitcoin.js";
|
|
15
|
-
import {
|
|
16
|
-
createRefundTxs,
|
|
17
|
-
getNextTransactionSequence,
|
|
12
|
+
createDecrementedTimelockRefundTxs,
|
|
13
|
+
getCurrentTimelock,
|
|
14
|
+
getNextHTLCTransactionSequence,
|
|
18
15
|
} from "../utils/transaction.js";
|
|
19
16
|
import { WalletConfigService } from "./config.js";
|
|
20
|
-
import type {
|
|
17
|
+
import type {
|
|
18
|
+
LeafKeyTweak,
|
|
19
|
+
SigningJobType,
|
|
20
|
+
SigningJobWithOptionalNonce,
|
|
21
|
+
} from "./transfer.js";
|
|
21
22
|
|
|
22
23
|
export class SigningService {
|
|
23
24
|
private readonly config: WalletConfigService;
|
|
@@ -102,10 +103,132 @@ export class SigningService {
|
|
|
102
103
|
}
|
|
103
104
|
|
|
104
105
|
const nodeTx = getTxFromRawTxBytes(leaf.leaf.nodeTx);
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
|
|
107
|
+
const currRefundTx = getTxFromRawTxBytes(leaf.leaf.refundTx);
|
|
108
|
+
|
|
109
|
+
const amountSats = currRefundTx.getOutput(0).amount;
|
|
110
|
+
if (amountSats === undefined) {
|
|
111
|
+
throw new ValidationError("Invalid refund transaction", {
|
|
112
|
+
field: "amount",
|
|
113
|
+
value: currRefundTx.getOutput(0),
|
|
114
|
+
expected: "Non-null amount",
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let directNodeTx: Transaction | undefined;
|
|
119
|
+
if (leaf.leaf.directTx.length > 0) {
|
|
120
|
+
directNodeTx = getTxFromRawTxBytes(leaf.leaf.directTx);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const currentSequence = currRefundTx.getInput(0).sequence;
|
|
124
|
+
if (!currentSequence) {
|
|
125
|
+
throw new ValidationError("Invalid refund transaction", {
|
|
126
|
+
field: "sequence",
|
|
127
|
+
value: currRefundTx.getInput(0),
|
|
128
|
+
expected: "Non-null sequence",
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const { cpfpRefundTx, directRefundTx, directFromCpfpRefundTx } =
|
|
133
|
+
createDecrementedTimelockRefundTxs({
|
|
134
|
+
nodeTx: nodeTx,
|
|
135
|
+
directNodeTx: directNodeTx,
|
|
136
|
+
sequence: currentSequence,
|
|
137
|
+
receivingPubkey: receiverIdentityPubkey,
|
|
138
|
+
network: this.config.getNetwork(),
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const refundSighash = getSigHashFromTx(
|
|
142
|
+
cpfpRefundTx,
|
|
143
|
+
0,
|
|
144
|
+
nodeTx.getOutput(0),
|
|
145
|
+
);
|
|
146
|
+
const signingJobs = await this.signRefundsInternal(
|
|
147
|
+
cpfpRefundTx,
|
|
148
|
+
refundSighash,
|
|
149
|
+
leaf,
|
|
150
|
+
cpfpSigningCommitments[i]?.signingNonceCommitments,
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
cpfpLeafSigningJobs.push(...signingJobs);
|
|
154
|
+
|
|
155
|
+
const isZeroNode = getCurrentTimelock(nodeTx.getInput(0).sequence);
|
|
156
|
+
if (directRefundTx && !isZeroNode) {
|
|
157
|
+
if (!directNodeTx) {
|
|
158
|
+
throw new ValidationError(
|
|
159
|
+
"Direct node transaction undefined while direct refund transaction is defined",
|
|
160
|
+
{
|
|
161
|
+
field: "directNodeTx",
|
|
162
|
+
value: directNodeTx,
|
|
163
|
+
expected: "Non-null direct node transaction",
|
|
164
|
+
},
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
const refundSighash = getSigHashFromTx(
|
|
168
|
+
directRefundTx,
|
|
169
|
+
0,
|
|
170
|
+
directNodeTx.getOutput(0),
|
|
171
|
+
);
|
|
172
|
+
const signingJobs = await this.signRefundsInternal(
|
|
173
|
+
directRefundTx,
|
|
174
|
+
refundSighash,
|
|
175
|
+
leaf,
|
|
176
|
+
directSigningCommitments[i]?.signingNonceCommitments,
|
|
177
|
+
);
|
|
178
|
+
directLeafSigningJobs.push(...signingJobs);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (directFromCpfpRefundTx) {
|
|
182
|
+
const refundSighash = getSigHashFromTx(
|
|
183
|
+
directFromCpfpRefundTx,
|
|
184
|
+
0,
|
|
185
|
+
nodeTx.getOutput(0),
|
|
186
|
+
);
|
|
187
|
+
const signingJobs = await this.signRefundsInternal(
|
|
188
|
+
directFromCpfpRefundTx,
|
|
189
|
+
refundSighash,
|
|
190
|
+
leaf,
|
|
191
|
+
directFromCpfpSigningCommitments[i]?.signingNonceCommitments,
|
|
192
|
+
);
|
|
193
|
+
directFromCpfpLeafSigningJobs.push(...signingJobs);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
cpfpLeafSigningJobs,
|
|
199
|
+
directLeafSigningJobs,
|
|
200
|
+
directFromCpfpLeafSigningJobs,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async signRefundsForLightning(
|
|
205
|
+
leaves: LeafKeyTweak[],
|
|
206
|
+
receiverIdentityPubkey: Uint8Array,
|
|
207
|
+
cpfpSigningCommitments: RequestedSigningCommitments[],
|
|
208
|
+
directSigningCommitments: RequestedSigningCommitments[],
|
|
209
|
+
directFromCpfpSigningCommitments: RequestedSigningCommitments[],
|
|
210
|
+
hash: Uint8Array,
|
|
211
|
+
): Promise<{
|
|
212
|
+
cpfpLeafSigningJobs: UserSignedTxSigningJob[];
|
|
213
|
+
directLeafSigningJobs: UserSignedTxSigningJob[];
|
|
214
|
+
directFromCpfpLeafSigningJobs: UserSignedTxSigningJob[];
|
|
215
|
+
}> {
|
|
216
|
+
const network = getNetwork(this.config.getNetwork());
|
|
217
|
+
const cpfpLeafSigningJobs: UserSignedTxSigningJob[] = [];
|
|
218
|
+
const directLeafSigningJobs: UserSignedTxSigningJob[] = [];
|
|
219
|
+
const directFromCpfpLeafSigningJobs: UserSignedTxSigningJob[] = [];
|
|
220
|
+
|
|
221
|
+
for (let i = 0; i < leaves.length; i++) {
|
|
222
|
+
const leaf = leaves[i];
|
|
223
|
+
if (!leaf?.leaf) {
|
|
224
|
+
throw new ValidationError("Leaf not found in signRefunds", {
|
|
225
|
+
field: "leaf",
|
|
226
|
+
value: leaf,
|
|
227
|
+
expected: "Non-null leaf",
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const nodeTx = getTxFromRawTxBytes(leaf.leaf.nodeTx);
|
|
109
232
|
|
|
110
233
|
const currRefundTx = getTxFromRawTxBytes(leaf.leaf.refundTx);
|
|
111
234
|
|
|
@@ -117,8 +240,6 @@ export class SigningService {
|
|
|
117
240
|
expected: "Non-null sequence",
|
|
118
241
|
});
|
|
119
242
|
}
|
|
120
|
-
const { nextSequence, nextDirectSequence } =
|
|
121
|
-
getNextTransactionSequence(sequence);
|
|
122
243
|
|
|
123
244
|
const amountSats = currRefundTx.getOutput(0).amount;
|
|
124
245
|
if (amountSats === undefined) {
|
|
@@ -129,25 +250,27 @@ export class SigningService {
|
|
|
129
250
|
});
|
|
130
251
|
}
|
|
131
252
|
|
|
253
|
+
const { nextSequence, nextDirectSequence } =
|
|
254
|
+
getNextHTLCTransactionSequence(sequence);
|
|
255
|
+
|
|
132
256
|
let directNodeTx: Transaction | undefined;
|
|
133
|
-
let directNodeOutPoint: TransactionInput | undefined;
|
|
134
257
|
if (leaf.leaf.directTx.length > 0) {
|
|
135
258
|
directNodeTx = getTxFromRawTxBytes(leaf.leaf.directTx);
|
|
136
|
-
directNodeOutPoint = {
|
|
137
|
-
txid: hexToBytes(getTxId(directNodeTx)),
|
|
138
|
-
index: 0,
|
|
139
|
-
};
|
|
140
259
|
}
|
|
141
260
|
|
|
261
|
+
const identityPublicKey = await this.config.signer.getIdentityPublicKey();
|
|
262
|
+
|
|
142
263
|
const { cpfpRefundTx, directRefundTx, directFromCpfpRefundTx } =
|
|
143
|
-
|
|
264
|
+
createRefundTxsForLightning({
|
|
265
|
+
nodeTx: nodeTx,
|
|
266
|
+
directNodeTx: directNodeTx,
|
|
267
|
+
vout: 0,
|
|
268
|
+
network,
|
|
144
269
|
sequence: nextSequence,
|
|
145
270
|
directSequence: nextDirectSequence,
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
receivingPubkey: receiverIdentityPubkey,
|
|
150
|
-
network: this.config.getNetwork(),
|
|
271
|
+
hash,
|
|
272
|
+
hashLockDestinationPubkey: receiverIdentityPubkey,
|
|
273
|
+
sequenceLockDestinationPubkey: identityPublicKey,
|
|
151
274
|
});
|
|
152
275
|
|
|
153
276
|
const refundSighash = getSigHashFromTx(
|
|
@@ -190,16 +313,6 @@ export class SigningService {
|
|
|
190
313
|
}
|
|
191
314
|
|
|
192
315
|
if (directFromCpfpRefundTx) {
|
|
193
|
-
if (!directNodeTx) {
|
|
194
|
-
throw new ValidationError(
|
|
195
|
-
"Direct node transaction undefined while direct from CPFP refund transaction is defined",
|
|
196
|
-
{
|
|
197
|
-
field: "directNodeTx",
|
|
198
|
-
value: directNodeTx,
|
|
199
|
-
expected: "Non-null direct node transaction",
|
|
200
|
-
},
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
316
|
const refundSighash = getSigHashFromTx(
|
|
204
317
|
directFromCpfpRefundTx,
|
|
205
318
|
0,
|
|
@@ -221,4 +334,41 @@ export class SigningService {
|
|
|
221
334
|
directFromCpfpLeafSigningJobs,
|
|
222
335
|
};
|
|
223
336
|
}
|
|
337
|
+
|
|
338
|
+
async signSigningJobs(
|
|
339
|
+
signingJobs: (SigningJobWithOptionalNonce & RequestedSigningCommitments)[],
|
|
340
|
+
): Promise<Map<SigningJobType, UserSignedTxSigningJob>> {
|
|
341
|
+
const userSignedTxSigningJobs: Map<SigningJobType, UserSignedTxSigningJob> =
|
|
342
|
+
new Map();
|
|
343
|
+
|
|
344
|
+
for (const signingJob of signingJobs) {
|
|
345
|
+
const rawTx = getTxFromRawTxBytes(signingJob.rawTx);
|
|
346
|
+
const txOut = signingJob.parentTxOut;
|
|
347
|
+
const rawTxSighash = getSigHashFromTx(rawTx, 0, txOut);
|
|
348
|
+
const userSignature = await this.config.signer.signFrost({
|
|
349
|
+
message: rawTxSighash,
|
|
350
|
+
keyDerivation: signingJob.keyDerivation,
|
|
351
|
+
publicKey: signingJob.signingPublicKey,
|
|
352
|
+
verifyingKey: signingJob.verifyingKey,
|
|
353
|
+
selfCommitment: signingJob.signingNonceCommitment,
|
|
354
|
+
statechainCommitments: signingJob.signingNonceCommitments,
|
|
355
|
+
adaptorPubKey: new Uint8Array(),
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
const userSignedTxSigningJob: UserSignedTxSigningJob = {
|
|
359
|
+
leafId: signingJob.leafId,
|
|
360
|
+
signingPublicKey: signingJob.signingPublicKey,
|
|
361
|
+
rawTx: rawTx.toBytes(),
|
|
362
|
+
signingNonceCommitment: signingJob.signingNonceCommitment.commitment,
|
|
363
|
+
signingCommitments: {
|
|
364
|
+
signingCommitments: signingJob.signingNonceCommitments,
|
|
365
|
+
},
|
|
366
|
+
userSignature,
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
userSignedTxSigningJobs.set(signingJob.type, userSignedTxSigningJob);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return userSignedTxSigningJobs;
|
|
373
|
+
}
|
|
224
374
|
}
|