@buildonspark/spark-sdk 0.0.9 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/services/config.d.ts +20 -16
- package/dist/services/config.js +26 -18
- package/dist/services/config.js.map +1 -1
- package/dist/services/connection.js +1 -1
- package/dist/services/connection.js.map +1 -1
- package/dist/services/deposit.js +2 -2
- package/dist/services/deposit.js.map +1 -1
- package/dist/services/lightning.js +4 -4
- package/dist/services/lightning.js.map +1 -1
- package/dist/services/token-transactions.js +8 -7
- package/dist/services/token-transactions.js.map +1 -1
- package/dist/services/transfer.js +12 -10
- package/dist/services/transfer.js.map +1 -1
- package/dist/spark-sdk.d.ts +2 -3
- package/dist/spark-sdk.js +2 -7
- package/dist/spark-sdk.js.map +1 -1
- package/dist/tests/coop-exit.test.js +8 -5
- package/dist/tests/coop-exit.test.js.map +1 -1
- package/dist/tests/deposit.test.js +8 -10
- package/dist/tests/deposit.test.js.map +1 -1
- package/dist/tests/jest.setup.d.ts +1 -0
- package/dist/tests/jest.setup.js +8 -0
- package/dist/tests/jest.setup.js.map +1 -0
- package/dist/tests/keys.test.js +0 -1
- package/dist/tests/keys.test.js.map +1 -1
- package/dist/tests/lightning.test.js +10 -7
- package/dist/tests/lightning.test.js.map +1 -1
- package/dist/tests/swap.test.js +8 -5
- package/dist/tests/swap.test.js.map +1 -1
- package/dist/tests/test-util.d.ts +8 -44
- package/dist/tests/test-util.js +10 -7
- package/dist/tests/test-util.js.map +1 -1
- package/dist/tests/transfer.test.js +30 -19
- package/dist/tests/transfer.test.js.map +1 -1
- package/package.json +2 -2
- package/dist/LightningSendRequest-CNJFhLVc.d.cts +0 -374
- package/dist/LightningSendRequest-CNJFhLVc.d.ts +0 -374
- package/dist/auto-bind.d.ts +0 -7
- package/dist/auto-bind.js +0 -41
- package/dist/auto-bind.js.map +0 -1
- package/dist/chunk-5SAJ52IV.js +0 -10309
- package/dist/chunk-COXVABEU.js +0 -1524
- package/dist/chunk-F4JW24C4.js +0 -78
- package/dist/chunk-H4A2WXR3.js +0 -331
- package/dist/chunk-HTNOFUHX.js +0 -1547
- package/dist/chunk-JQFHUW4I.js +0 -21
- package/dist/chunk-K3Y7DVLD.js +0 -19
- package/dist/chunk-NDKNVHGP.js +0 -127
- package/dist/chunk-PMVJGQCP.js +0 -627
- package/dist/chunk-QX3ZJH2S.js +0 -527
- package/dist/chunk-SL2YOBVM.js +0 -127
- package/dist/chunk-SWCOMKD6.js +0 -333
- package/dist/chunk-SWFFNBSR.js +0 -1244
- package/dist/chunk-WLK5POBV.js +0 -527
- package/dist/chunk-WZ74TD7N.js +0 -660
- package/dist/chunk-WZYVI3M3.js +0 -1244
- package/dist/chunk-ZGU3XW7W.js +0 -78
- package/dist/connection-BgWj7Hnd.d.cts +0 -77
- package/dist/connection-BgbVJtzh.d.ts +0 -77
- package/dist/connection-DX-9yFl8.d.ts +0 -77
- package/dist/connection-hITj9Mgk.d.cts +0 -77
- package/dist/graphql/objects/index.cjs +0 -626
- package/dist/graphql/objects/index.d.cts +0 -140
- package/dist/index.cjs +0 -17202
- package/dist/index.d.cts +0 -413
- package/dist/index.d.ts +0 -413
- package/dist/index.js +0 -3390
- package/dist/proto/spark.cjs +0 -10451
- package/dist/proto/spark.d.cts +0 -3
- package/dist/services/index.cjs +0 -12503
- package/dist/services/index.d.cts +0 -23
- package/dist/services/index.d.ts +0 -23
- package/dist/services/index.js +0 -17
- package/dist/signer/signer.cjs +0 -894
- package/dist/signer/signer.d.cts +0 -5
- package/dist/signer-BaC_ZP1g.d.ts +0 -138
- package/dist/signer-C6h1OnSQ.d.cts +0 -138
- package/dist/signer-CO4owhHI.d.ts +0 -154
- package/dist/signer-DDkpXvNZ.d.cts +0 -154
- package/dist/spark-BUTdOtMz.d.cts +0 -1170
- package/dist/spark-BUTdOtMz.d.ts +0 -1170
- package/dist/tests/test-util.cjs +0 -12269
- package/dist/tests/test-util.d.cts +0 -90
- package/dist/utils/index.cjs +0 -1825
- package/dist/utils/index.d.cts +0 -280
package/dist/chunk-WLK5POBV.js
DELETED
|
@@ -1,527 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
validateResponses
|
|
3
|
-
} from "./chunk-K3Y7DVLD.js";
|
|
4
|
-
import {
|
|
5
|
-
bigIntToPrivateKey,
|
|
6
|
-
recoverSecret
|
|
7
|
-
} from "./chunk-HTNOFUHX.js";
|
|
8
|
-
|
|
9
|
-
// src/services/token-transactions.ts
|
|
10
|
-
import {
|
|
11
|
-
bytesToHex as bytesToHex2,
|
|
12
|
-
bytesToNumberBE as bytesToNumberBE2,
|
|
13
|
-
numberToBytesBE
|
|
14
|
-
} from "@noble/curves/abstract/utils";
|
|
15
|
-
import { secp256k1 as secp256k12 } from "@noble/curves/secp256k1";
|
|
16
|
-
|
|
17
|
-
// src/utils/token-hashing.ts
|
|
18
|
-
import { sha256 } from "@scure/btc-signer/utils";
|
|
19
|
-
function hashTokenTransaction(tokenTransaction, partialHash = false) {
|
|
20
|
-
if (!tokenTransaction) {
|
|
21
|
-
throw new Error("token transaction cannot be nil");
|
|
22
|
-
}
|
|
23
|
-
let allHashes = [];
|
|
24
|
-
if (tokenTransaction.tokenInput?.$case === "transferInput") {
|
|
25
|
-
for (const leaf of tokenTransaction.tokenInput.transferInput.leavesToSpend || []) {
|
|
26
|
-
const hashObj = sha256.create();
|
|
27
|
-
if (leaf.prevTokenTransactionHash) {
|
|
28
|
-
hashObj.update(leaf.prevTokenTransactionHash);
|
|
29
|
-
}
|
|
30
|
-
const voutBytes = new Uint8Array(4);
|
|
31
|
-
new DataView(voutBytes.buffer).setUint32(
|
|
32
|
-
0,
|
|
33
|
-
leaf.prevTokenTransactionLeafVout,
|
|
34
|
-
false
|
|
35
|
-
);
|
|
36
|
-
hashObj.update(voutBytes);
|
|
37
|
-
allHashes.push(hashObj.digest());
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
if (tokenTransaction.tokenInput?.$case === "mintInput") {
|
|
41
|
-
const hashObj = sha256.create();
|
|
42
|
-
if (tokenTransaction.tokenInput.mintInput.issuerPublicKey) {
|
|
43
|
-
hashObj.update(tokenTransaction.tokenInput.mintInput.issuerPublicKey);
|
|
44
|
-
}
|
|
45
|
-
if (tokenTransaction.tokenInput.mintInput.issuerProvidedTimestamp) {
|
|
46
|
-
const timestampBytes = new Uint8Array(8);
|
|
47
|
-
new DataView(timestampBytes.buffer).setBigUint64(
|
|
48
|
-
0,
|
|
49
|
-
BigInt(tokenTransaction.tokenInput.mintInput.issuerProvidedTimestamp),
|
|
50
|
-
true
|
|
51
|
-
// true for little-endian to match Go implementation
|
|
52
|
-
);
|
|
53
|
-
hashObj.update(timestampBytes);
|
|
54
|
-
}
|
|
55
|
-
allHashes.push(hashObj.digest());
|
|
56
|
-
}
|
|
57
|
-
for (const leaf of tokenTransaction.outputLeaves || []) {
|
|
58
|
-
const hashObj = sha256.create();
|
|
59
|
-
if (leaf.id && !partialHash) {
|
|
60
|
-
hashObj.update(new TextEncoder().encode(leaf.id));
|
|
61
|
-
}
|
|
62
|
-
if (leaf.ownerPublicKey) {
|
|
63
|
-
hashObj.update(leaf.ownerPublicKey);
|
|
64
|
-
}
|
|
65
|
-
if (leaf.revocationPublicKey && !partialHash) {
|
|
66
|
-
hashObj.update(leaf.revocationPublicKey);
|
|
67
|
-
}
|
|
68
|
-
if (leaf.withdrawBondSats && !partialHash) {
|
|
69
|
-
const bondBytes = new Uint8Array(8);
|
|
70
|
-
new DataView(bondBytes.buffer).setBigUint64(
|
|
71
|
-
0,
|
|
72
|
-
BigInt(leaf.withdrawBondSats),
|
|
73
|
-
false
|
|
74
|
-
);
|
|
75
|
-
hashObj.update(bondBytes);
|
|
76
|
-
}
|
|
77
|
-
if (leaf.withdrawRelativeBlockLocktime && !partialHash) {
|
|
78
|
-
const locktimeBytes = new Uint8Array(8);
|
|
79
|
-
new DataView(locktimeBytes.buffer).setBigUint64(
|
|
80
|
-
0,
|
|
81
|
-
BigInt(leaf.withdrawRelativeBlockLocktime),
|
|
82
|
-
false
|
|
83
|
-
);
|
|
84
|
-
hashObj.update(locktimeBytes);
|
|
85
|
-
}
|
|
86
|
-
if (leaf.tokenPublicKey) {
|
|
87
|
-
hashObj.update(leaf.tokenPublicKey);
|
|
88
|
-
}
|
|
89
|
-
if (leaf.tokenAmount) {
|
|
90
|
-
hashObj.update(leaf.tokenAmount);
|
|
91
|
-
}
|
|
92
|
-
allHashes.push(hashObj.digest());
|
|
93
|
-
}
|
|
94
|
-
const sortedPubKeys = [
|
|
95
|
-
...tokenTransaction.sparkOperatorIdentityPublicKeys || []
|
|
96
|
-
].sort((a, b) => {
|
|
97
|
-
for (let i = 0; i < a.length && i < b.length; i++) {
|
|
98
|
-
if (a[i] !== b[i]) return a[i] - b[i];
|
|
99
|
-
}
|
|
100
|
-
return a.length - b.length;
|
|
101
|
-
});
|
|
102
|
-
for (const pubKey of sortedPubKeys) {
|
|
103
|
-
const hashObj = sha256.create();
|
|
104
|
-
if (pubKey) {
|
|
105
|
-
hashObj.update(pubKey);
|
|
106
|
-
}
|
|
107
|
-
allHashes.push(hashObj.digest());
|
|
108
|
-
}
|
|
109
|
-
const finalHashObj = sha256.create();
|
|
110
|
-
const concatenatedHashes = new Uint8Array(
|
|
111
|
-
allHashes.reduce((sum, hash) => sum + hash.length, 0)
|
|
112
|
-
);
|
|
113
|
-
let offset = 0;
|
|
114
|
-
for (const hash of allHashes) {
|
|
115
|
-
concatenatedHashes.set(hash, offset);
|
|
116
|
-
offset += hash.length;
|
|
117
|
-
}
|
|
118
|
-
finalHashObj.update(concatenatedHashes);
|
|
119
|
-
return finalHashObj.digest();
|
|
120
|
-
}
|
|
121
|
-
function hashOperatorSpecificTokenTransactionSignablePayload(payload) {
|
|
122
|
-
if (!payload) {
|
|
123
|
-
throw new Error("revocation keyshare signable payload cannot be nil");
|
|
124
|
-
}
|
|
125
|
-
let allHashes = new Uint8Array(0);
|
|
126
|
-
const hash1 = sha256(payload.finalTokenTransactionHash || new Uint8Array(0));
|
|
127
|
-
allHashes = concatenateUint8Arrays(allHashes, hash1);
|
|
128
|
-
const hash2 = sha256(payload.operatorIdentityPublicKey || new Uint8Array(0));
|
|
129
|
-
allHashes = concatenateUint8Arrays(allHashes, hash2);
|
|
130
|
-
return sha256(allHashes);
|
|
131
|
-
}
|
|
132
|
-
function concatenateUint8Arrays(array1, array2) {
|
|
133
|
-
const result = new Uint8Array(array1.length + array2.length);
|
|
134
|
-
result.set(array1, 0);
|
|
135
|
-
result.set(array2, array1.length);
|
|
136
|
-
return result;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// src/utils/token-keyshares.ts
|
|
140
|
-
import { secp256k1 } from "@noble/curves/secp256k1";
|
|
141
|
-
function recoverPrivateKeyFromKeyshares(keyshares, threshold) {
|
|
142
|
-
const shares = keyshares.map((keyshare) => ({
|
|
143
|
-
fieldModulus: BigInt("0x" + secp256k1.CURVE.n.toString(16)),
|
|
144
|
-
// secp256k1 curve order
|
|
145
|
-
threshold,
|
|
146
|
-
index: BigInt(keyshare.index),
|
|
147
|
-
share: BigInt("0x" + Buffer.from(keyshare.keyshare).toString("hex")),
|
|
148
|
-
proofs: []
|
|
149
|
-
}));
|
|
150
|
-
const recoveredKey = recoverSecret(shares);
|
|
151
|
-
return bigIntToPrivateKey(recoveredKey);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// src/utils/token-transactions.ts
|
|
155
|
-
import { bytesToHex, bytesToNumberBE } from "@noble/curves/abstract/utils";
|
|
156
|
-
function calculateAvailableTokenAmount(outputLeaves) {
|
|
157
|
-
return outputLeaves.reduce(
|
|
158
|
-
(sum, leaf) => sum + BigInt(bytesToNumberBE(leaf.leaf.tokenAmount)),
|
|
159
|
-
BigInt(0)
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
function checkIfSelectedLeavesAreAvailable(selectedLeaves, tokenLeaves, tokenPublicKey) {
|
|
163
|
-
const tokenPubKeyHex = bytesToHex(tokenPublicKey);
|
|
164
|
-
const tokenLeavesAvailable = tokenLeaves.get(tokenPubKeyHex);
|
|
165
|
-
if (!tokenLeavesAvailable) {
|
|
166
|
-
return false;
|
|
167
|
-
}
|
|
168
|
-
if (selectedLeaves.length === 0 || tokenLeavesAvailable.length < selectedLeaves.length) {
|
|
169
|
-
return false;
|
|
170
|
-
}
|
|
171
|
-
const availableLeafIds = new Set(
|
|
172
|
-
tokenLeavesAvailable.map((leaf) => leaf.leaf.id)
|
|
173
|
-
);
|
|
174
|
-
for (const selectedLeaf of selectedLeaves) {
|
|
175
|
-
if (!selectedLeaf.leaf?.id || !availableLeafIds.has(selectedLeaf.leaf.id)) {
|
|
176
|
-
return false;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
return true;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// src/services/token-transactions.ts
|
|
183
|
-
var TokenTransactionService = class {
|
|
184
|
-
config;
|
|
185
|
-
connectionManager;
|
|
186
|
-
constructor(config, connectionManager) {
|
|
187
|
-
this.config = config;
|
|
188
|
-
this.connectionManager = connectionManager;
|
|
189
|
-
}
|
|
190
|
-
async constructTransferTokenTransaction(selectedLeaves, receiverSparkAddress, tokenPublicKey, tokenAmount) {
|
|
191
|
-
let availableTokenAmount = calculateAvailableTokenAmount(selectedLeaves);
|
|
192
|
-
if (availableTokenAmount === tokenAmount) {
|
|
193
|
-
return {
|
|
194
|
-
tokenInput: {
|
|
195
|
-
$case: "transferInput",
|
|
196
|
-
transferInput: {
|
|
197
|
-
leavesToSpend: selectedLeaves.map((leaf) => ({
|
|
198
|
-
prevTokenTransactionHash: leaf.previousTransactionHash,
|
|
199
|
-
prevTokenTransactionLeafVout: leaf.previousTransactionVout
|
|
200
|
-
}))
|
|
201
|
-
}
|
|
202
|
-
},
|
|
203
|
-
outputLeaves: [
|
|
204
|
-
{
|
|
205
|
-
ownerPublicKey: receiverSparkAddress,
|
|
206
|
-
tokenPublicKey,
|
|
207
|
-
tokenAmount: numberToBytesBE(tokenAmount, 16)
|
|
208
|
-
}
|
|
209
|
-
],
|
|
210
|
-
sparkOperatorIdentityPublicKeys: this.collectOperatorIdentityPublicKeys()
|
|
211
|
-
};
|
|
212
|
-
} else {
|
|
213
|
-
const tokenAmountDifference = availableTokenAmount - tokenAmount;
|
|
214
|
-
return {
|
|
215
|
-
tokenInput: {
|
|
216
|
-
$case: "transferInput",
|
|
217
|
-
transferInput: {
|
|
218
|
-
leavesToSpend: selectedLeaves.map((leaf) => ({
|
|
219
|
-
prevTokenTransactionHash: leaf.previousTransactionHash,
|
|
220
|
-
prevTokenTransactionLeafVout: leaf.previousTransactionVout
|
|
221
|
-
}))
|
|
222
|
-
}
|
|
223
|
-
},
|
|
224
|
-
outputLeaves: [
|
|
225
|
-
{
|
|
226
|
-
ownerPublicKey: receiverSparkAddress,
|
|
227
|
-
tokenPublicKey,
|
|
228
|
-
tokenAmount: numberToBytesBE(tokenAmount, 16)
|
|
229
|
-
},
|
|
230
|
-
{
|
|
231
|
-
ownerPublicKey: await this.config.signer.getIdentityPublicKey(),
|
|
232
|
-
tokenPublicKey,
|
|
233
|
-
tokenAmount: numberToBytesBE(tokenAmountDifference, 16)
|
|
234
|
-
}
|
|
235
|
-
],
|
|
236
|
-
sparkOperatorIdentityPublicKeys: this.collectOperatorIdentityPublicKeys()
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
collectOperatorIdentityPublicKeys() {
|
|
241
|
-
const operatorKeys = [];
|
|
242
|
-
for (const [_, operator] of Object.entries(
|
|
243
|
-
this.config.getConfig().signingOperators
|
|
244
|
-
)) {
|
|
245
|
-
operatorKeys.push(operator.identityPublicKey);
|
|
246
|
-
}
|
|
247
|
-
return operatorKeys;
|
|
248
|
-
}
|
|
249
|
-
async broadcastTokenTransaction(tokenTransaction, leafToSpendSigningPublicKeys, leafToSpendRevocationPublicKeys) {
|
|
250
|
-
const sparkClient = await this.connectionManager.createSparkClient(
|
|
251
|
-
this.config.getCoordinatorAddress()
|
|
252
|
-
);
|
|
253
|
-
const signingOperators = this.config.getConfig().signingOperators;
|
|
254
|
-
const partialTokenTransactionHash = hashTokenTransaction(
|
|
255
|
-
tokenTransaction,
|
|
256
|
-
true
|
|
257
|
-
);
|
|
258
|
-
const ownerSignatures = [];
|
|
259
|
-
if (tokenTransaction.tokenInput.$case === "mintInput") {
|
|
260
|
-
const issuerPublicKey = tokenTransaction.tokenInput.mintInput.issuerPublicKey;
|
|
261
|
-
if (!issuerPublicKey) {
|
|
262
|
-
throw new Error("issuer public key cannot be nil");
|
|
263
|
-
}
|
|
264
|
-
const ownerSignature = await this.signMessageWithKey(
|
|
265
|
-
partialTokenTransactionHash,
|
|
266
|
-
issuerPublicKey
|
|
267
|
-
);
|
|
268
|
-
ownerSignatures.push(ownerSignature);
|
|
269
|
-
} else if (tokenTransaction.tokenInput.$case === "transferInput") {
|
|
270
|
-
const transferInput = tokenTransaction.tokenInput.transferInput;
|
|
271
|
-
if (!leafToSpendSigningPublicKeys || !leafToSpendRevocationPublicKeys) {
|
|
272
|
-
throw new Error(
|
|
273
|
-
"leafToSpendSigningPublicKeys and leafToSpendRevocationPublicKeys are required"
|
|
274
|
-
);
|
|
275
|
-
}
|
|
276
|
-
for (let i = 0; i < transferInput.leavesToSpend.length; i++) {
|
|
277
|
-
const key = leafToSpendSigningPublicKeys[i];
|
|
278
|
-
if (!key) {
|
|
279
|
-
throw new Error("key not found");
|
|
280
|
-
}
|
|
281
|
-
const ownerSignature = await this.signMessageWithKey(
|
|
282
|
-
partialTokenTransactionHash,
|
|
283
|
-
key
|
|
284
|
-
);
|
|
285
|
-
ownerSignatures.push(ownerSignature);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
const startResponse = await sparkClient.start_token_transaction({
|
|
289
|
-
identityPublicKey: await this.config.signer.getIdentityPublicKey(),
|
|
290
|
-
partialTokenTransaction: tokenTransaction,
|
|
291
|
-
tokenTransactionSignatures: {
|
|
292
|
-
ownerSignatures
|
|
293
|
-
}
|
|
294
|
-
});
|
|
295
|
-
if (startResponse.keyshareInfo?.ownerIdentifiers.length !== Object.keys(signingOperators).length) {
|
|
296
|
-
throw new Error(
|
|
297
|
-
`Keyshare operator count (${startResponse.keyshareInfo?.ownerIdentifiers.length}) does not match signing operator count (${Object.keys(signingOperators).length})`
|
|
298
|
-
);
|
|
299
|
-
}
|
|
300
|
-
for (const identifier of startResponse.keyshareInfo?.ownerIdentifiers || []) {
|
|
301
|
-
if (!signingOperators[identifier]) {
|
|
302
|
-
throw new Error(
|
|
303
|
-
`Keyshare operator ${identifier} not found in signing operator list`
|
|
304
|
-
);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
const finalTokenTransaction = startResponse.finalTokenTransaction;
|
|
308
|
-
const finalTokenTransactionHash = hashTokenTransaction(
|
|
309
|
-
finalTokenTransaction,
|
|
310
|
-
false
|
|
311
|
-
);
|
|
312
|
-
const payload = {
|
|
313
|
-
finalTokenTransactionHash,
|
|
314
|
-
operatorIdentityPublicKey: await this.config.signer.getIdentityPublicKey()
|
|
315
|
-
};
|
|
316
|
-
const payloadHash = await hashOperatorSpecificTokenTransactionSignablePayload(payload);
|
|
317
|
-
const operatorSpecificSignatures = [];
|
|
318
|
-
if (tokenTransaction.tokenInput.$case === "mintInput") {
|
|
319
|
-
const issuerPublicKey = tokenTransaction.tokenInput.mintInput.issuerPublicKey;
|
|
320
|
-
if (!issuerPublicKey) {
|
|
321
|
-
throw new Error("issuer public key cannot be nil");
|
|
322
|
-
}
|
|
323
|
-
const ownerSignature = await this.signMessageWithKey(
|
|
324
|
-
payloadHash,
|
|
325
|
-
issuerPublicKey
|
|
326
|
-
);
|
|
327
|
-
operatorSpecificSignatures.push({
|
|
328
|
-
ownerPublicKey: issuerPublicKey,
|
|
329
|
-
ownerSignature,
|
|
330
|
-
payload
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
if (tokenTransaction.tokenInput.$case === "transferInput") {
|
|
334
|
-
const transferInput = tokenTransaction.tokenInput.transferInput;
|
|
335
|
-
for (let i = 0; i < transferInput.leavesToSpend.length; i++) {
|
|
336
|
-
let ownerSignature;
|
|
337
|
-
if (this.config.getConfig().useTokenTransactionSchnorrSignatures) {
|
|
338
|
-
ownerSignature = await this.config.signer.signSchnorrWithIdentityKey(payloadHash);
|
|
339
|
-
} else {
|
|
340
|
-
ownerSignature = await this.config.signer.signMessageWithIdentityKey(payloadHash);
|
|
341
|
-
}
|
|
342
|
-
operatorSpecificSignatures.push({
|
|
343
|
-
ownerPublicKey: await this.config.signer.getIdentityPublicKey(),
|
|
344
|
-
ownerSignature,
|
|
345
|
-
payload
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
const soSignatures = await Promise.allSettled(
|
|
350
|
-
Object.entries(signingOperators).map(
|
|
351
|
-
async ([identifier, operator], index) => {
|
|
352
|
-
const internalSparkClient = await this.connectionManager.createSparkClient(operator.address);
|
|
353
|
-
const identityPublicKey = await this.config.signer.getIdentityPublicKey();
|
|
354
|
-
const response = await internalSparkClient.sign_token_transaction(
|
|
355
|
-
{
|
|
356
|
-
finalTokenTransaction,
|
|
357
|
-
operatorSpecificSignatures,
|
|
358
|
-
identityPublicKey
|
|
359
|
-
},
|
|
360
|
-
{
|
|
361
|
-
retry: true,
|
|
362
|
-
retryMaxAttempts: 5
|
|
363
|
-
}
|
|
364
|
-
);
|
|
365
|
-
return {
|
|
366
|
-
index,
|
|
367
|
-
identifier,
|
|
368
|
-
response
|
|
369
|
-
};
|
|
370
|
-
}
|
|
371
|
-
)
|
|
372
|
-
);
|
|
373
|
-
const threshold = startResponse.keyshareInfo.threshold;
|
|
374
|
-
const successfulSignatures = validateResponses(soSignatures);
|
|
375
|
-
if (tokenTransaction.tokenInput.$case === "transferInput") {
|
|
376
|
-
const leavesToSpend = tokenTransaction.tokenInput.transferInput.leavesToSpend;
|
|
377
|
-
let revocationKeys = [];
|
|
378
|
-
for (let leafIndex = 0; leafIndex < leavesToSpend.length; leafIndex++) {
|
|
379
|
-
const leafKeyshares = successfulSignatures.map(
|
|
380
|
-
({ identifier, response }) => ({
|
|
381
|
-
index: parseInt(identifier, 16),
|
|
382
|
-
keyshare: response.tokenTransactionRevocationKeyshares[leafIndex]
|
|
383
|
-
})
|
|
384
|
-
);
|
|
385
|
-
if (leafKeyshares.length < threshold) {
|
|
386
|
-
throw new Error(
|
|
387
|
-
`Insufficient keyshares for leaf ${leafIndex}: got ${leafKeyshares.length}, need ${threshold}`
|
|
388
|
-
);
|
|
389
|
-
}
|
|
390
|
-
const seenIndices = /* @__PURE__ */ new Set();
|
|
391
|
-
for (const { index } of leafKeyshares) {
|
|
392
|
-
if (seenIndices.has(index)) {
|
|
393
|
-
throw new Error(
|
|
394
|
-
`Duplicate operator index ${index} for leaf ${leafIndex}`
|
|
395
|
-
);
|
|
396
|
-
}
|
|
397
|
-
seenIndices.add(index);
|
|
398
|
-
}
|
|
399
|
-
const recoveredPrivateKey = recoverPrivateKeyFromKeyshares(
|
|
400
|
-
leafKeyshares,
|
|
401
|
-
threshold
|
|
402
|
-
);
|
|
403
|
-
const recoveredPublicKey = secp256k12.getPublicKey(
|
|
404
|
-
recoveredPrivateKey,
|
|
405
|
-
true
|
|
406
|
-
);
|
|
407
|
-
if (!leafToSpendRevocationPublicKeys || !leafToSpendRevocationPublicKeys[leafIndex] || !recoveredPublicKey.every(
|
|
408
|
-
(byte, i) => byte === leafToSpendRevocationPublicKeys[leafIndex][i]
|
|
409
|
-
)) {
|
|
410
|
-
throw new Error(
|
|
411
|
-
`Recovered public key does not match expected revocation public key for leaf ${leafIndex}`
|
|
412
|
-
);
|
|
413
|
-
}
|
|
414
|
-
revocationKeys.push(recoveredPrivateKey);
|
|
415
|
-
}
|
|
416
|
-
await this.finalizeTokenTransaction(
|
|
417
|
-
finalTokenTransaction,
|
|
418
|
-
revocationKeys,
|
|
419
|
-
threshold
|
|
420
|
-
);
|
|
421
|
-
}
|
|
422
|
-
return bytesToHex2(
|
|
423
|
-
hashTokenTransaction(startResponse.finalTokenTransaction)
|
|
424
|
-
);
|
|
425
|
-
}
|
|
426
|
-
async finalizeTokenTransaction(finalTokenTransaction, leafToSpendRevocationKeys, threshold) {
|
|
427
|
-
const signingOperators = this.config.getConfig().signingOperators;
|
|
428
|
-
const soResponses = await Promise.allSettled(
|
|
429
|
-
Object.entries(signingOperators).map(async ([identifier, operator]) => {
|
|
430
|
-
const internalSparkClient = await this.connectionManager.createSparkClient(operator.address);
|
|
431
|
-
const identityPublicKey = await this.config.signer.getIdentityPublicKey();
|
|
432
|
-
const response = await internalSparkClient.finalize_token_transaction(
|
|
433
|
-
{
|
|
434
|
-
finalTokenTransaction,
|
|
435
|
-
leafToSpendRevocationKeys,
|
|
436
|
-
identityPublicKey
|
|
437
|
-
},
|
|
438
|
-
{
|
|
439
|
-
retry: true,
|
|
440
|
-
retryMaxAttempts: 5
|
|
441
|
-
}
|
|
442
|
-
);
|
|
443
|
-
return {
|
|
444
|
-
identifier,
|
|
445
|
-
response
|
|
446
|
-
};
|
|
447
|
-
})
|
|
448
|
-
);
|
|
449
|
-
validateResponses(soResponses);
|
|
450
|
-
return finalTokenTransaction;
|
|
451
|
-
}
|
|
452
|
-
async fetchOwnedTokenLeaves(ownerPublicKeys, tokenPublicKeys) {
|
|
453
|
-
const sparkClient = await this.connectionManager.createSparkClient(
|
|
454
|
-
this.config.getCoordinatorAddress()
|
|
455
|
-
);
|
|
456
|
-
const result = await sparkClient.get_owned_token_leaves({
|
|
457
|
-
ownerPublicKeys,
|
|
458
|
-
tokenPublicKeys
|
|
459
|
-
});
|
|
460
|
-
return result.leavesWithPreviousTransactionData;
|
|
461
|
-
}
|
|
462
|
-
async syncTokenLeaves(tokenLeaves) {
|
|
463
|
-
const unsortedTokenLeaves = await this.fetchOwnedTokenLeaves(
|
|
464
|
-
await this.config.signer.getTrackedPublicKeys(),
|
|
465
|
-
[]
|
|
466
|
-
);
|
|
467
|
-
unsortedTokenLeaves.forEach((leaf) => {
|
|
468
|
-
const tokenKey = bytesToHex2(leaf.leaf.tokenPublicKey);
|
|
469
|
-
const index = leaf.previousTransactionVout;
|
|
470
|
-
tokenLeaves.set(tokenKey, [{ ...leaf, previousTransactionVout: index }]);
|
|
471
|
-
});
|
|
472
|
-
}
|
|
473
|
-
selectTokenLeaves(tokenLeaves, tokenAmount) {
|
|
474
|
-
if (calculateAvailableTokenAmount(tokenLeaves) < tokenAmount) {
|
|
475
|
-
throw new Error("Insufficient available token amount");
|
|
476
|
-
}
|
|
477
|
-
const exactMatch = tokenLeaves.find(
|
|
478
|
-
(item) => bytesToNumberBE2(item.leaf.tokenAmount) === tokenAmount
|
|
479
|
-
);
|
|
480
|
-
if (exactMatch) {
|
|
481
|
-
return [exactMatch];
|
|
482
|
-
}
|
|
483
|
-
tokenLeaves.sort(
|
|
484
|
-
(a, b) => Number(
|
|
485
|
-
bytesToNumberBE2(a.leaf.tokenAmount) - bytesToNumberBE2(b.leaf.tokenAmount)
|
|
486
|
-
)
|
|
487
|
-
);
|
|
488
|
-
let remainingAmount = tokenAmount;
|
|
489
|
-
const selectedLeaves = [];
|
|
490
|
-
for (const leafInfo of tokenLeaves) {
|
|
491
|
-
if (remainingAmount <= 0n) break;
|
|
492
|
-
selectedLeaves.push(leafInfo);
|
|
493
|
-
remainingAmount -= bytesToNumberBE2(leafInfo.leaf.tokenAmount);
|
|
494
|
-
}
|
|
495
|
-
if (remainingAmount > 0n) {
|
|
496
|
-
throw new Error(
|
|
497
|
-
"You do not have enough funds to complete the specified operation"
|
|
498
|
-
);
|
|
499
|
-
}
|
|
500
|
-
return selectedLeaves;
|
|
501
|
-
}
|
|
502
|
-
// Helper function for deciding if the signer public key is the identity public key
|
|
503
|
-
async signMessageWithKey(message, publicKey) {
|
|
504
|
-
if (bytesToHex2(publicKey) === bytesToHex2(await this.config.signer.getIdentityPublicKey())) {
|
|
505
|
-
if (this.config.getConfig().useTokenTransactionSchnorrSignatures) {
|
|
506
|
-
return await this.config.signer.signSchnorrWithIdentityKey(message);
|
|
507
|
-
} else {
|
|
508
|
-
return await this.config.signer.signMessageWithIdentityKey(message);
|
|
509
|
-
}
|
|
510
|
-
} else {
|
|
511
|
-
if (this.config.getConfig().useTokenTransactionSchnorrSignatures) {
|
|
512
|
-
return await this.config.signer.signSchnorr(message, publicKey);
|
|
513
|
-
} else {
|
|
514
|
-
return await this.config.signer.signMessageWithPublicKey(
|
|
515
|
-
message,
|
|
516
|
-
publicKey
|
|
517
|
-
);
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
};
|
|
522
|
-
|
|
523
|
-
export {
|
|
524
|
-
calculateAvailableTokenAmount,
|
|
525
|
-
checkIfSelectedLeavesAreAvailable,
|
|
526
|
-
TokenTransactionService
|
|
527
|
-
};
|