@buildonspark/spark-sdk 0.1.45 → 0.1.47

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.
Files changed (146) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/{chunk-I54FARY2.js → chunk-EAP3U3CW.js} +14 -14
  3. package/dist/chunk-GWFQ7EBA.js +3773 -0
  4. package/dist/{chunk-J2IE4Z7Y.js → chunk-NNX4OK44.js} +3487 -934
  5. package/dist/{RequestLightningSendInput-Du0z7Om7.d.cts → client-CvpTRpcw.d.cts} +422 -212
  6. package/dist/{RequestLightningSendInput-DEPd_fPO.d.ts → client-D7KgLN44.d.ts} +422 -212
  7. package/dist/graphql/objects/index.d.cts +5 -9
  8. package/dist/graphql/objects/index.d.ts +5 -9
  9. package/dist/graphql/objects/index.js +1 -1
  10. package/dist/index.cjs +20461 -23377
  11. package/dist/index.d.cts +15 -769
  12. package/dist/index.d.ts +15 -769
  13. package/dist/index.js +81 -71
  14. package/dist/index.node.cjs +21994 -25018
  15. package/dist/index.node.d.cts +312 -34
  16. package/dist/index.node.d.ts +312 -34
  17. package/dist/index.node.js +82 -176
  18. package/dist/native/index.cjs +22847 -25841
  19. package/dist/native/index.d.cts +974 -1138
  20. package/dist/native/index.d.ts +974 -1138
  21. package/dist/native/index.js +10604 -13592
  22. package/dist/proto/lrc20.d.cts +2 -2
  23. package/dist/proto/lrc20.d.ts +2 -2
  24. package/dist/proto/lrc20.js +3098 -46
  25. package/dist/proto/spark.d.cts +1 -1
  26. package/dist/proto/spark.d.ts +1 -1
  27. package/dist/proto/spark_token.d.cts +1 -1
  28. package/dist/proto/spark_token.d.ts +1 -1
  29. package/dist/{sdk-types-Cc4l4kb1.d.ts → sdk-types-BGCeea0G.d.ts} +1 -1
  30. package/dist/{sdk-types-B0SwjolI.d.cts → sdk-types-XUeQMLFP.d.cts} +1 -1
  31. package/dist/{spark-dM7EYXYQ.d.cts → spark-BbUrbvZz.d.cts} +1 -1
  32. package/dist/{spark-dM7EYXYQ.d.ts → spark-BbUrbvZz.d.ts} +1 -1
  33. package/dist/spark-wallet-BAFPpPtY.d.cts +923 -0
  34. package/dist/spark-wallet-CJkQW8pK.d.ts +923 -0
  35. package/dist/spark_bindings/native/index.d.cts +1 -1
  36. package/dist/spark_bindings/native/index.d.ts +1 -1
  37. package/dist/spark_bindings/wasm/index.d.cts +1 -1
  38. package/dist/spark_bindings/wasm/index.d.ts +1 -1
  39. package/dist/{services/index.cjs → tests/test-utils.cjs} +2512 -4380
  40. package/dist/tests/test-utils.d.cts +79 -0
  41. package/dist/tests/test-utils.d.ts +79 -0
  42. package/dist/tests/test-utils.js +85 -0
  43. package/dist/types/index.d.cts +5 -9
  44. package/dist/types/index.d.ts +5 -9
  45. package/dist/types/index.js +5 -5
  46. package/dist/{types-C-Rp0Oo7.d.cts → types-BADxR3bm.d.cts} +1 -1
  47. package/dist/{types-C-Rp0Oo7.d.ts → types-BADxR3bm.d.ts} +1 -1
  48. package/package.json +7 -35
  49. package/src/graphql/client.ts +59 -20
  50. package/src/index.node.ts +28 -2
  51. package/src/index.ts +31 -1
  52. package/src/native/index.ts +16 -2
  53. package/src/services/config.ts +4 -6
  54. package/src/services/connection.ts +131 -64
  55. package/src/services/lightning.ts +1 -2
  56. package/src/services/token-transactions.ts +7 -7
  57. package/src/services/transfer.ts +1 -1
  58. package/src/services/tree-creation.ts +1 -1
  59. package/src/services/wallet-config.ts +18 -10
  60. package/src/signer/signer.react-native.ts +2 -5
  61. package/src/signer/signer.ts +138 -64
  62. package/src/signer/types.ts +52 -0
  63. package/src/spark-wallet/spark-wallet.ts +79 -36
  64. package/src/spark-wallet/types.ts +4 -4
  65. package/src/tests/integration/coop-exit.test.ts +2 -1
  66. package/src/tests/integration/lightning.test.ts +2 -2
  67. package/src/tests/integration/swap.test.ts +1 -1
  68. package/src/tests/integration/transfer.test.ts +5 -5
  69. package/src/tests/integration/tree-creation.test.ts +1 -1
  70. package/src/tests/integration/wallet.test.ts +1 -0
  71. package/src/tests/isHermeticTest.ts +3 -24
  72. package/src/tests/{test-util.ts → test-utils.ts} +3 -7
  73. package/src/tests/wrapWithOtelSpan.test.ts +1 -1
  74. package/src/{address → utils}/address.ts +1 -1
  75. package/src/utils/crypto.ts +19 -9
  76. package/src/utils/index.ts +2 -0
  77. package/src/utils/network.ts +17 -0
  78. package/src/utils/secret-sharing.ts +1 -2
  79. package/src/utils/signing.ts +1 -1
  80. package/src/utils/token-transactions.ts +3 -3
  81. package/src/utils/unilateral-exit.ts +32 -0
  82. package/src/utils/xchain-address.ts +1 -1
  83. package/dist/BitcoinNetwork-TnABML0T.d.cts +0 -18
  84. package/dist/BitcoinNetwork-TnABML0T.d.ts +0 -18
  85. package/dist/LightningSendFeeEstimateInput-BgOhEAI-.d.cts +0 -10
  86. package/dist/LightningSendFeeEstimateInput-BgOhEAI-.d.ts +0 -10
  87. package/dist/address/index.cjs +0 -458
  88. package/dist/address/index.d.cts +0 -32
  89. package/dist/address/index.d.ts +0 -32
  90. package/dist/address/index.js +0 -17
  91. package/dist/chunk-5FUB65LX.js +0 -838
  92. package/dist/chunk-6264CGDM.js +0 -113
  93. package/dist/chunk-7V6N75CC.js +0 -24
  94. package/dist/chunk-C2S227QR.js +0 -2336
  95. package/dist/chunk-GSI4OLXZ.js +0 -117
  96. package/dist/chunk-GZ5IPPJ2.js +0 -170
  97. package/dist/chunk-HWJWKEIU.js +0 -75
  98. package/dist/chunk-KMUMFYFX.js +0 -137
  99. package/dist/chunk-L3EHBOUX.js +0 -0
  100. package/dist/chunk-NSJF5F5O.js +0 -325
  101. package/dist/chunk-NTFKFRQ2.js +0 -3146
  102. package/dist/chunk-PQN3C2MF.js +0 -1122
  103. package/dist/chunk-QNNSEJ4P.js +0 -232
  104. package/dist/chunk-R5PXJZQS.js +0 -277
  105. package/dist/chunk-VTUGIIWI.js +0 -0
  106. package/dist/chunk-YUPMXTCJ.js +0 -622
  107. package/dist/chunk-Z5HIAYFT.js +0 -84
  108. package/dist/index-B2AwKW5J.d.cts +0 -214
  109. package/dist/index-CJDi1HWc.d.ts +0 -214
  110. package/dist/network-BTJl-Sul.d.ts +0 -46
  111. package/dist/network-CqgsdUF2.d.cts +0 -46
  112. package/dist/services/config.cjs +0 -2354
  113. package/dist/services/config.d.cts +0 -42
  114. package/dist/services/config.d.ts +0 -42
  115. package/dist/services/config.js +0 -17
  116. package/dist/services/connection.cjs +0 -17691
  117. package/dist/services/connection.d.cts +0 -95
  118. package/dist/services/connection.d.ts +0 -95
  119. package/dist/services/connection.js +0 -11
  120. package/dist/services/index.d.cts +0 -21
  121. package/dist/services/index.d.ts +0 -21
  122. package/dist/services/index.js +0 -58
  123. package/dist/services/lrc-connection.cjs +0 -4713
  124. package/dist/services/lrc-connection.d.cts +0 -34
  125. package/dist/services/lrc-connection.d.ts +0 -34
  126. package/dist/services/lrc-connection.js +0 -11
  127. package/dist/services/token-transactions.cjs +0 -2877
  128. package/dist/services/token-transactions.d.cts +0 -75
  129. package/dist/services/token-transactions.d.ts +0 -75
  130. package/dist/services/token-transactions.js +0 -15
  131. package/dist/services/wallet-config.cjs +0 -340
  132. package/dist/services/wallet-config.d.cts +0 -56
  133. package/dist/services/wallet-config.d.ts +0 -56
  134. package/dist/services/wallet-config.js +0 -33
  135. package/dist/signer/signer.cjs +0 -2004
  136. package/dist/signer/signer.d.cts +0 -10
  137. package/dist/signer/signer.d.ts +0 -10
  138. package/dist/signer/signer.js +0 -24
  139. package/dist/signer-BocS_J6B.d.ts +0 -187
  140. package/dist/signer-DKS0AJkw.d.cts +0 -187
  141. package/dist/utils/index.cjs +0 -2947
  142. package/dist/utils/index.d.cts +0 -18
  143. package/dist/utils/index.d.ts +0 -18
  144. package/dist/utils/index.js +0 -157
  145. package/src/address/index.ts +0 -1
  146. package/src/services/lrc-connection.ts +0 -215
@@ -1,2336 +0,0 @@
1
- import {
2
- calculateAvailableTokenAmount,
3
- checkIfSelectedOutputsAreAvailable,
4
- collectResponses
5
- } from "./chunk-HWJWKEIU.js";
6
- import {
7
- decodeSparkAddress
8
- } from "./chunk-KMUMFYFX.js";
9
- import {
10
- bigIntToPrivateKey,
11
- recoverSecret
12
- } from "./chunk-QNNSEJ4P.js";
13
- import {
14
- InternalValidationError,
15
- NetworkError,
16
- ValidationError
17
- } from "./chunk-GSI4OLXZ.js";
18
- import {
19
- Buffer
20
- } from "./chunk-MVRQ5US7.js";
21
-
22
- // src/services/token-transactions.ts
23
- import {
24
- bytesToHex,
25
- bytesToNumberBE,
26
- numberToBytesBE
27
- } from "@noble/curves/abstract/utils";
28
- import { secp256k1 as secp256k12 } from "@noble/curves/secp256k1";
29
-
30
- // src/utils/token-hashing.ts
31
- import { sha256 } from "@noble/hashes/sha2";
32
- function hashTokenTransaction(tokenTransaction, partialHash = false) {
33
- switch (tokenTransaction.version) {
34
- case 0:
35
- return hashTokenTransactionV0(tokenTransaction, partialHash);
36
- case 1:
37
- return hashTokenTransactionV1(tokenTransaction, partialHash);
38
- default:
39
- throw new ValidationError("invalid token transaction version", {
40
- field: "tokenTransaction.version",
41
- value: tokenTransaction.version
42
- });
43
- }
44
- }
45
- function hashTokenTransactionV0(tokenTransaction, partialHash = false) {
46
- if (!tokenTransaction) {
47
- throw new ValidationError("token transaction cannot be nil", {
48
- field: "tokenTransaction"
49
- });
50
- }
51
- let allHashes = [];
52
- if (tokenTransaction.tokenInputs?.$case === "transferInput") {
53
- if (!tokenTransaction.tokenInputs.transferInput.outputsToSpend) {
54
- throw new ValidationError("outputs to spend cannot be null", {
55
- field: "tokenInputs.transferInput.outputsToSpend"
56
- });
57
- }
58
- if (tokenTransaction.tokenInputs.transferInput.outputsToSpend.length === 0) {
59
- throw new ValidationError("outputs to spend cannot be empty", {
60
- field: "tokenInputs.transferInput.outputsToSpend"
61
- });
62
- }
63
- for (const [
64
- i,
65
- output
66
- ] of tokenTransaction.tokenInputs.transferInput.outputsToSpend.entries()) {
67
- if (!output) {
68
- throw new ValidationError(`output cannot be null at index ${i}`, {
69
- field: `tokenInputs.transferInput.outputsToSpend[${i}]`,
70
- index: i
71
- });
72
- }
73
- const hashObj2 = sha256.create();
74
- if (output.prevTokenTransactionHash) {
75
- const prevHash = output.prevTokenTransactionHash;
76
- if (output.prevTokenTransactionHash.length !== 32) {
77
- throw new ValidationError(
78
- `invalid previous transaction hash length at index ${i}`,
79
- {
80
- field: `tokenInputs.transferInput.outputsToSpend[${i}].prevTokenTransactionHash`,
81
- value: prevHash,
82
- expectedLength: 32,
83
- actualLength: prevHash.length,
84
- index: i
85
- }
86
- );
87
- }
88
- hashObj2.update(output.prevTokenTransactionHash);
89
- }
90
- const voutBytes = new Uint8Array(4);
91
- new DataView(voutBytes.buffer).setUint32(
92
- 0,
93
- output.prevTokenTransactionVout,
94
- false
95
- );
96
- hashObj2.update(voutBytes);
97
- allHashes.push(hashObj2.digest());
98
- }
99
- }
100
- if (tokenTransaction.tokenInputs?.$case === "mintInput") {
101
- const hashObj2 = sha256.create();
102
- if (tokenTransaction.tokenInputs.mintInput.issuerPublicKey) {
103
- const issuerPubKey = tokenTransaction.tokenInputs.mintInput.issuerPublicKey;
104
- if (issuerPubKey.length === 0) {
105
- throw new ValidationError("issuer public key cannot be empty", {
106
- field: "tokenInputs.mintInput.issuerPublicKey",
107
- value: issuerPubKey,
108
- expectedLength: 1,
109
- actualLength: 0
110
- });
111
- }
112
- hashObj2.update(issuerPubKey);
113
- let timestampValue = 0;
114
- const mintInput = tokenTransaction.tokenInputs.mintInput;
115
- if ("issuerProvidedTimestamp" in mintInput) {
116
- const v0MintInput = mintInput;
117
- if (v0MintInput.issuerProvidedTimestamp != 0) {
118
- timestampValue = v0MintInput.issuerProvidedTimestamp;
119
- }
120
- } else if ("clientCreatedTimestamp" in tokenTransaction && tokenTransaction.clientCreatedTimestamp) {
121
- timestampValue = tokenTransaction.clientCreatedTimestamp.getTime();
122
- }
123
- if (timestampValue != 0) {
124
- const timestampBytes = new Uint8Array(8);
125
- new DataView(timestampBytes.buffer).setBigUint64(
126
- 0,
127
- BigInt(timestampValue),
128
- true
129
- // true for little-endian to match Go implementation
130
- );
131
- hashObj2.update(timestampBytes);
132
- }
133
- allHashes.push(hashObj2.digest());
134
- }
135
- }
136
- if (tokenTransaction.tokenInputs?.$case === "createInput") {
137
- const issuerPubKeyHashObj = sha256.create();
138
- const createInput = tokenTransaction.tokenInputs.createInput;
139
- if (!createInput.issuerPublicKey || createInput.issuerPublicKey.length === 0) {
140
- throw new ValidationError("issuer public key cannot be nil or empty", {
141
- field: "tokenInputs.createInput.issuerPublicKey"
142
- });
143
- }
144
- issuerPubKeyHashObj.update(createInput.issuerPublicKey);
145
- allHashes.push(issuerPubKeyHashObj.digest());
146
- const tokenNameHashObj = sha256.create();
147
- if (!createInput.tokenName || createInput.tokenName.length === 0) {
148
- throw new ValidationError("token name cannot be empty", {
149
- field: "tokenInputs.createInput.tokenName"
150
- });
151
- }
152
- if (createInput.tokenName.length > 20) {
153
- throw new ValidationError("token name cannot be longer than 20 bytes", {
154
- field: "tokenInputs.createInput.tokenName",
155
- value: createInput.tokenName,
156
- expectedLength: 20,
157
- actualLength: createInput.tokenName.length
158
- });
159
- }
160
- const tokenNameBytes = new Uint8Array(20);
161
- const tokenNameEncoder = new TextEncoder();
162
- tokenNameBytes.set(tokenNameEncoder.encode(createInput.tokenName));
163
- tokenNameHashObj.update(tokenNameBytes);
164
- allHashes.push(tokenNameHashObj.digest());
165
- const tokenTickerHashObj = sha256.create();
166
- if (!createInput.tokenTicker || createInput.tokenTicker.length === 0) {
167
- throw new ValidationError("token ticker cannot be empty", {
168
- field: "tokenInputs.createInput.tokenTicker"
169
- });
170
- }
171
- if (createInput.tokenTicker.length > 6) {
172
- throw new ValidationError("token ticker cannot be longer than 6 bytes", {
173
- field: "tokenInputs.createInput.tokenTicker",
174
- value: createInput.tokenTicker,
175
- expectedLength: 6,
176
- actualLength: createInput.tokenTicker.length
177
- });
178
- }
179
- const tokenTickerBytes = new Uint8Array(6);
180
- const tokenTickerEncoder = new TextEncoder();
181
- tokenTickerBytes.set(tokenTickerEncoder.encode(createInput.tokenTicker));
182
- tokenTickerHashObj.update(tokenTickerBytes);
183
- allHashes.push(tokenTickerHashObj.digest());
184
- const decimalsHashObj = sha256.create();
185
- const decimalsBytes = new Uint8Array(4);
186
- new DataView(decimalsBytes.buffer).setUint32(
187
- 0,
188
- createInput.decimals,
189
- false
190
- );
191
- decimalsHashObj.update(decimalsBytes);
192
- allHashes.push(decimalsHashObj.digest());
193
- const maxSupplyHashObj = sha256.create();
194
- if (!createInput.maxSupply) {
195
- throw new ValidationError("max supply cannot be nil", {
196
- field: "tokenInputs.createInput.maxSupply"
197
- });
198
- }
199
- if (createInput.maxSupply.length !== 16) {
200
- throw new ValidationError("max supply must be exactly 16 bytes", {
201
- field: "tokenInputs.createInput.maxSupply",
202
- value: createInput.maxSupply,
203
- expectedLength: 16,
204
- actualLength: createInput.maxSupply.length
205
- });
206
- }
207
- maxSupplyHashObj.update(createInput.maxSupply);
208
- allHashes.push(maxSupplyHashObj.digest());
209
- const isFreezableHashObj = sha256.create();
210
- const isFreezableByte = new Uint8Array([createInput.isFreezable ? 1 : 0]);
211
- isFreezableHashObj.update(isFreezableByte);
212
- allHashes.push(isFreezableHashObj.digest());
213
- const creationEntityHashObj = sha256.create();
214
- if (!partialHash && createInput.creationEntityPublicKey) {
215
- creationEntityHashObj.update(createInput.creationEntityPublicKey);
216
- }
217
- allHashes.push(creationEntityHashObj.digest());
218
- }
219
- if (!tokenTransaction.tokenOutputs) {
220
- throw new ValidationError("token outputs cannot be null", {
221
- field: "tokenOutputs"
222
- });
223
- }
224
- if (tokenTransaction.tokenOutputs.length === 0 && tokenTransaction.tokenInputs?.$case !== "createInput") {
225
- throw new ValidationError("token outputs cannot be empty", {
226
- field: "tokenOutputs"
227
- });
228
- }
229
- for (const [i, output] of tokenTransaction.tokenOutputs.entries()) {
230
- if (!output) {
231
- throw new ValidationError(`output cannot be null at index ${i}`, {
232
- field: `tokenOutputs[${i}]`,
233
- index: i
234
- });
235
- }
236
- const hashObj2 = sha256.create();
237
- if (output.id && !partialHash) {
238
- if (output.id.length === 0) {
239
- throw new ValidationError(`output ID at index ${i} cannot be empty`, {
240
- field: `tokenOutputs[${i}].id`,
241
- index: i
242
- });
243
- }
244
- hashObj2.update(new TextEncoder().encode(output.id));
245
- }
246
- if (output.ownerPublicKey) {
247
- if (output.ownerPublicKey.length === 0) {
248
- throw new ValidationError(
249
- `owner public key at index ${i} cannot be empty`,
250
- {
251
- field: `tokenOutputs[${i}].ownerPublicKey`,
252
- index: i
253
- }
254
- );
255
- }
256
- hashObj2.update(output.ownerPublicKey);
257
- }
258
- if (!partialHash) {
259
- const revPubKey = output.revocationCommitment;
260
- if (revPubKey) {
261
- if (revPubKey.length === 0) {
262
- throw new ValidationError(
263
- `revocation commitment at index ${i} cannot be empty`,
264
- {
265
- field: `tokenOutputs[${i}].revocationCommitment`,
266
- index: i
267
- }
268
- );
269
- }
270
- hashObj2.update(revPubKey);
271
- }
272
- const bondBytes = new Uint8Array(8);
273
- new DataView(bondBytes.buffer).setBigUint64(
274
- 0,
275
- BigInt(output.withdrawBondSats),
276
- false
277
- );
278
- hashObj2.update(bondBytes);
279
- const locktimeBytes = new Uint8Array(8);
280
- new DataView(locktimeBytes.buffer).setBigUint64(
281
- 0,
282
- BigInt(output.withdrawRelativeBlockLocktime),
283
- false
284
- );
285
- hashObj2.update(locktimeBytes);
286
- }
287
- if (output.tokenPublicKey) {
288
- if (output.tokenPublicKey.length === 0) {
289
- throw new ValidationError(
290
- `token public key at index ${i} cannot be empty`,
291
- {
292
- field: `tokenOutputs[${i}].tokenPublicKey`,
293
- index: i
294
- }
295
- );
296
- }
297
- hashObj2.update(output.tokenPublicKey);
298
- }
299
- if (output.tokenAmount) {
300
- if (output.tokenAmount.length === 0) {
301
- throw new ValidationError(
302
- `token amount at index ${i} cannot be empty`,
303
- {
304
- field: `tokenOutputs[${i}].tokenAmount`,
305
- index: i
306
- }
307
- );
308
- }
309
- if (output.tokenAmount.length > 16) {
310
- throw new ValidationError(
311
- `token amount at index ${i} exceeds maximum length`,
312
- {
313
- field: `tokenOutputs[${i}].tokenAmount`,
314
- value: output.tokenAmount,
315
- expectedLength: 16,
316
- actualLength: output.tokenAmount.length,
317
- index: i
318
- }
319
- );
320
- }
321
- hashObj2.update(output.tokenAmount);
322
- }
323
- allHashes.push(hashObj2.digest());
324
- }
325
- if (!tokenTransaction.sparkOperatorIdentityPublicKeys) {
326
- throw new ValidationError(
327
- "spark operator identity public keys cannot be null",
328
- {}
329
- );
330
- }
331
- const sortedPubKeys = [
332
- ...tokenTransaction.sparkOperatorIdentityPublicKeys || []
333
- ].sort((a, b) => {
334
- for (let i = 0; i < a.length && i < b.length; i++) {
335
- if (a[i] !== b[i]) return a[i] - b[i];
336
- }
337
- return a.length - b.length;
338
- });
339
- for (const [i, pubKey] of sortedPubKeys.entries()) {
340
- if (!pubKey) {
341
- throw new ValidationError(
342
- `operator public key at index ${i} cannot be null`,
343
- {
344
- field: `sparkOperatorIdentityPublicKeys[${i}]`,
345
- index: i
346
- }
347
- );
348
- }
349
- if (pubKey.length === 0) {
350
- throw new ValidationError(
351
- `operator public key at index ${i} cannot be empty`,
352
- {
353
- field: `sparkOperatorIdentityPublicKeys[${i}]`,
354
- index: i
355
- }
356
- );
357
- }
358
- const hashObj2 = sha256.create();
359
- hashObj2.update(pubKey);
360
- allHashes.push(hashObj2.digest());
361
- }
362
- const hashObj = sha256.create();
363
- let networkBytes = new Uint8Array(4);
364
- new DataView(networkBytes.buffer).setUint32(
365
- 0,
366
- tokenTransaction.network.valueOf(),
367
- false
368
- // false for big-endian
369
- );
370
- hashObj.update(networkBytes);
371
- allHashes.push(hashObj.digest());
372
- const finalHashObj = sha256.create();
373
- const concatenatedHashes = new Uint8Array(
374
- allHashes.reduce((sum, hash) => sum + hash.length, 0)
375
- );
376
- let offset = 0;
377
- for (const hash of allHashes) {
378
- concatenatedHashes.set(hash, offset);
379
- offset += hash.length;
380
- }
381
- finalHashObj.update(concatenatedHashes);
382
- return finalHashObj.digest();
383
- }
384
- function hashTokenTransactionV1(tokenTransaction, partialHash = false) {
385
- if (!tokenTransaction) {
386
- throw new ValidationError("token transaction cannot be nil", {
387
- field: "tokenTransaction"
388
- });
389
- }
390
- let allHashes = [];
391
- const versionHashObj = sha256.create();
392
- const versionBytes = new Uint8Array(4);
393
- new DataView(versionBytes.buffer).setUint32(
394
- 0,
395
- tokenTransaction.version,
396
- false
397
- // false for big-endian
398
- );
399
- versionHashObj.update(versionBytes);
400
- allHashes.push(versionHashObj.digest());
401
- const typeHashObj = sha256.create();
402
- const typeBytes = new Uint8Array(4);
403
- let transactionType = 0;
404
- if (tokenTransaction.tokenInputs?.$case === "mintInput") {
405
- transactionType = 2 /* TOKEN_TRANSACTION_TYPE_MINT */;
406
- } else if (tokenTransaction.tokenInputs?.$case === "transferInput") {
407
- transactionType = 3 /* TOKEN_TRANSACTION_TYPE_TRANSFER */;
408
- } else if (tokenTransaction.tokenInputs?.$case === "createInput") {
409
- transactionType = 1 /* TOKEN_TRANSACTION_TYPE_CREATE */;
410
- } else {
411
- throw new ValidationError(
412
- "token transaction must have exactly one input type",
413
- {
414
- field: "tokenInputs"
415
- }
416
- );
417
- }
418
- new DataView(typeBytes.buffer).setUint32(0, transactionType, false);
419
- typeHashObj.update(typeBytes);
420
- allHashes.push(typeHashObj.digest());
421
- if (tokenTransaction.tokenInputs?.$case === "transferInput") {
422
- if (!tokenTransaction.tokenInputs.transferInput.outputsToSpend) {
423
- throw new ValidationError("outputs to spend cannot be null", {
424
- field: "tokenInputs.transferInput.outputsToSpend"
425
- });
426
- }
427
- if (tokenTransaction.tokenInputs.transferInput.outputsToSpend.length === 0) {
428
- throw new ValidationError("outputs to spend cannot be empty", {
429
- field: "tokenInputs.transferInput.outputsToSpend"
430
- });
431
- }
432
- const outputsLenHashObj2 = sha256.create();
433
- const outputsLenBytes2 = new Uint8Array(4);
434
- new DataView(outputsLenBytes2.buffer).setUint32(
435
- 0,
436
- tokenTransaction.tokenInputs.transferInput.outputsToSpend.length,
437
- false
438
- );
439
- outputsLenHashObj2.update(outputsLenBytes2);
440
- allHashes.push(outputsLenHashObj2.digest());
441
- for (const [
442
- i,
443
- output
444
- ] of tokenTransaction.tokenInputs.transferInput.outputsToSpend.entries()) {
445
- if (!output) {
446
- throw new ValidationError(`output cannot be null at index ${i}`, {
447
- field: `tokenInputs.transferInput.outputsToSpend[${i}]`,
448
- index: i
449
- });
450
- }
451
- const hashObj2 = sha256.create();
452
- if (output.prevTokenTransactionHash) {
453
- const prevHash = output.prevTokenTransactionHash;
454
- if (output.prevTokenTransactionHash.length !== 32) {
455
- throw new ValidationError(
456
- `invalid previous transaction hash length at index ${i}`,
457
- {
458
- field: `tokenInputs.transferInput.outputsToSpend[${i}].prevTokenTransactionHash`,
459
- value: prevHash,
460
- expectedLength: 32,
461
- actualLength: prevHash.length,
462
- index: i
463
- }
464
- );
465
- }
466
- hashObj2.update(output.prevTokenTransactionHash);
467
- }
468
- const voutBytes = new Uint8Array(4);
469
- new DataView(voutBytes.buffer).setUint32(
470
- 0,
471
- output.prevTokenTransactionVout,
472
- false
473
- );
474
- hashObj2.update(voutBytes);
475
- allHashes.push(hashObj2.digest());
476
- }
477
- } else if (tokenTransaction.tokenInputs?.$case === "mintInput") {
478
- const hashObj2 = sha256.create();
479
- if (tokenTransaction.tokenInputs.mintInput.issuerPublicKey) {
480
- const issuerPubKey = tokenTransaction.tokenInputs.mintInput.issuerPublicKey;
481
- if (issuerPubKey.length === 0) {
482
- throw new ValidationError("issuer public key cannot be empty", {
483
- field: "tokenInputs.mintInput.issuerPublicKey",
484
- value: issuerPubKey,
485
- expectedLength: 1,
486
- actualLength: 0
487
- });
488
- }
489
- hashObj2.update(issuerPubKey);
490
- allHashes.push(hashObj2.digest());
491
- const tokenIdentifierHashObj = sha256.create();
492
- if (tokenTransaction.tokenInputs.mintInput.tokenIdentifier) {
493
- tokenIdentifierHashObj.update(
494
- tokenTransaction.tokenInputs.mintInput.tokenIdentifier
495
- );
496
- } else {
497
- tokenIdentifierHashObj.update(new Uint8Array(32));
498
- }
499
- allHashes.push(tokenIdentifierHashObj.digest());
500
- }
501
- } else if (tokenTransaction.tokenInputs?.$case === "createInput") {
502
- const createInput = tokenTransaction.tokenInputs.createInput;
503
- const issuerPubKeyHashObj = sha256.create();
504
- if (!createInput.issuerPublicKey || createInput.issuerPublicKey.length === 0) {
505
- throw new ValidationError("issuer public key cannot be nil or empty", {
506
- field: "tokenInputs.createInput.issuerPublicKey"
507
- });
508
- }
509
- issuerPubKeyHashObj.update(createInput.issuerPublicKey);
510
- allHashes.push(issuerPubKeyHashObj.digest());
511
- const tokenNameHashObj = sha256.create();
512
- if (!createInput.tokenName || createInput.tokenName.length === 0) {
513
- throw new ValidationError("token name cannot be empty", {
514
- field: "tokenInputs.createInput.tokenName"
515
- });
516
- }
517
- if (createInput.tokenName.length > 20) {
518
- throw new ValidationError("token name cannot be longer than 20 bytes", {
519
- field: "tokenInputs.createInput.tokenName",
520
- value: createInput.tokenName,
521
- expectedLength: 20,
522
- actualLength: createInput.tokenName.length
523
- });
524
- }
525
- const tokenNameEncoder = new TextEncoder();
526
- tokenNameHashObj.update(tokenNameEncoder.encode(createInput.tokenName));
527
- allHashes.push(tokenNameHashObj.digest());
528
- const tokenTickerHashObj = sha256.create();
529
- if (!createInput.tokenTicker || createInput.tokenTicker.length === 0) {
530
- throw new ValidationError("token ticker cannot be empty", {
531
- field: "tokenInputs.createInput.tokenTicker"
532
- });
533
- }
534
- if (createInput.tokenTicker.length > 6) {
535
- throw new ValidationError("token ticker cannot be longer than 6 bytes", {
536
- field: "tokenInputs.createInput.tokenTicker",
537
- value: createInput.tokenTicker,
538
- expectedLength: 6,
539
- actualLength: createInput.tokenTicker.length
540
- });
541
- }
542
- const tokenTickerEncoder = new TextEncoder();
543
- tokenTickerHashObj.update(
544
- tokenTickerEncoder.encode(createInput.tokenTicker)
545
- );
546
- allHashes.push(tokenTickerHashObj.digest());
547
- const decimalsHashObj = sha256.create();
548
- const decimalsBytes = new Uint8Array(4);
549
- new DataView(decimalsBytes.buffer).setUint32(
550
- 0,
551
- createInput.decimals,
552
- false
553
- );
554
- decimalsHashObj.update(decimalsBytes);
555
- allHashes.push(decimalsHashObj.digest());
556
- const maxSupplyHashObj = sha256.create();
557
- if (!createInput.maxSupply) {
558
- throw new ValidationError("max supply cannot be nil", {
559
- field: "tokenInputs.createInput.maxSupply"
560
- });
561
- }
562
- if (createInput.maxSupply.length !== 16) {
563
- throw new ValidationError("max supply must be exactly 16 bytes", {
564
- field: "tokenInputs.createInput.maxSupply",
565
- value: createInput.maxSupply,
566
- expectedLength: 16,
567
- actualLength: createInput.maxSupply.length
568
- });
569
- }
570
- maxSupplyHashObj.update(createInput.maxSupply);
571
- allHashes.push(maxSupplyHashObj.digest());
572
- const isFreezableHashObj = sha256.create();
573
- isFreezableHashObj.update(
574
- new Uint8Array([createInput.isFreezable ? 1 : 0])
575
- );
576
- allHashes.push(isFreezableHashObj.digest());
577
- const creationEntityHashObj = sha256.create();
578
- if (!partialHash && createInput.creationEntityPublicKey) {
579
- creationEntityHashObj.update(createInput.creationEntityPublicKey);
580
- }
581
- allHashes.push(creationEntityHashObj.digest());
582
- }
583
- if (!tokenTransaction.tokenOutputs) {
584
- throw new ValidationError("token outputs cannot be null", {
585
- field: "tokenOutputs"
586
- });
587
- }
588
- const outputsLenHashObj = sha256.create();
589
- const outputsLenBytes = new Uint8Array(4);
590
- new DataView(outputsLenBytes.buffer).setUint32(
591
- 0,
592
- tokenTransaction.tokenOutputs.length,
593
- false
594
- );
595
- outputsLenHashObj.update(outputsLenBytes);
596
- allHashes.push(outputsLenHashObj.digest());
597
- for (const [i, output] of tokenTransaction.tokenOutputs.entries()) {
598
- if (!output) {
599
- throw new ValidationError(`output cannot be null at index ${i}`, {
600
- field: `tokenOutputs[${i}]`,
601
- index: i
602
- });
603
- }
604
- const hashObj2 = sha256.create();
605
- if (output.id && !partialHash) {
606
- if (output.id.length === 0) {
607
- throw new ValidationError(`output ID at index ${i} cannot be empty`, {
608
- field: `tokenOutputs[${i}].id`,
609
- index: i
610
- });
611
- }
612
- hashObj2.update(new TextEncoder().encode(output.id));
613
- }
614
- if (output.ownerPublicKey) {
615
- if (output.ownerPublicKey.length === 0) {
616
- throw new ValidationError(
617
- `owner public key at index ${i} cannot be empty`,
618
- {
619
- field: `tokenOutputs[${i}].ownerPublicKey`,
620
- index: i
621
- }
622
- );
623
- }
624
- hashObj2.update(output.ownerPublicKey);
625
- }
626
- if (!partialHash) {
627
- const revPubKey = output.revocationCommitment;
628
- if (revPubKey) {
629
- if (revPubKey.length === 0) {
630
- throw new ValidationError(
631
- `revocation commitment at index ${i} cannot be empty`,
632
- {
633
- field: `tokenOutputs[${i}].revocationCommitment`,
634
- index: i
635
- }
636
- );
637
- }
638
- hashObj2.update(revPubKey);
639
- }
640
- const bondBytes = new Uint8Array(8);
641
- new DataView(bondBytes.buffer).setBigUint64(
642
- 0,
643
- BigInt(output.withdrawBondSats),
644
- false
645
- );
646
- hashObj2.update(bondBytes);
647
- const locktimeBytes = new Uint8Array(8);
648
- new DataView(locktimeBytes.buffer).setBigUint64(
649
- 0,
650
- BigInt(output.withdrawRelativeBlockLocktime),
651
- false
652
- );
653
- hashObj2.update(locktimeBytes);
654
- }
655
- if (!output.tokenPublicKey || output.tokenPublicKey.length === 0) {
656
- hashObj2.update(new Uint8Array(33));
657
- } else {
658
- hashObj2.update(output.tokenPublicKey);
659
- }
660
- if (!output.tokenIdentifier || output.tokenIdentifier.length === 0) {
661
- hashObj2.update(new Uint8Array(32));
662
- } else {
663
- hashObj2.update(output.tokenIdentifier);
664
- }
665
- if (output.tokenAmount) {
666
- if (output.tokenAmount.length === 0) {
667
- throw new ValidationError(
668
- `token amount at index ${i} cannot be empty`,
669
- {
670
- field: `tokenOutputs[${i}].tokenAmount`,
671
- index: i
672
- }
673
- );
674
- }
675
- if (output.tokenAmount.length > 16) {
676
- throw new ValidationError(
677
- `token amount at index ${i} exceeds maximum length`,
678
- {
679
- field: `tokenOutputs[${i}].tokenAmount`,
680
- value: output.tokenAmount,
681
- expectedLength: 16,
682
- actualLength: output.tokenAmount.length,
683
- index: i
684
- }
685
- );
686
- }
687
- hashObj2.update(output.tokenAmount);
688
- }
689
- allHashes.push(hashObj2.digest());
690
- }
691
- if (!tokenTransaction.sparkOperatorIdentityPublicKeys) {
692
- throw new ValidationError(
693
- "spark operator identity public keys cannot be null",
694
- {}
695
- );
696
- }
697
- const sortedPubKeys = [
698
- ...tokenTransaction.sparkOperatorIdentityPublicKeys || []
699
- ].sort((a, b) => {
700
- for (let i = 0; i < a.length && i < b.length; i++) {
701
- if (a[i] !== b[i]) return a[i] - b[i];
702
- }
703
- return a.length - b.length;
704
- });
705
- const operatorLenHashObj = sha256.create();
706
- const operatorLenBytes = new Uint8Array(4);
707
- new DataView(operatorLenBytes.buffer).setUint32(
708
- 0,
709
- sortedPubKeys.length,
710
- false
711
- );
712
- operatorLenHashObj.update(operatorLenBytes);
713
- allHashes.push(operatorLenHashObj.digest());
714
- for (const [i, pubKey] of sortedPubKeys.entries()) {
715
- if (!pubKey) {
716
- throw new ValidationError(
717
- `operator public key at index ${i} cannot be null`,
718
- {
719
- field: `sparkOperatorIdentityPublicKeys[${i}]`,
720
- index: i
721
- }
722
- );
723
- }
724
- if (pubKey.length === 0) {
725
- throw new ValidationError(
726
- `operator public key at index ${i} cannot be empty`,
727
- {
728
- field: `sparkOperatorIdentityPublicKeys[${i}]`,
729
- index: i
730
- }
731
- );
732
- }
733
- const hashObj2 = sha256.create();
734
- hashObj2.update(pubKey);
735
- allHashes.push(hashObj2.digest());
736
- }
737
- const hashObj = sha256.create();
738
- let networkBytes = new Uint8Array(4);
739
- new DataView(networkBytes.buffer).setUint32(
740
- 0,
741
- tokenTransaction.network.valueOf(),
742
- false
743
- // false for big-endian
744
- );
745
- hashObj.update(networkBytes);
746
- allHashes.push(hashObj.digest());
747
- const clientTimestampHashObj = sha256.create();
748
- const clientCreatedTs = tokenTransaction.clientCreatedTimestamp;
749
- if (!clientCreatedTs) {
750
- throw new ValidationError(
751
- "client created timestamp cannot be null for V1 token transactions",
752
- {
753
- field: "clientCreatedTimestamp"
754
- }
755
- );
756
- }
757
- const clientUnixTime = clientCreatedTs.getTime();
758
- const clientTimestampBytes = new Uint8Array(8);
759
- new DataView(clientTimestampBytes.buffer).setBigUint64(
760
- 0,
761
- BigInt(clientUnixTime),
762
- false
763
- );
764
- clientTimestampHashObj.update(clientTimestampBytes);
765
- allHashes.push(clientTimestampHashObj.digest());
766
- if (!partialHash) {
767
- const expiryHashObj = sha256.create();
768
- const expiryTimeBytes = new Uint8Array(8);
769
- const expiryUnixTime = tokenTransaction.expiryTime ? Math.floor(tokenTransaction.expiryTime.getTime() / 1e3) : 0;
770
- new DataView(expiryTimeBytes.buffer).setBigUint64(
771
- 0,
772
- BigInt(expiryUnixTime),
773
- false
774
- // false for big-endian
775
- );
776
- expiryHashObj.update(expiryTimeBytes);
777
- allHashes.push(expiryHashObj.digest());
778
- }
779
- const finalHashObj = sha256.create();
780
- const concatenatedHashes = new Uint8Array(
781
- allHashes.reduce((sum, hash) => sum + hash.length, 0)
782
- );
783
- let offset = 0;
784
- for (const hash of allHashes) {
785
- concatenatedHashes.set(hash, offset);
786
- offset += hash.length;
787
- }
788
- finalHashObj.update(concatenatedHashes);
789
- return finalHashObj.digest();
790
- }
791
- function hashOperatorSpecificTokenTransactionSignablePayload(payload) {
792
- if (!payload) {
793
- throw new ValidationError(
794
- "operator specific token transaction signable payload cannot be null",
795
- {
796
- field: "payload"
797
- }
798
- );
799
- }
800
- let allHashes = [];
801
- if (payload.finalTokenTransactionHash) {
802
- const hashObj2 = sha256.create();
803
- if (payload.finalTokenTransactionHash.length !== 32) {
804
- throw new ValidationError(`invalid final token transaction hash length`, {
805
- field: "finalTokenTransactionHash",
806
- value: payload.finalTokenTransactionHash,
807
- expectedLength: 32,
808
- actualLength: payload.finalTokenTransactionHash.length
809
- });
810
- }
811
- hashObj2.update(payload.finalTokenTransactionHash);
812
- allHashes.push(hashObj2.digest());
813
- }
814
- if (!payload.operatorIdentityPublicKey) {
815
- throw new ValidationError("operator identity public key cannot be null", {
816
- field: "operatorIdentityPublicKey"
817
- });
818
- }
819
- if (payload.operatorIdentityPublicKey.length === 0) {
820
- throw new ValidationError("operator identity public key cannot be empty", {
821
- field: "operatorIdentityPublicKey"
822
- });
823
- }
824
- const hashObj = sha256.create();
825
- hashObj.update(payload.operatorIdentityPublicKey);
826
- allHashes.push(hashObj.digest());
827
- const finalHashObj = sha256.create();
828
- const concatenatedHashes = new Uint8Array(
829
- allHashes.reduce((sum, hash) => sum + hash.length, 0)
830
- );
831
- let offset = 0;
832
- for (const hash of allHashes) {
833
- concatenatedHashes.set(hash, offset);
834
- offset += hash.length;
835
- }
836
- finalHashObj.update(concatenatedHashes);
837
- return finalHashObj.digest();
838
- }
839
-
840
- // src/utils/token-transaction-validation.ts
841
- function areByteArraysEqual(a, b) {
842
- if (a.length !== b.length) {
843
- return false;
844
- }
845
- return a.every((byte, index) => byte === b[index]);
846
- }
847
- function hasDuplicates(array) {
848
- return new Set(array).size !== array.length;
849
- }
850
- function validateTokenTransactionV0(finalTokenTransaction, partialTokenTransaction, signingOperators, keyshareInfo, expectedWithdrawBondSats, expectedWithdrawRelativeBlockLocktime, expectedThreshold) {
851
- if (finalTokenTransaction.network !== partialTokenTransaction.network) {
852
- throw new InternalValidationError(
853
- "Network mismatch in response token transaction",
854
- {
855
- value: finalTokenTransaction.network,
856
- expected: partialTokenTransaction.network
857
- }
858
- );
859
- }
860
- if (!finalTokenTransaction.tokenInputs) {
861
- throw new InternalValidationError(
862
- "Token inputs missing in final transaction",
863
- {
864
- value: finalTokenTransaction
865
- }
866
- );
867
- }
868
- if (!partialTokenTransaction.tokenInputs) {
869
- throw new InternalValidationError(
870
- "Token inputs missing in partial transaction",
871
- {
872
- value: partialTokenTransaction
873
- }
874
- );
875
- }
876
- if (finalTokenTransaction.tokenInputs.$case !== partialTokenTransaction.tokenInputs.$case) {
877
- throw new InternalValidationError(
878
- `Transaction type mismatch: final transaction has ${finalTokenTransaction.tokenInputs.$case}, partial transaction has ${partialTokenTransaction.tokenInputs.$case}`,
879
- {
880
- value: finalTokenTransaction.tokenInputs.$case,
881
- expected: partialTokenTransaction.tokenInputs.$case
882
- }
883
- );
884
- }
885
- if (finalTokenTransaction.sparkOperatorIdentityPublicKeys.length !== partialTokenTransaction.sparkOperatorIdentityPublicKeys.length) {
886
- throw new InternalValidationError(
887
- "Spark operator identity public keys count mismatch",
888
- {
889
- value: finalTokenTransaction.sparkOperatorIdentityPublicKeys.length,
890
- expected: partialTokenTransaction.sparkOperatorIdentityPublicKeys.length
891
- }
892
- );
893
- }
894
- if (partialTokenTransaction.tokenInputs.$case === "mintInput" && finalTokenTransaction.tokenInputs.$case === "mintInput") {
895
- const finalMintInput = finalTokenTransaction.tokenInputs.mintInput;
896
- const partialMintInput = partialTokenTransaction.tokenInputs.mintInput;
897
- if (!areByteArraysEqual(
898
- finalMintInput.issuerPublicKey,
899
- partialMintInput.issuerPublicKey
900
- )) {
901
- throw new InternalValidationError(
902
- "Issuer public key mismatch in mint input",
903
- {
904
- value: finalMintInput.issuerPublicKey.toString(),
905
- expected: partialMintInput.issuerPublicKey.toString()
906
- }
907
- );
908
- }
909
- } else if (partialTokenTransaction.tokenInputs.$case === "transferInput" && finalTokenTransaction.tokenInputs.$case === "transferInput") {
910
- const finalTransferInput = finalTokenTransaction.tokenInputs.transferInput;
911
- const partialTransferInput = partialTokenTransaction.tokenInputs.transferInput;
912
- if (finalTransferInput.outputsToSpend.length !== partialTransferInput.outputsToSpend.length) {
913
- throw new InternalValidationError(
914
- "Outputs to spend count mismatch in transfer input",
915
- {
916
- value: finalTransferInput.outputsToSpend.length,
917
- expected: partialTransferInput.outputsToSpend.length
918
- }
919
- );
920
- }
921
- for (let i = 0; i < finalTransferInput.outputsToSpend.length; i++) {
922
- const finalOutput = finalTransferInput.outputsToSpend[i];
923
- const partialOutput = partialTransferInput.outputsToSpend[i];
924
- if (!finalOutput) {
925
- throw new InternalValidationError(
926
- "Token output to spend missing in final transaction",
927
- {
928
- outputIndex: i,
929
- value: finalOutput
930
- }
931
- );
932
- }
933
- if (!partialOutput) {
934
- throw new InternalValidationError(
935
- "Token output to spend missing in partial transaction",
936
- {
937
- outputIndex: i,
938
- value: partialOutput
939
- }
940
- );
941
- }
942
- if (!areByteArraysEqual(
943
- finalOutput.prevTokenTransactionHash,
944
- partialOutput.prevTokenTransactionHash
945
- )) {
946
- throw new InternalValidationError(
947
- "Previous token transaction hash mismatch in transfer input",
948
- {
949
- outputIndex: i,
950
- value: finalOutput.prevTokenTransactionHash.toString(),
951
- expected: partialOutput.prevTokenTransactionHash.toString()
952
- }
953
- );
954
- }
955
- if (finalOutput.prevTokenTransactionVout !== partialOutput.prevTokenTransactionVout) {
956
- throw new InternalValidationError(
957
- "Previous token transaction vout mismatch in transfer input",
958
- {
959
- outputIndex: i,
960
- value: finalOutput.prevTokenTransactionVout,
961
- expected: partialOutput.prevTokenTransactionVout
962
- }
963
- );
964
- }
965
- }
966
- }
967
- if (finalTokenTransaction.tokenOutputs.length !== partialTokenTransaction.tokenOutputs.length) {
968
- throw new InternalValidationError("Token outputs count mismatch", {
969
- value: finalTokenTransaction.tokenOutputs.length,
970
- expected: partialTokenTransaction.tokenOutputs.length
971
- });
972
- }
973
- for (let i = 0; i < finalTokenTransaction.tokenOutputs.length; i++) {
974
- const finalOutput = finalTokenTransaction.tokenOutputs[i];
975
- const partialOutput = partialTokenTransaction.tokenOutputs[i];
976
- if (!finalOutput) {
977
- throw new InternalValidationError(
978
- "Token output missing in final transaction",
979
- {
980
- outputIndex: i,
981
- value: finalOutput
982
- }
983
- );
984
- }
985
- if (!partialOutput) {
986
- throw new InternalValidationError(
987
- "Token output missing in partial transaction",
988
- {
989
- outputIndex: i,
990
- value: partialOutput
991
- }
992
- );
993
- }
994
- if (!areByteArraysEqual(
995
- finalOutput.ownerPublicKey,
996
- partialOutput.ownerPublicKey
997
- )) {
998
- throw new InternalValidationError(
999
- "Owner public key mismatch in token output",
1000
- {
1001
- outputIndex: i,
1002
- value: finalOutput.ownerPublicKey.toString(),
1003
- expected: partialOutput.ownerPublicKey.toString()
1004
- }
1005
- );
1006
- }
1007
- if (finalOutput.tokenPublicKey !== void 0 && partialOutput.tokenPublicKey !== void 0 && !areByteArraysEqual(
1008
- finalOutput.tokenPublicKey,
1009
- partialOutput.tokenPublicKey
1010
- )) {
1011
- throw new InternalValidationError(
1012
- "Token public key mismatch in token output",
1013
- {
1014
- outputIndex: i,
1015
- value: finalOutput.tokenPublicKey?.toString(),
1016
- expected: partialOutput.tokenPublicKey?.toString()
1017
- }
1018
- );
1019
- }
1020
- if (!areByteArraysEqual(finalOutput.tokenAmount, partialOutput.tokenAmount)) {
1021
- throw new InternalValidationError(
1022
- "Token amount mismatch in token output",
1023
- {
1024
- outputIndex: i,
1025
- value: finalOutput.tokenAmount.toString(),
1026
- expected: partialOutput.tokenAmount.toString()
1027
- }
1028
- );
1029
- }
1030
- if (finalOutput.withdrawBondSats !== void 0) {
1031
- if (finalOutput.withdrawBondSats !== expectedWithdrawBondSats) {
1032
- throw new InternalValidationError(
1033
- "Withdraw bond sats mismatch in token output",
1034
- {
1035
- outputIndex: i,
1036
- value: finalOutput.withdrawBondSats,
1037
- expected: expectedWithdrawBondSats
1038
- }
1039
- );
1040
- }
1041
- }
1042
- if (finalOutput.withdrawRelativeBlockLocktime !== void 0) {
1043
- if (finalOutput.withdrawRelativeBlockLocktime !== expectedWithdrawRelativeBlockLocktime) {
1044
- throw new InternalValidationError(
1045
- "Withdraw relative block locktime mismatch in token output",
1046
- {
1047
- outputIndex: i,
1048
- value: finalOutput.withdrawRelativeBlockLocktime,
1049
- expected: expectedWithdrawRelativeBlockLocktime
1050
- }
1051
- );
1052
- }
1053
- }
1054
- if (keyshareInfo.threshold !== expectedThreshold) {
1055
- throw new InternalValidationError(
1056
- "Threshold mismatch: expected " + expectedThreshold + " but got " + keyshareInfo.threshold,
1057
- {
1058
- field: "threshold",
1059
- value: keyshareInfo.threshold,
1060
- expected: expectedThreshold
1061
- }
1062
- );
1063
- }
1064
- }
1065
- if (keyshareInfo.ownerIdentifiers.length !== Object.keys(signingOperators).length) {
1066
- throw new InternalValidationError(
1067
- `Keyshare operator count (${keyshareInfo.ownerIdentifiers.length}) does not match signing operator count (${Object.keys(signingOperators).length})`,
1068
- {
1069
- keyshareInfo: keyshareInfo.ownerIdentifiers.length,
1070
- signingOperators: Object.keys(signingOperators).length
1071
- }
1072
- );
1073
- }
1074
- if (hasDuplicates(keyshareInfo.ownerIdentifiers)) {
1075
- throw new InternalValidationError(
1076
- "Duplicate ownerIdentifiers found in keyshareInfo",
1077
- {
1078
- keyshareInfo: keyshareInfo.ownerIdentifiers
1079
- }
1080
- );
1081
- }
1082
- for (const identifier of keyshareInfo.ownerIdentifiers) {
1083
- if (!signingOperators[identifier]) {
1084
- throw new InternalValidationError(
1085
- `Keyshare operator ${identifier} not found in signing operator list`,
1086
- {
1087
- keyshareInfo: identifier,
1088
- signingOperators: Object.keys(signingOperators)
1089
- }
1090
- );
1091
- }
1092
- }
1093
- }
1094
- function validateTokenTransaction(finalTokenTransaction, partialTokenTransaction, signingOperators, keyshareInfo, expectedWithdrawBondSats, expectedWithdrawRelativeBlockLocktime, expectedThreshold) {
1095
- if (finalTokenTransaction.network !== partialTokenTransaction.network) {
1096
- throw new InternalValidationError(
1097
- "Network mismatch in response token transaction",
1098
- {
1099
- value: finalTokenTransaction.network,
1100
- expected: partialTokenTransaction.network
1101
- }
1102
- );
1103
- }
1104
- if (!finalTokenTransaction.tokenInputs) {
1105
- throw new InternalValidationError(
1106
- "Token inputs missing in final transaction",
1107
- {
1108
- value: finalTokenTransaction
1109
- }
1110
- );
1111
- }
1112
- if (!partialTokenTransaction.tokenInputs) {
1113
- throw new InternalValidationError(
1114
- "Token inputs missing in partial transaction",
1115
- {
1116
- value: partialTokenTransaction
1117
- }
1118
- );
1119
- }
1120
- if (finalTokenTransaction.tokenInputs.$case !== partialTokenTransaction.tokenInputs.$case) {
1121
- throw new InternalValidationError(
1122
- `Transaction type mismatch: final transaction has ${finalTokenTransaction.tokenInputs.$case}, partial transaction has ${partialTokenTransaction.tokenInputs.$case}`,
1123
- {
1124
- value: finalTokenTransaction.tokenInputs.$case,
1125
- expected: partialTokenTransaction.tokenInputs.$case
1126
- }
1127
- );
1128
- }
1129
- if (finalTokenTransaction.sparkOperatorIdentityPublicKeys.length !== partialTokenTransaction.sparkOperatorIdentityPublicKeys.length) {
1130
- throw new InternalValidationError(
1131
- "Spark operator identity public keys count mismatch",
1132
- {
1133
- value: finalTokenTransaction.sparkOperatorIdentityPublicKeys.length,
1134
- expected: partialTokenTransaction.sparkOperatorIdentityPublicKeys.length
1135
- }
1136
- );
1137
- }
1138
- if (partialTokenTransaction.tokenInputs.$case === "mintInput" && finalTokenTransaction.tokenInputs.$case === "mintInput") {
1139
- const finalMintInput = finalTokenTransaction.tokenInputs.mintInput;
1140
- const partialMintInput = partialTokenTransaction.tokenInputs.mintInput;
1141
- if (!areByteArraysEqual(
1142
- finalMintInput.issuerPublicKey,
1143
- partialMintInput.issuerPublicKey
1144
- )) {
1145
- throw new InternalValidationError(
1146
- "Issuer public key mismatch in mint input",
1147
- {
1148
- value: finalMintInput.issuerPublicKey.toString(),
1149
- expected: partialMintInput.issuerPublicKey.toString()
1150
- }
1151
- );
1152
- }
1153
- } else if (partialTokenTransaction.tokenInputs.$case === "transferInput" && finalTokenTransaction.tokenInputs.$case === "transferInput") {
1154
- const finalTransferInput = finalTokenTransaction.tokenInputs.transferInput;
1155
- const partialTransferInput = partialTokenTransaction.tokenInputs.transferInput;
1156
- if (finalTransferInput.outputsToSpend.length !== partialTransferInput.outputsToSpend.length) {
1157
- throw new InternalValidationError(
1158
- "Outputs to spend count mismatch in transfer input",
1159
- {
1160
- value: finalTransferInput.outputsToSpend.length,
1161
- expected: partialTransferInput.outputsToSpend.length
1162
- }
1163
- );
1164
- }
1165
- for (let i = 0; i < finalTransferInput.outputsToSpend.length; i++) {
1166
- const finalOutput = finalTransferInput.outputsToSpend[i];
1167
- const partialOutput = partialTransferInput.outputsToSpend[i];
1168
- if (!finalOutput) {
1169
- throw new InternalValidationError(
1170
- "Token output to spend missing in final transaction",
1171
- {
1172
- outputIndex: i,
1173
- value: finalOutput
1174
- }
1175
- );
1176
- }
1177
- if (!partialOutput) {
1178
- throw new InternalValidationError(
1179
- "Token output to spend missing in partial transaction",
1180
- {
1181
- outputIndex: i,
1182
- value: partialOutput
1183
- }
1184
- );
1185
- }
1186
- if (!areByteArraysEqual(
1187
- finalOutput.prevTokenTransactionHash,
1188
- partialOutput.prevTokenTransactionHash
1189
- )) {
1190
- throw new InternalValidationError(
1191
- "Previous token transaction hash mismatch in transfer input",
1192
- {
1193
- outputIndex: i,
1194
- value: finalOutput.prevTokenTransactionHash.toString(),
1195
- expected: partialOutput.prevTokenTransactionHash.toString()
1196
- }
1197
- );
1198
- }
1199
- if (finalOutput.prevTokenTransactionVout !== partialOutput.prevTokenTransactionVout) {
1200
- throw new InternalValidationError(
1201
- "Previous token transaction vout mismatch in transfer input",
1202
- {
1203
- outputIndex: i,
1204
- value: finalOutput.prevTokenTransactionVout,
1205
- expected: partialOutput.prevTokenTransactionVout
1206
- }
1207
- );
1208
- }
1209
- }
1210
- }
1211
- if (finalTokenTransaction.tokenOutputs.length !== partialTokenTransaction.tokenOutputs.length) {
1212
- throw new InternalValidationError("Token outputs count mismatch", {
1213
- value: finalTokenTransaction.tokenOutputs.length,
1214
- expected: partialTokenTransaction.tokenOutputs.length
1215
- });
1216
- }
1217
- for (let i = 0; i < finalTokenTransaction.tokenOutputs.length; i++) {
1218
- const finalOutput = finalTokenTransaction.tokenOutputs[i];
1219
- const partialOutput = partialTokenTransaction.tokenOutputs[i];
1220
- if (!finalOutput) {
1221
- throw new InternalValidationError(
1222
- "Token output missing in final transaction",
1223
- {
1224
- outputIndex: i,
1225
- value: finalOutput
1226
- }
1227
- );
1228
- }
1229
- if (!partialOutput) {
1230
- throw new InternalValidationError(
1231
- "Token output missing in partial transaction",
1232
- {
1233
- outputIndex: i,
1234
- value: partialOutput
1235
- }
1236
- );
1237
- }
1238
- if (!areByteArraysEqual(
1239
- finalOutput.ownerPublicKey,
1240
- partialOutput.ownerPublicKey
1241
- )) {
1242
- throw new InternalValidationError(
1243
- "Owner public key mismatch in token output",
1244
- {
1245
- outputIndex: i,
1246
- value: finalOutput.ownerPublicKey.toString(),
1247
- expected: partialOutput.ownerPublicKey.toString()
1248
- }
1249
- );
1250
- }
1251
- if (finalOutput.tokenPublicKey !== void 0 && partialOutput.tokenPublicKey !== void 0 && !areByteArraysEqual(
1252
- finalOutput.tokenPublicKey,
1253
- partialOutput.tokenPublicKey
1254
- )) {
1255
- throw new InternalValidationError(
1256
- "Token public key mismatch in token output",
1257
- {
1258
- outputIndex: i,
1259
- value: finalOutput.tokenPublicKey.toString(),
1260
- expected: partialOutput.tokenPublicKey.toString()
1261
- }
1262
- );
1263
- }
1264
- if (!areByteArraysEqual(finalOutput.tokenAmount, partialOutput.tokenAmount)) {
1265
- throw new InternalValidationError(
1266
- "Token amount mismatch in token output",
1267
- {
1268
- outputIndex: i,
1269
- value: finalOutput.tokenAmount.toString(),
1270
- expected: partialOutput.tokenAmount.toString()
1271
- }
1272
- );
1273
- }
1274
- if (finalOutput.withdrawBondSats !== void 0) {
1275
- if (finalOutput.withdrawBondSats !== expectedWithdrawBondSats) {
1276
- throw new InternalValidationError(
1277
- "Withdraw bond sats mismatch in token output",
1278
- {
1279
- outputIndex: i,
1280
- value: finalOutput.withdrawBondSats,
1281
- expected: expectedWithdrawBondSats
1282
- }
1283
- );
1284
- }
1285
- }
1286
- if (finalOutput.withdrawRelativeBlockLocktime !== void 0) {
1287
- if (finalOutput.withdrawRelativeBlockLocktime !== expectedWithdrawRelativeBlockLocktime) {
1288
- throw new InternalValidationError(
1289
- "Withdraw relative block locktime mismatch in token output",
1290
- {
1291
- outputIndex: i,
1292
- value: finalOutput.withdrawRelativeBlockLocktime,
1293
- expected: expectedWithdrawRelativeBlockLocktime
1294
- }
1295
- );
1296
- }
1297
- }
1298
- if (keyshareInfo.threshold !== expectedThreshold) {
1299
- throw new InternalValidationError(
1300
- "Threshold mismatch: expected " + expectedThreshold + " but got " + keyshareInfo.threshold,
1301
- {
1302
- field: "threshold",
1303
- value: keyshareInfo.threshold,
1304
- expected: expectedThreshold
1305
- }
1306
- );
1307
- }
1308
- }
1309
- if (keyshareInfo.ownerIdentifiers.length !== Object.keys(signingOperators).length) {
1310
- throw new InternalValidationError(
1311
- `Keyshare operator count (${keyshareInfo.ownerIdentifiers.length}) does not match signing operator count (${Object.keys(signingOperators).length})`,
1312
- {
1313
- keyshareInfo: keyshareInfo.ownerIdentifiers.length,
1314
- signingOperators: Object.keys(signingOperators).length
1315
- }
1316
- );
1317
- }
1318
- if (hasDuplicates(keyshareInfo.ownerIdentifiers)) {
1319
- throw new InternalValidationError(
1320
- "Duplicate ownerIdentifiers found in keyshareInfo",
1321
- {
1322
- keyshareInfo: keyshareInfo.ownerIdentifiers
1323
- }
1324
- );
1325
- }
1326
- for (const identifier of keyshareInfo.ownerIdentifiers) {
1327
- if (!signingOperators[identifier]) {
1328
- throw new InternalValidationError(
1329
- `Keyshare operator ${identifier} not found in signing operator list`,
1330
- {
1331
- keyshareInfo: identifier,
1332
- signingOperators: Object.keys(signingOperators)
1333
- }
1334
- );
1335
- }
1336
- }
1337
- if (finalTokenTransaction.clientCreatedTimestamp.getTime() !== partialTokenTransaction.clientCreatedTimestamp.getTime()) {
1338
- throw new InternalValidationError("Client created timestamp mismatch", {
1339
- value: finalTokenTransaction.clientCreatedTimestamp,
1340
- expected: partialTokenTransaction.clientCreatedTimestamp
1341
- });
1342
- }
1343
- }
1344
-
1345
- // src/services/token-transactions.ts
1346
- import { hexToBytes } from "@noble/hashes/utils";
1347
-
1348
- // src/utils/token-keyshares.ts
1349
- import { secp256k1 } from "@noble/curves/secp256k1";
1350
- function recoverRevocationSecretFromKeyshares(keyshares, threshold) {
1351
- const shares = keyshares.map((keyshare) => ({
1352
- fieldModulus: BigInt("0x" + secp256k1.CURVE.n.toString(16)),
1353
- // secp256k1 curve order
1354
- threshold,
1355
- index: BigInt(keyshare.operatorIndex),
1356
- share: BigInt(
1357
- "0x" + Buffer.from(keyshare.keyshare.keyshare).toString("hex")
1358
- ),
1359
- proofs: []
1360
- }));
1361
- const recoveredSecret = recoverSecret(shares);
1362
- return bigIntToPrivateKey(recoveredSecret);
1363
- }
1364
-
1365
- // src/services/token-transactions.ts
1366
- var MAX_TOKEN_OUTPUTS = 500;
1367
- var TokenTransactionService = class {
1368
- config;
1369
- connectionManager;
1370
- constructor(config, connectionManager) {
1371
- this.config = config;
1372
- this.connectionManager = connectionManager;
1373
- }
1374
- async tokenTransfer(tokenOutputs, receiverOutputs, outputSelectionStrategy = "SMALL_FIRST", selectedOutputs) {
1375
- if (receiverOutputs.length === 0) {
1376
- throw new ValidationError("No receiver outputs provided", {
1377
- field: "receiverOutputs",
1378
- value: receiverOutputs,
1379
- expected: "Non-empty array"
1380
- });
1381
- }
1382
- const totalTokenAmount = receiverOutputs.reduce(
1383
- (sum, transfer) => sum + transfer.tokenAmount,
1384
- 0n
1385
- );
1386
- let outputsToUse;
1387
- if (selectedOutputs) {
1388
- outputsToUse = selectedOutputs;
1389
- if (!checkIfSelectedOutputsAreAvailable(
1390
- outputsToUse,
1391
- tokenOutputs,
1392
- hexToBytes(receiverOutputs[0].tokenPublicKey)
1393
- )) {
1394
- throw new ValidationError(
1395
- "One or more selected TTXOs are not available",
1396
- {
1397
- field: "selectedOutputs",
1398
- value: selectedOutputs,
1399
- expected: "Available TTXOs"
1400
- }
1401
- );
1402
- }
1403
- } else {
1404
- outputsToUse = this.selectTokenOutputs(
1405
- tokenOutputs.get(receiverOutputs[0].tokenPublicKey),
1406
- totalTokenAmount,
1407
- outputSelectionStrategy
1408
- );
1409
- }
1410
- if (outputsToUse.length > MAX_TOKEN_OUTPUTS) {
1411
- const availableOutputs = tokenOutputs.get(
1412
- receiverOutputs[0].tokenPublicKey
1413
- );
1414
- const sortedOutputs = [...availableOutputs];
1415
- this.sortTokenOutputsByStrategy(sortedOutputs, outputSelectionStrategy);
1416
- const maxOutputsToUse = sortedOutputs.slice(0, MAX_TOKEN_OUTPUTS);
1417
- const maxAmount = calculateAvailableTokenAmount(maxOutputsToUse);
1418
- throw new ValidationError(
1419
- `Cannot transfer more than ${MAX_TOKEN_OUTPUTS} TTXOs in a single transaction (${outputsToUse.length} selected). Maximum transferable amount is: ${maxAmount}`,
1420
- {
1421
- field: "outputsToUse",
1422
- value: outputsToUse.length,
1423
- expected: `Less than or equal to ${MAX_TOKEN_OUTPUTS}, with maximum transferable amount of ${maxAmount}`
1424
- }
1425
- );
1426
- }
1427
- const tokenOutputData = receiverOutputs.map((transfer) => {
1428
- const receiverAddress = decodeSparkAddress(
1429
- transfer.receiverSparkAddress,
1430
- this.config.getNetworkType()
1431
- );
1432
- return {
1433
- receiverSparkAddress: hexToBytes(receiverAddress.identityPublicKey),
1434
- tokenPublicKey: hexToBytes(transfer.tokenPublicKey),
1435
- tokenAmount: transfer.tokenAmount
1436
- };
1437
- });
1438
- let tokenTransaction;
1439
- if (this.config.getTokenTransactionVersion() === "V0") {
1440
- tokenTransaction = await this.constructTransferTokenTransactionV0(
1441
- outputsToUse,
1442
- tokenOutputData
1443
- );
1444
- } else {
1445
- tokenTransaction = await this.constructTransferTokenTransaction(
1446
- outputsToUse,
1447
- tokenOutputData
1448
- );
1449
- }
1450
- const txId = await this.broadcastTokenTransaction(
1451
- tokenTransaction,
1452
- outputsToUse.map((output) => output.output.ownerPublicKey),
1453
- outputsToUse.map((output) => output.output.revocationCommitment)
1454
- );
1455
- return txId;
1456
- }
1457
- async constructTransferTokenTransactionV0(selectedOutputs, tokenOutputData) {
1458
- selectedOutputs.sort(
1459
- (a, b) => a.previousTransactionVout - b.previousTransactionVout
1460
- );
1461
- const availableTokenAmount = calculateAvailableTokenAmount(selectedOutputs);
1462
- const totalRequestedAmount = tokenOutputData.reduce(
1463
- (sum, output) => sum + output.tokenAmount,
1464
- 0n
1465
- );
1466
- const tokenOutputs = tokenOutputData.map((output) => ({
1467
- ownerPublicKey: output.receiverSparkAddress,
1468
- tokenPublicKey: output.tokenPublicKey,
1469
- tokenAmount: numberToBytesBE(output.tokenAmount, 16)
1470
- }));
1471
- if (availableTokenAmount > totalRequestedAmount) {
1472
- const changeAmount = availableTokenAmount - totalRequestedAmount;
1473
- const firstTokenPublicKey = tokenOutputData[0].tokenPublicKey;
1474
- tokenOutputs.push({
1475
- ownerPublicKey: await this.config.signer.getIdentityPublicKey(),
1476
- tokenPublicKey: firstTokenPublicKey,
1477
- tokenAmount: numberToBytesBE(changeAmount, 16)
1478
- });
1479
- }
1480
- return {
1481
- network: this.config.getNetworkProto(),
1482
- tokenInputs: {
1483
- $case: "transferInput",
1484
- transferInput: {
1485
- outputsToSpend: selectedOutputs.map((output) => ({
1486
- prevTokenTransactionHash: output.previousTransactionHash,
1487
- prevTokenTransactionVout: output.previousTransactionVout
1488
- }))
1489
- }
1490
- },
1491
- tokenOutputs,
1492
- sparkOperatorIdentityPublicKeys: this.collectOperatorIdentityPublicKeys()
1493
- };
1494
- }
1495
- async constructTransferTokenTransaction(selectedOutputs, tokenOutputData) {
1496
- selectedOutputs.sort(
1497
- (a, b) => a.previousTransactionVout - b.previousTransactionVout
1498
- );
1499
- const availableTokenAmount = calculateAvailableTokenAmount(selectedOutputs);
1500
- const totalRequestedAmount = tokenOutputData.reduce(
1501
- (sum, output) => sum + output.tokenAmount,
1502
- 0n
1503
- );
1504
- const tokenOutputs = tokenOutputData.map((output) => ({
1505
- ownerPublicKey: output.receiverSparkAddress,
1506
- tokenPublicKey: output.tokenPublicKey,
1507
- tokenAmount: numberToBytesBE(output.tokenAmount, 16)
1508
- }));
1509
- if (availableTokenAmount > totalRequestedAmount) {
1510
- const changeAmount = availableTokenAmount - totalRequestedAmount;
1511
- const firstTokenPublicKey = tokenOutputData[0].tokenPublicKey;
1512
- tokenOutputs.push({
1513
- ownerPublicKey: await this.config.signer.getIdentityPublicKey(),
1514
- tokenPublicKey: firstTokenPublicKey,
1515
- tokenAmount: numberToBytesBE(changeAmount, 16)
1516
- });
1517
- }
1518
- return {
1519
- version: 1,
1520
- network: this.config.getNetworkProto(),
1521
- tokenInputs: {
1522
- $case: "transferInput",
1523
- transferInput: {
1524
- outputsToSpend: selectedOutputs.map((output) => ({
1525
- prevTokenTransactionHash: output.previousTransactionHash,
1526
- prevTokenTransactionVout: output.previousTransactionVout
1527
- }))
1528
- }
1529
- },
1530
- tokenOutputs,
1531
- sparkOperatorIdentityPublicKeys: this.collectOperatorIdentityPublicKeys(),
1532
- expiryTime: void 0,
1533
- clientCreatedTimestamp: /* @__PURE__ */ new Date()
1534
- };
1535
- }
1536
- collectOperatorIdentityPublicKeys() {
1537
- const operatorKeys = [];
1538
- for (const [_, operator] of Object.entries(
1539
- this.config.getSigningOperators()
1540
- )) {
1541
- operatorKeys.push(hexToBytes(operator.identityPublicKey));
1542
- }
1543
- return operatorKeys;
1544
- }
1545
- async broadcastTokenTransaction(tokenTransaction, outputsToSpendSigningPublicKeys, outputsToSpendCommitments) {
1546
- const signingOperators = this.config.getSigningOperators();
1547
- if (!isTokenTransaction(tokenTransaction)) {
1548
- return this.broadcastTokenTransactionV0(
1549
- tokenTransaction,
1550
- signingOperators,
1551
- outputsToSpendSigningPublicKeys,
1552
- outputsToSpendCommitments
1553
- );
1554
- } else {
1555
- return this.broadcastTokenTransactionV1(
1556
- tokenTransaction,
1557
- signingOperators,
1558
- outputsToSpendSigningPublicKeys,
1559
- outputsToSpendCommitments
1560
- );
1561
- }
1562
- }
1563
- async broadcastTokenTransactionV0(tokenTransaction, signingOperators, outputsToSpendSigningPublicKeys, outputsToSpendCommitments) {
1564
- const { finalTokenTransaction, finalTokenTransactionHash, threshold } = await this.startTokenTransactionV0(
1565
- tokenTransaction,
1566
- signingOperators,
1567
- outputsToSpendSigningPublicKeys,
1568
- outputsToSpendCommitments
1569
- );
1570
- const { successfulSignatures } = await this.signTokenTransactionV0(
1571
- finalTokenTransaction,
1572
- finalTokenTransactionHash,
1573
- signingOperators
1574
- );
1575
- if (finalTokenTransaction.tokenInputs.$case === "transferInput") {
1576
- const outputsToSpend = finalTokenTransaction.tokenInputs.transferInput.outputsToSpend;
1577
- const errors = [];
1578
- const revocationSecrets = [];
1579
- for (let outputIndex = 0; outputIndex < outputsToSpend.length; outputIndex++) {
1580
- const outputKeyshares = successfulSignatures.map(({ identifier, response }) => ({
1581
- operatorIndex: parseInt(identifier, 16),
1582
- keyshare: response.revocationKeyshares[outputIndex]
1583
- }));
1584
- if (outputKeyshares.length < threshold) {
1585
- errors.push(
1586
- new ValidationError("Insufficient keyshares", {
1587
- field: "outputKeyshares",
1588
- value: outputKeyshares.length,
1589
- expected: threshold,
1590
- index: outputIndex
1591
- })
1592
- );
1593
- }
1594
- const seenIndices = /* @__PURE__ */ new Set();
1595
- for (const { operatorIndex } of outputKeyshares) {
1596
- if (seenIndices.has(operatorIndex)) {
1597
- errors.push(
1598
- new ValidationError("Duplicate operator index", {
1599
- field: "outputKeyshares",
1600
- value: operatorIndex,
1601
- expected: "Unique operator index",
1602
- index: outputIndex
1603
- })
1604
- );
1605
- }
1606
- seenIndices.add(operatorIndex);
1607
- }
1608
- const revocationSecret = recoverRevocationSecretFromKeyshares(
1609
- outputKeyshares,
1610
- threshold
1611
- );
1612
- const derivedRevocationCommitment = secp256k12.getPublicKey(
1613
- revocationSecret,
1614
- true
1615
- );
1616
- if (!outputsToSpendCommitments || !outputsToSpendCommitments[outputIndex] || !derivedRevocationCommitment.every(
1617
- (byte, i) => byte === outputsToSpendCommitments[outputIndex][i]
1618
- )) {
1619
- errors.push(
1620
- new InternalValidationError(
1621
- "Revocation commitment verification failed",
1622
- {
1623
- field: "revocationCommitment",
1624
- value: derivedRevocationCommitment,
1625
- expected: bytesToHex(outputsToSpendCommitments[outputIndex]),
1626
- outputIndex
1627
- }
1628
- )
1629
- );
1630
- }
1631
- revocationSecrets.push({
1632
- inputIndex: outputIndex,
1633
- revocationSecret
1634
- });
1635
- }
1636
- if (errors.length > 0) {
1637
- throw new ValidationError(
1638
- "Multiple validation errors occurred across outputs",
1639
- {
1640
- field: "outputValidation",
1641
- value: errors
1642
- }
1643
- );
1644
- }
1645
- await this.finalizeTokenTransaction(
1646
- finalTokenTransaction,
1647
- revocationSecrets,
1648
- threshold
1649
- );
1650
- }
1651
- return bytesToHex(finalTokenTransactionHash);
1652
- }
1653
- async broadcastTokenTransactionV1(tokenTransaction, signingOperators, outputsToSpendSigningPublicKeys, outputsToSpendCommitments) {
1654
- const { finalTokenTransaction, finalTokenTransactionHash, threshold } = await this.startTokenTransaction(
1655
- tokenTransaction,
1656
- signingOperators,
1657
- outputsToSpendSigningPublicKeys,
1658
- outputsToSpendCommitments
1659
- );
1660
- await this.signTokenTransaction(
1661
- finalTokenTransaction,
1662
- finalTokenTransactionHash,
1663
- signingOperators
1664
- );
1665
- return bytesToHex(finalTokenTransactionHash);
1666
- }
1667
- async startTokenTransactionV0(tokenTransaction, signingOperators, outputsToSpendSigningPublicKeys, outputsToSpendCommitments) {
1668
- const sparkClient = await this.connectionManager.createSparkClient(
1669
- this.config.getCoordinatorAddress()
1670
- );
1671
- const partialTokenTransactionHash = hashTokenTransactionV0(
1672
- tokenTransaction,
1673
- true
1674
- );
1675
- const ownerSignaturesWithIndex = [];
1676
- if (tokenTransaction.tokenInputs.$case === "mintInput") {
1677
- const issuerPublicKey = tokenTransaction.tokenInputs.mintInput.issuerPublicKey;
1678
- if (!issuerPublicKey) {
1679
- throw new ValidationError("Invalid mint input", {
1680
- field: "issuerPublicKey",
1681
- value: null,
1682
- expected: "Non-null issuer public key"
1683
- });
1684
- }
1685
- const ownerSignature = await this.signMessageWithKey(
1686
- partialTokenTransactionHash,
1687
- issuerPublicKey
1688
- );
1689
- ownerSignaturesWithIndex.push({
1690
- signature: ownerSignature,
1691
- inputIndex: 0
1692
- });
1693
- } else if (tokenTransaction.tokenInputs.$case === "transferInput") {
1694
- if (!outputsToSpendSigningPublicKeys || !outputsToSpendCommitments) {
1695
- throw new ValidationError("Invalid transfer input", {
1696
- field: "outputsToSpend",
1697
- value: {
1698
- signingPublicKeys: outputsToSpendSigningPublicKeys,
1699
- revocationPublicKeys: outputsToSpendCommitments
1700
- },
1701
- expected: "Non-null signing and revocation public keys"
1702
- });
1703
- }
1704
- for (const [i, key] of outputsToSpendSigningPublicKeys.entries()) {
1705
- if (!key) {
1706
- throw new ValidationError("Invalid signing key", {
1707
- field: "outputsToSpendSigningPublicKeys",
1708
- value: i,
1709
- expected: "Non-null signing key"
1710
- });
1711
- }
1712
- const ownerSignature = await this.signMessageWithKey(
1713
- partialTokenTransactionHash,
1714
- key
1715
- );
1716
- ownerSignaturesWithIndex.push({
1717
- signature: ownerSignature,
1718
- inputIndex: i
1719
- });
1720
- }
1721
- }
1722
- const startResponse = await sparkClient.start_token_transaction(
1723
- {
1724
- identityPublicKey: await this.config.signer.getIdentityPublicKey(),
1725
- partialTokenTransaction: tokenTransaction,
1726
- tokenTransactionSignatures: {
1727
- ownerSignatures: ownerSignaturesWithIndex
1728
- }
1729
- },
1730
- {
1731
- retry: true,
1732
- retryableStatuses: ["UNKNOWN", "UNAVAILABLE", "CANCELLED", "INTERNAL"],
1733
- retryMaxAttempts: 3
1734
- }
1735
- );
1736
- if (!startResponse.finalTokenTransaction) {
1737
- throw new Error("Final token transaction missing in start response");
1738
- }
1739
- if (!startResponse.keyshareInfo) {
1740
- throw new Error("Keyshare info missing in start response");
1741
- }
1742
- validateTokenTransactionV0(
1743
- startResponse.finalTokenTransaction,
1744
- tokenTransaction,
1745
- signingOperators,
1746
- startResponse.keyshareInfo,
1747
- this.config.getExpectedWithdrawBondSats(),
1748
- this.config.getExpectedWithdrawRelativeBlockLocktime(),
1749
- this.config.getThreshold()
1750
- );
1751
- const finalTokenTransaction = startResponse.finalTokenTransaction;
1752
- const finalTokenTransactionHash = hashTokenTransactionV0(
1753
- finalTokenTransaction,
1754
- false
1755
- );
1756
- return {
1757
- finalTokenTransaction,
1758
- finalTokenTransactionHash,
1759
- threshold: startResponse.keyshareInfo.threshold
1760
- };
1761
- }
1762
- async startTokenTransaction(tokenTransaction, signingOperators, outputsToSpendSigningPublicKeys, outputsToSpendCommitments) {
1763
- const sparkClient = await this.connectionManager.createSparkTokenClient(
1764
- this.config.getCoordinatorAddress()
1765
- );
1766
- const partialTokenTransactionHash = hashTokenTransaction(
1767
- tokenTransaction,
1768
- true
1769
- );
1770
- const ownerSignaturesWithIndex = [];
1771
- if (tokenTransaction.tokenInputs.$case === "mintInput") {
1772
- const issuerPublicKey = tokenTransaction.tokenInputs.mintInput.issuerPublicKey;
1773
- if (!issuerPublicKey) {
1774
- throw new ValidationError("Invalid mint input", {
1775
- field: "issuerPublicKey",
1776
- value: null,
1777
- expected: "Non-null issuer public key"
1778
- });
1779
- }
1780
- const ownerSignature = await this.signMessageWithKey(
1781
- partialTokenTransactionHash,
1782
- issuerPublicKey
1783
- );
1784
- ownerSignaturesWithIndex.push({
1785
- signature: ownerSignature,
1786
- inputIndex: 0
1787
- });
1788
- } else if (tokenTransaction.tokenInputs.$case === "transferInput") {
1789
- if (!outputsToSpendSigningPublicKeys || !outputsToSpendCommitments) {
1790
- throw new ValidationError("Invalid transfer input", {
1791
- field: "outputsToSpend",
1792
- value: {
1793
- signingPublicKeys: outputsToSpendSigningPublicKeys,
1794
- revocationPublicKeys: outputsToSpendCommitments
1795
- },
1796
- expected: "Non-null signing and revocation public keys"
1797
- });
1798
- }
1799
- for (const [i, key] of outputsToSpendSigningPublicKeys.entries()) {
1800
- if (!key) {
1801
- throw new ValidationError("Invalid signing key", {
1802
- field: "outputsToSpendSigningPublicKeys",
1803
- value: i,
1804
- expected: "Non-null signing key"
1805
- });
1806
- }
1807
- const ownerSignature = await this.signMessageWithKey(
1808
- partialTokenTransactionHash,
1809
- key
1810
- );
1811
- ownerSignaturesWithIndex.push({
1812
- signature: ownerSignature,
1813
- inputIndex: i
1814
- });
1815
- }
1816
- }
1817
- const startResponse = await sparkClient.start_transaction(
1818
- {
1819
- identityPublicKey: await this.config.signer.getIdentityPublicKey(),
1820
- partialTokenTransaction: tokenTransaction,
1821
- validityDurationSeconds: await this.config.getTokenValidityDurationSeconds(),
1822
- partialTokenTransactionOwnerSignatures: ownerSignaturesWithIndex
1823
- },
1824
- {
1825
- retry: true,
1826
- retryableStatuses: ["UNKNOWN", "UNAVAILABLE", "CANCELLED", "INTERNAL"],
1827
- retryMaxAttempts: 3
1828
- }
1829
- );
1830
- if (!startResponse.finalTokenTransaction) {
1831
- throw new Error("Final token transaction missing in start response");
1832
- }
1833
- if (!startResponse.keyshareInfo) {
1834
- throw new Error("Keyshare info missing in start response");
1835
- }
1836
- validateTokenTransaction(
1837
- startResponse.finalTokenTransaction,
1838
- tokenTransaction,
1839
- signingOperators,
1840
- startResponse.keyshareInfo,
1841
- this.config.getExpectedWithdrawBondSats(),
1842
- this.config.getExpectedWithdrawRelativeBlockLocktime(),
1843
- this.config.getThreshold()
1844
- );
1845
- const finalTokenTransaction = startResponse.finalTokenTransaction;
1846
- const finalTokenTransactionHash = hashTokenTransaction(
1847
- finalTokenTransaction,
1848
- false
1849
- );
1850
- return {
1851
- finalTokenTransaction,
1852
- finalTokenTransactionHash,
1853
- threshold: startResponse.keyshareInfo.threshold
1854
- };
1855
- }
1856
- async signTokenTransactionV0(finalTokenTransaction, finalTokenTransactionHash, signingOperators) {
1857
- const soSignatures = await Promise.allSettled(
1858
- Object.entries(signingOperators).map(
1859
- async ([identifier, operator], index) => {
1860
- const internalSparkClient = await this.connectionManager.createSparkClient(operator.address);
1861
- const identityPublicKey = await this.config.signer.getIdentityPublicKey();
1862
- const payload = {
1863
- finalTokenTransactionHash,
1864
- operatorIdentityPublicKey: hexToBytes(operator.identityPublicKey)
1865
- };
1866
- const payloadHash = await hashOperatorSpecificTokenTransactionSignablePayload(payload);
1867
- let operatorSpecificSignatures = [];
1868
- if (finalTokenTransaction.tokenInputs.$case === "mintInput") {
1869
- const issuerPublicKey = finalTokenTransaction.tokenInputs.mintInput.issuerPublicKey;
1870
- if (!issuerPublicKey) {
1871
- throw new ValidationError("Invalid mint input", {
1872
- field: "issuerPublicKey",
1873
- value: null,
1874
- expected: "Non-null issuer public key"
1875
- });
1876
- }
1877
- const ownerSignature = await this.signMessageWithKey(
1878
- payloadHash,
1879
- issuerPublicKey
1880
- );
1881
- operatorSpecificSignatures.push({
1882
- ownerSignature: {
1883
- signature: ownerSignature,
1884
- inputIndex: 0
1885
- },
1886
- payload
1887
- });
1888
- }
1889
- if (finalTokenTransaction.tokenInputs.$case === "transferInput") {
1890
- const transferInput = finalTokenTransaction.tokenInputs.transferInput;
1891
- for (let i = 0; i < transferInput.outputsToSpend.length; i++) {
1892
- let ownerSignature;
1893
- if (this.config.getTokenSignatures() === "SCHNORR") {
1894
- ownerSignature = await this.config.signer.signSchnorrWithIdentityKey(
1895
- payloadHash
1896
- );
1897
- } else {
1898
- ownerSignature = await this.config.signer.signMessageWithIdentityKey(
1899
- payloadHash
1900
- );
1901
- }
1902
- operatorSpecificSignatures.push({
1903
- ownerSignature: {
1904
- signature: ownerSignature,
1905
- inputIndex: i
1906
- },
1907
- payload
1908
- });
1909
- }
1910
- }
1911
- try {
1912
- const response = await internalSparkClient.sign_token_transaction(
1913
- {
1914
- finalTokenTransaction,
1915
- operatorSpecificSignatures,
1916
- identityPublicKey
1917
- },
1918
- {
1919
- retry: true,
1920
- retryableStatuses: [
1921
- "UNKNOWN",
1922
- "UNAVAILABLE",
1923
- "CANCELLED",
1924
- "INTERNAL"
1925
- ],
1926
- retryMaxAttempts: 3
1927
- }
1928
- );
1929
- return {
1930
- index,
1931
- identifier,
1932
- response
1933
- };
1934
- } catch (error) {
1935
- throw new NetworkError(
1936
- "Failed to sign token transaction",
1937
- {
1938
- operation: "sign_token_transaction",
1939
- errorCount: 1,
1940
- errors: error instanceof Error ? error.message : String(error)
1941
- },
1942
- error
1943
- );
1944
- }
1945
- }
1946
- )
1947
- );
1948
- const successfulSignatures = collectResponses(soSignatures);
1949
- return {
1950
- successfulSignatures
1951
- };
1952
- }
1953
- async signTokenTransaction(finalTokenTransaction, finalTokenTransactionHash, signingOperators) {
1954
- const coordinatorClient = await this.connectionManager.createSparkTokenClient(
1955
- this.config.getCoordinatorAddress()
1956
- );
1957
- const inputTtxoSignaturesPerOperator = await this.createSignaturesForOperators(
1958
- finalTokenTransaction,
1959
- finalTokenTransactionHash,
1960
- signingOperators
1961
- );
1962
- try {
1963
- await coordinatorClient.commit_transaction(
1964
- {
1965
- finalTokenTransaction,
1966
- finalTokenTransactionHash,
1967
- inputTtxoSignaturesPerOperator,
1968
- ownerIdentityPublicKey: await this.config.signer.getIdentityPublicKey()
1969
- },
1970
- {
1971
- retry: true,
1972
- retryableStatuses: [
1973
- "UNKNOWN",
1974
- "UNAVAILABLE",
1975
- "CANCELLED",
1976
- "INTERNAL"
1977
- ],
1978
- retryMaxAttempts: 3
1979
- }
1980
- );
1981
- } catch (error) {
1982
- throw new NetworkError(
1983
- "Failed to sign token transaction",
1984
- {
1985
- operation: "sign_token_transaction",
1986
- errorCount: 1,
1987
- errors: error instanceof Error ? error.message : String(error)
1988
- },
1989
- error
1990
- );
1991
- }
1992
- }
1993
- async fetchOwnedTokenOutputs(params) {
1994
- if (this.config.getTokenTransactionVersion() === "V0") {
1995
- return this.fetchOwnedTokenOutputsV0(params);
1996
- } else {
1997
- return this.fetchOwnedTokenOutputsV1(params);
1998
- }
1999
- }
2000
- async queryTokenTransactions(params) {
2001
- if (this.config.getTokenTransactionVersion() === "V0") {
2002
- return this.queryTokenTransactionsV0(params);
2003
- } else {
2004
- return this.queryTokenTransactionsV1(params);
2005
- }
2006
- }
2007
- async fetchOwnedTokenOutputsV0(params) {
2008
- const {
2009
- ownerPublicKeys,
2010
- issuerPublicKeys: tokenPublicKeys = [],
2011
- tokenIdentifiers = []
2012
- } = params;
2013
- const sparkClient = await this.connectionManager.createSparkClient(
2014
- this.config.getCoordinatorAddress()
2015
- );
2016
- try {
2017
- const result = await sparkClient.query_token_outputs({
2018
- ownerPublicKeys,
2019
- tokenPublicKeys,
2020
- tokenIdentifiers,
2021
- network: this.config.getNetworkProto()
2022
- });
2023
- return result.outputsWithPreviousTransactionData;
2024
- } catch (error) {
2025
- throw new NetworkError(
2026
- "Failed to fetch owned token outputs",
2027
- {
2028
- operation: "spark.query_token_outputs",
2029
- errorCount: 1,
2030
- errors: error instanceof Error ? error.message : String(error)
2031
- },
2032
- error
2033
- );
2034
- }
2035
- }
2036
- async fetchOwnedTokenOutputsV1(params) {
2037
- const {
2038
- ownerPublicKeys,
2039
- issuerPublicKeys = [],
2040
- tokenIdentifiers = []
2041
- } = params;
2042
- const tokenClient = await this.connectionManager.createSparkTokenClient(
2043
- this.config.getCoordinatorAddress()
2044
- );
2045
- try {
2046
- const result = await tokenClient.query_token_outputs({
2047
- ownerPublicKeys,
2048
- issuerPublicKeys,
2049
- tokenIdentifiers,
2050
- network: this.config.getNetworkProto()
2051
- });
2052
- return result.outputsWithPreviousTransactionData;
2053
- } catch (error) {
2054
- throw new NetworkError(
2055
- "Failed to fetch owned token outputs",
2056
- {
2057
- operation: "spark_token.query_token_outputs",
2058
- errorCount: 1,
2059
- errors: error instanceof Error ? error.message : String(error)
2060
- },
2061
- error
2062
- );
2063
- }
2064
- }
2065
- async queryTokenTransactionsV0(params) {
2066
- const {
2067
- ownerPublicKeys,
2068
- issuerPublicKeys,
2069
- tokenTransactionHashes,
2070
- tokenIdentifiers,
2071
- outputIds
2072
- } = params;
2073
- const sparkClient = await this.connectionManager.createSparkClient(
2074
- this.config.getCoordinatorAddress()
2075
- );
2076
- let queryParams = {
2077
- tokenPublicKeys: issuerPublicKeys?.map(hexToBytes),
2078
- ownerPublicKeys: ownerPublicKeys?.map(hexToBytes),
2079
- tokenIdentifiers: tokenIdentifiers?.map(hexToBytes),
2080
- tokenTransactionHashes: tokenTransactionHashes?.map(hexToBytes),
2081
- outputIds: outputIds || [],
2082
- limit: 100,
2083
- offset: 0
2084
- };
2085
- try {
2086
- const response = await sparkClient.query_token_transactions(queryParams);
2087
- return response.tokenTransactionsWithStatus.map((tx) => {
2088
- const v1TokenTransaction = {
2089
- version: 1,
2090
- network: tx.tokenTransaction.network,
2091
- tokenInputs: tx.tokenTransaction.tokenInputs,
2092
- tokenOutputs: tx.tokenTransaction.tokenOutputs,
2093
- sparkOperatorIdentityPublicKeys: tx.tokenTransaction.sparkOperatorIdentityPublicKeys,
2094
- expiryTime: void 0,
2095
- // V0 doesn't have expiry time
2096
- clientCreatedTimestamp: tx.tokenTransaction?.tokenInputs?.$case === "mintInput" ? new Date(
2097
- tx.tokenTransaction.tokenInputs.mintInput.issuerProvidedTimestamp * 1e3
2098
- ) : /* @__PURE__ */ new Date()
2099
- };
2100
- return {
2101
- tokenTransaction: v1TokenTransaction,
2102
- status: tx.status,
2103
- confirmationMetadata: tx.confirmationMetadata
2104
- };
2105
- });
2106
- } catch (error) {
2107
- throw new NetworkError(
2108
- "Failed to query token transactions",
2109
- {
2110
- operation: "spark.query_token_transactions",
2111
- errorCount: 1,
2112
- errors: error instanceof Error ? error.message : String(error)
2113
- },
2114
- error
2115
- );
2116
- }
2117
- }
2118
- async queryTokenTransactionsV1(params) {
2119
- const {
2120
- ownerPublicKeys,
2121
- issuerPublicKeys,
2122
- tokenTransactionHashes,
2123
- tokenIdentifiers,
2124
- outputIds
2125
- } = params;
2126
- const tokenClient = await this.connectionManager.createSparkTokenClient(
2127
- this.config.getCoordinatorAddress()
2128
- );
2129
- let queryParams = {
2130
- issuerPublicKeys: issuerPublicKeys?.map(hexToBytes),
2131
- ownerPublicKeys: ownerPublicKeys?.map(hexToBytes),
2132
- tokenIdentifiers: tokenIdentifiers?.map(hexToBytes),
2133
- tokenTransactionHashes: tokenTransactionHashes?.map(hexToBytes),
2134
- outputIds: outputIds || [],
2135
- limit: 100,
2136
- offset: 0
2137
- };
2138
- try {
2139
- const response = await tokenClient.query_token_transactions(queryParams);
2140
- return response.tokenTransactionsWithStatus;
2141
- } catch (error) {
2142
- throw new NetworkError(
2143
- "Failed to query token transactions",
2144
- {
2145
- operation: "spark_token.query_token_transactions",
2146
- errorCount: 1,
2147
- errors: error instanceof Error ? error.message : String(error)
2148
- },
2149
- error
2150
- );
2151
- }
2152
- }
2153
- async syncTokenOutputs(tokenOutputs) {
2154
- const unsortedTokenOutputs = await this.fetchOwnedTokenOutputs({
2155
- ownerPublicKeys: await this.config.signer.getTrackedPublicKeys()
2156
- });
2157
- unsortedTokenOutputs.forEach((output) => {
2158
- const tokenKey = bytesToHex(output.output.tokenPublicKey);
2159
- const index = output.previousTransactionVout;
2160
- tokenOutputs.set(tokenKey, [
2161
- { ...output, previousTransactionVout: index }
2162
- ]);
2163
- });
2164
- }
2165
- selectTokenOutputs(tokenOutputs, tokenAmount, strategy) {
2166
- if (calculateAvailableTokenAmount(tokenOutputs) < tokenAmount) {
2167
- throw new ValidationError("Insufficient token amount", {
2168
- field: "tokenAmount",
2169
- value: calculateAvailableTokenAmount(tokenOutputs),
2170
- expected: tokenAmount
2171
- });
2172
- }
2173
- const exactMatch = tokenOutputs.find(
2174
- (item) => bytesToNumberBE(item.output.tokenAmount) === tokenAmount
2175
- );
2176
- if (exactMatch) {
2177
- return [exactMatch];
2178
- }
2179
- this.sortTokenOutputsByStrategy(tokenOutputs, strategy);
2180
- let remainingAmount = tokenAmount;
2181
- const selectedOutputs = [];
2182
- for (const outputWithPreviousTransactionData of tokenOutputs) {
2183
- if (remainingAmount <= 0n) break;
2184
- selectedOutputs.push(outputWithPreviousTransactionData);
2185
- remainingAmount -= bytesToNumberBE(
2186
- outputWithPreviousTransactionData.output.tokenAmount
2187
- );
2188
- }
2189
- if (remainingAmount > 0n) {
2190
- throw new ValidationError("Insufficient funds", {
2191
- field: "remainingAmount",
2192
- value: remainingAmount
2193
- });
2194
- }
2195
- return selectedOutputs;
2196
- }
2197
- sortTokenOutputsByStrategy(tokenOutputs, strategy) {
2198
- if (strategy === "SMALL_FIRST") {
2199
- tokenOutputs.sort((a, b) => {
2200
- return Number(
2201
- bytesToNumberBE(a.output.tokenAmount) - bytesToNumberBE(b.output.tokenAmount)
2202
- );
2203
- });
2204
- } else {
2205
- tokenOutputs.sort((a, b) => {
2206
- return Number(
2207
- bytesToNumberBE(b.output.tokenAmount) - bytesToNumberBE(a.output.tokenAmount)
2208
- );
2209
- });
2210
- }
2211
- }
2212
- // Helper function for deciding if the signer public key is the identity public key
2213
- async signMessageWithKey(message, publicKey) {
2214
- const tokenSignatures = this.config.getTokenSignatures();
2215
- if (bytesToHex(publicKey) === bytesToHex(await this.config.signer.getIdentityPublicKey())) {
2216
- if (tokenSignatures === "SCHNORR") {
2217
- return await this.config.signer.signSchnorrWithIdentityKey(message);
2218
- } else {
2219
- return await this.config.signer.signMessageWithIdentityKey(message);
2220
- }
2221
- } else {
2222
- if (tokenSignatures === "SCHNORR") {
2223
- return await this.config.signer.signSchnorr(message, publicKey);
2224
- } else {
2225
- return await this.config.signer.signMessageWithPublicKey(
2226
- message,
2227
- publicKey
2228
- );
2229
- }
2230
- }
2231
- }
2232
- async finalizeTokenTransaction(finalTokenTransaction, revocationSecrets, threshold) {
2233
- const signingOperators = this.config.getSigningOperators();
2234
- const soResponses = await Promise.allSettled(
2235
- Object.entries(signingOperators).map(async ([identifier, operator]) => {
2236
- const internalSparkClient = await this.connectionManager.createSparkClient(operator.address);
2237
- const identityPublicKey = await this.config.signer.getIdentityPublicKey();
2238
- try {
2239
- const response = await internalSparkClient.finalize_token_transaction(
2240
- {
2241
- finalTokenTransaction,
2242
- revocationSecrets,
2243
- identityPublicKey
2244
- },
2245
- {
2246
- retry: true,
2247
- retryableStatuses: [
2248
- "UNKNOWN",
2249
- "UNAVAILABLE",
2250
- "CANCELLED",
2251
- "INTERNAL"
2252
- ],
2253
- retryMaxAttempts: 3
2254
- }
2255
- );
2256
- return {
2257
- identifier,
2258
- response
2259
- };
2260
- } catch (error) {
2261
- throw new NetworkError(
2262
- "Failed to finalize token transaction",
2263
- {
2264
- operation: "finalize_token_transaction",
2265
- errorCount: 1,
2266
- errors: error instanceof Error ? error.message : String(error)
2267
- },
2268
- error
2269
- );
2270
- }
2271
- })
2272
- );
2273
- collectResponses(soResponses);
2274
- return finalTokenTransaction;
2275
- }
2276
- async createSignaturesForOperators(finalTokenTransaction, finalTokenTransactionHash, signingOperators) {
2277
- const inputTtxoSignaturesPerOperator = [];
2278
- for (const [_, operator] of Object.entries(signingOperators)) {
2279
- let ttxoSignatures = [];
2280
- if (finalTokenTransaction.tokenInputs.$case === "mintInput") {
2281
- const issuerPublicKey = finalTokenTransaction.tokenInputs.mintInput.issuerPublicKey;
2282
- if (!issuerPublicKey) {
2283
- throw new ValidationError("Invalid mint input", {
2284
- field: "issuerPublicKey",
2285
- value: null,
2286
- expected: "Non-null issuer public key"
2287
- });
2288
- }
2289
- const payload = {
2290
- finalTokenTransactionHash,
2291
- operatorIdentityPublicKey: hexToBytes(operator.identityPublicKey)
2292
- };
2293
- const payloadHash = await hashOperatorSpecificTokenTransactionSignablePayload(payload);
2294
- const ownerSignature = await this.signMessageWithKey(
2295
- payloadHash,
2296
- issuerPublicKey
2297
- );
2298
- ttxoSignatures.push({
2299
- signature: ownerSignature,
2300
- inputIndex: 0
2301
- });
2302
- } else if (finalTokenTransaction.tokenInputs.$case === "transferInput") {
2303
- const transferInput = finalTokenTransaction.tokenInputs.transferInput;
2304
- for (let i = 0; i < transferInput.outputsToSpend.length; i++) {
2305
- const payload = {
2306
- finalTokenTransactionHash,
2307
- operatorIdentityPublicKey: hexToBytes(operator.identityPublicKey)
2308
- };
2309
- const payloadHash = await hashOperatorSpecificTokenTransactionSignablePayload(payload);
2310
- let ownerSignature;
2311
- if (this.config.getTokenSignatures() === "SCHNORR") {
2312
- ownerSignature = await this.config.signer.signSchnorrWithIdentityKey(payloadHash);
2313
- } else {
2314
- ownerSignature = await this.config.signer.signMessageWithIdentityKey(payloadHash);
2315
- }
2316
- ttxoSignatures.push({
2317
- signature: ownerSignature,
2318
- inputIndex: i
2319
- });
2320
- }
2321
- }
2322
- inputTtxoSignaturesPerOperator.push({
2323
- ttxoSignatures,
2324
- operatorIdentityPublicKey: hexToBytes(operator.identityPublicKey)
2325
- });
2326
- }
2327
- return inputTtxoSignaturesPerOperator;
2328
- }
2329
- };
2330
- function isTokenTransaction(tokenTransaction) {
2331
- return "version" in tokenTransaction && "expiryTime" in tokenTransaction;
2332
- }
2333
-
2334
- export {
2335
- TokenTransactionService
2336
- };