@buildonspark/spark-sdk 0.3.3 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/dist/bare/index.cjs +945 -1401
- package/dist/bare/index.d.cts +128 -10
- package/dist/bare/index.d.ts +128 -10
- package/dist/bare/index.js +940 -1399
- package/dist/{chunk-55XNR6DM.js → chunk-DIXXHATX.js} +1 -1
- package/dist/{chunk-MGCUXELA.js → chunk-IC4IUEOS.js} +931 -125
- package/dist/{chunk-MH7BMOLL.js → chunk-J2P3KTQP.js} +1 -1
- package/dist/{chunk-SXXM52XH.js → chunk-JE73HB26.js} +409 -1656
- package/dist/{chunk-73GJOG5R.js → chunk-XWLR6G5C.js} +1 -1
- package/dist/{client-DrjQwET9.d.ts → client-DBZ43pJT.d.ts} +1 -1
- package/dist/{client-DUFejFfn.d.cts → client-DWml6sjL.d.cts} +1 -1
- package/dist/debug.cjs +949 -1403
- package/dist/debug.d.cts +8 -5
- package/dist/debug.d.ts +8 -5
- package/dist/debug.js +4 -4
- package/dist/graphql/objects/index.d.cts +3 -3
- package/dist/graphql/objects/index.d.ts +3 -3
- package/dist/index.cjs +905 -1362
- package/dist/index.d.cts +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +9 -5
- package/dist/index.node.cjs +905 -1362
- package/dist/index.node.d.cts +6 -6
- package/dist/index.node.d.ts +6 -6
- package/dist/index.node.js +8 -4
- package/dist/{logging-CGeEoKYd.d.cts → logging-BUpzk4Z6.d.cts} +3 -3
- package/dist/{logging-DpSsvFVM.d.ts → logging-Dt2ooQiP.d.ts} +3 -3
- package/dist/native/index.cjs +905 -1362
- package/dist/native/index.d.cts +129 -25
- package/dist/native/index.d.ts +129 -25
- package/dist/native/index.js +904 -1363
- package/dist/proto/spark.cjs +931 -125
- package/dist/proto/spark.d.cts +1 -1
- package/dist/proto/spark.d.ts +1 -1
- package/dist/proto/spark.js +17 -1
- package/dist/proto/spark_token.d.cts +1 -1
- package/dist/proto/spark_token.d.ts +1 -1
- package/dist/proto/spark_token.js +2 -2
- package/dist/{spark-CLz4-Ln8.d.cts → spark-DasxuVfm.d.cts} +150 -5
- package/dist/{spark-CLz4-Ln8.d.ts → spark-DasxuVfm.d.ts} +150 -5
- package/dist/{spark-wallet-BVBrWYKL.d.cts → spark-wallet-BoMIOPWW.d.cts} +13 -9
- package/dist/{spark-wallet-CFPm6wZs.d.ts → spark-wallet-jlC0XN5f.d.ts} +13 -9
- package/dist/{spark-wallet.node-e1gncoIZ.d.ts → spark-wallet.node-07PksUHH.d.cts} +1 -1
- package/dist/{spark-wallet.node-B_00X-1j.d.cts → spark-wallet.node-CdWkKMSq.d.ts} +1 -1
- package/dist/tests/test-utils.cjs +947 -144
- package/dist/tests/test-utils.d.cts +4 -4
- package/dist/tests/test-utils.d.ts +4 -4
- package/dist/tests/test-utils.js +5 -5
- package/dist/{token-transactions-BkAqlmY6.d.ts → token-transactions-BDzCrQSk.d.cts} +5 -19
- package/dist/{token-transactions-BZGtwFFM.d.cts → token-transactions-DscJaJOE.d.ts} +5 -19
- package/dist/types/index.cjs +923 -125
- package/dist/types/index.d.cts +2 -2
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.js +2 -2
- package/package.json +1 -1
- package/src/proto/spark.ts +1167 -103
- package/src/services/config.ts +0 -4
- package/src/services/token-transactions.ts +11 -703
- package/src/services/wallet-config.ts +0 -2
- package/src/spark-wallet/proto-descriptors.ts +1 -1
- package/src/spark-wallet/spark-wallet.ts +58 -215
- package/src/spark_descriptors.pb +0 -0
- package/src/tests/address.test.ts +141 -0
- package/src/tests/integration/address.test.ts +4 -0
- package/src/tests/integration/lightning.test.ts +14 -9
- package/src/tests/integration/token-output.test.ts +0 -1
- package/src/tests/integration/transfer.test.ts +108 -2
- package/src/tests/token-hashing.test.ts +0 -247
- package/src/utils/address.ts +58 -35
- package/src/utils/token-hashing.ts +1 -420
- package/src/utils/token-transaction-validation.ts +0 -330
|
@@ -4,11 +4,11 @@ import {
|
|
|
4
4
|
} from "./chunk-NX5KPN5F.js";
|
|
5
5
|
import {
|
|
6
6
|
SparkTokenServiceDefinition
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-XWLR6G5C.js";
|
|
8
8
|
import {
|
|
9
9
|
mapTransferToWalletTransfer,
|
|
10
10
|
mapTreeNodeToWalletLeaf
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-J2P3KTQP.js";
|
|
12
12
|
import {
|
|
13
13
|
Empty,
|
|
14
14
|
SatsPayment,
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
TokensPayment,
|
|
20
20
|
TreeNode,
|
|
21
21
|
networkToJSON
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-IC4IUEOS.js";
|
|
23
23
|
import {
|
|
24
24
|
BitcoinNetwork_default,
|
|
25
25
|
ClaimStaticDepositFromJson,
|
|
@@ -584,7 +584,7 @@ var isWebExtension = (
|
|
|
584
584
|
"chrome" in globalThis && globalThis.chrome.runtime?.id
|
|
585
585
|
);
|
|
586
586
|
var userAgent = "navigator" in globalThis ? globalThis.navigator.userAgent || "unknown-user-agent" : void 0;
|
|
587
|
-
var packageVersion = true ? "0.3.
|
|
587
|
+
var packageVersion = true ? "0.3.5" : "unknown";
|
|
588
588
|
var baseEnvStr = "unknown";
|
|
589
589
|
if (isBun) {
|
|
590
590
|
const bunVersion = "version" in globalThis.Bun ? globalThis.Bun.version : "unknown-version";
|
|
@@ -1273,7 +1273,6 @@ var BASE_CONFIG = {
|
|
|
1273
1273
|
threshold: 2,
|
|
1274
1274
|
signingOperators: getLocalSigningOperators(),
|
|
1275
1275
|
tokenSignatures: "SCHNORR",
|
|
1276
|
-
tokenTransactionVersion: "V1",
|
|
1277
1276
|
tokenValidityDurationSeconds: 180,
|
|
1278
1277
|
electrsUrl: getElectrsUrl("LOCAL"),
|
|
1279
1278
|
expectedWithdrawBondSats: 1e4,
|
|
@@ -1477,9 +1476,6 @@ var WalletConfigService = class {
|
|
|
1477
1476
|
getTokenSignatures() {
|
|
1478
1477
|
return this.config.tokenSignatures;
|
|
1479
1478
|
}
|
|
1480
|
-
getTokenTransactionVersion() {
|
|
1481
|
-
return this.config.tokenTransactionVersion;
|
|
1482
|
-
}
|
|
1483
1479
|
getTokenValidityDurationSeconds() {
|
|
1484
1480
|
return this.config.tokenValidityDurationSeconds;
|
|
1485
1481
|
}
|
|
@@ -4173,6 +4169,13 @@ function uint64be(value) {
|
|
|
4173
4169
|
// src/utils/address.ts
|
|
4174
4170
|
var BECH32M_LIMIT = 1024;
|
|
4175
4171
|
var AddressNetwork = {
|
|
4172
|
+
MAINNET: "spark",
|
|
4173
|
+
TESTNET: "sparkt",
|
|
4174
|
+
REGTEST: "sparkrt",
|
|
4175
|
+
SIGNET: "sparks",
|
|
4176
|
+
LOCAL: "sparkl"
|
|
4177
|
+
};
|
|
4178
|
+
var LegacyAddressNetwork = {
|
|
4176
4179
|
MAINNET: "sp",
|
|
4177
4180
|
TESTNET: "spt",
|
|
4178
4181
|
REGTEST: "sprt",
|
|
@@ -4203,7 +4206,7 @@ function encodeSparkAddressWithSignature(payload, signature) {
|
|
|
4203
4206
|
const serializedPayload = w.finish();
|
|
4204
4207
|
const words = bech32m.toWords(serializedPayload);
|
|
4205
4208
|
return bech32mEncode(
|
|
4206
|
-
|
|
4209
|
+
LegacyAddressNetwork[payload.network],
|
|
4207
4210
|
words
|
|
4208
4211
|
);
|
|
4209
4212
|
} catch (error) {
|
|
@@ -4219,14 +4222,14 @@ function encodeSparkAddressWithSignature(payload, signature) {
|
|
|
4219
4222
|
}
|
|
4220
4223
|
function decodeSparkAddress(address2, network) {
|
|
4221
4224
|
try {
|
|
4222
|
-
|
|
4223
|
-
if (decoded.prefix !== AddressNetwork[network]) {
|
|
4225
|
+
if (network !== getNetworkFromSparkAddress(address2)) {
|
|
4224
4226
|
throw new ValidationError("Invalid Spark address prefix", {
|
|
4225
4227
|
field: "address",
|
|
4226
4228
|
value: address2,
|
|
4227
|
-
expected: `prefix='${AddressNetwork[network]}'`
|
|
4229
|
+
expected: `prefix='${AddressNetwork[network]}' or '${LegacyAddressNetwork[network]}'`
|
|
4228
4230
|
});
|
|
4229
4231
|
}
|
|
4232
|
+
const decoded = bech32mDecode(address2);
|
|
4230
4233
|
const payload = SparkAddress.decode(bech32m.fromWords(decoded.words));
|
|
4231
4234
|
const { identityPublicKey, sparkInvoiceFields, signature } = payload;
|
|
4232
4235
|
const identityPubkeyHex = bytesToHex5(identityPublicKey);
|
|
@@ -4270,18 +4273,35 @@ function decodeSparkAddress(address2, network) {
|
|
|
4270
4273
|
);
|
|
4271
4274
|
}
|
|
4272
4275
|
}
|
|
4276
|
+
var PrefixToNetwork = Object.fromEntries(
|
|
4277
|
+
Object.entries(AddressNetwork).map(([k, v]) => [v, k])
|
|
4278
|
+
);
|
|
4279
|
+
var LegacyPrefixToNetwork = Object.fromEntries(
|
|
4280
|
+
Object.entries(LegacyAddressNetwork).map(([k, v]) => [v, k])
|
|
4281
|
+
);
|
|
4282
|
+
function getNetworkFromSparkAddress(address2) {
|
|
4283
|
+
const { prefix } = bech32mDecode(address2);
|
|
4284
|
+
const network = PrefixToNetwork[prefix] ?? LegacyPrefixToNetwork[prefix];
|
|
4285
|
+
if (!network) {
|
|
4286
|
+
throw new ValidationError("Invalid Spark address prefix", {
|
|
4287
|
+
field: "network",
|
|
4288
|
+
value: address2,
|
|
4289
|
+
expected: "prefix='spark1', 'sparkt1', 'sparkrt1', 'sparks1', 'sparkl1' or legacy ('sp1', 'spt1', 'sprt1', 'sps1', 'spl1')"
|
|
4290
|
+
});
|
|
4291
|
+
}
|
|
4292
|
+
return network;
|
|
4293
|
+
}
|
|
4294
|
+
function isLegacySparkAddress(address2) {
|
|
4295
|
+
try {
|
|
4296
|
+
const { prefix } = bech32mDecode(address2);
|
|
4297
|
+
return prefix in LegacyPrefixToNetwork;
|
|
4298
|
+
} catch (error) {
|
|
4299
|
+
return false;
|
|
4300
|
+
}
|
|
4301
|
+
}
|
|
4273
4302
|
function isValidSparkAddress(address2) {
|
|
4274
4303
|
try {
|
|
4275
|
-
const network =
|
|
4276
|
-
([_, prefix]) => address2.startsWith(prefix)
|
|
4277
|
-
)?.[0];
|
|
4278
|
-
if (!network) {
|
|
4279
|
-
throw new ValidationError("Invalid Spark address network", {
|
|
4280
|
-
field: "network",
|
|
4281
|
-
value: address2,
|
|
4282
|
-
expected: Object.values(AddressNetwork)
|
|
4283
|
-
});
|
|
4284
|
-
}
|
|
4304
|
+
const network = getNetworkFromSparkAddress(address2);
|
|
4285
4305
|
decodeSparkAddress(address2, network);
|
|
4286
4306
|
return true;
|
|
4287
4307
|
} catch (error) {
|
|
@@ -4477,25 +4497,16 @@ function validateSparkInvoiceSignature(invoice) {
|
|
|
4477
4497
|
);
|
|
4478
4498
|
}
|
|
4479
4499
|
}
|
|
4480
|
-
function getNetworkFromSparkAddress(address2) {
|
|
4481
|
-
const { prefix } = bech32mDecode(address2);
|
|
4482
|
-
const network = Object.entries(AddressNetwork).find(
|
|
4483
|
-
([, p]) => p === prefix
|
|
4484
|
-
)?.[0];
|
|
4485
|
-
if (!network) {
|
|
4486
|
-
throw new ValidationError("Invalid Spark address network", {
|
|
4487
|
-
field: "network",
|
|
4488
|
-
value: address2,
|
|
4489
|
-
expected: Object.values(AddressNetwork)
|
|
4490
|
-
});
|
|
4491
|
-
}
|
|
4492
|
-
return network;
|
|
4493
|
-
}
|
|
4494
4500
|
function toProtoTimestamp(date) {
|
|
4495
4501
|
const ms = date.getTime();
|
|
4496
4502
|
return { seconds: Math.floor(ms / 1e3), nanos: ms % 1e3 * 1e6 };
|
|
4497
4503
|
}
|
|
4504
|
+
function assertBech32(s) {
|
|
4505
|
+
const i = s.lastIndexOf("1");
|
|
4506
|
+
if (i <= 0 || i >= s.length - 1) throw new Error("invalid bech32 string");
|
|
4507
|
+
}
|
|
4498
4508
|
function bech32mDecode(address2) {
|
|
4509
|
+
assertBech32(address2);
|
|
4499
4510
|
return bech32m.decode(address2, BECH32M_LIMIT);
|
|
4500
4511
|
}
|
|
4501
4512
|
function bech32mEncode(prefix, words) {
|
|
@@ -4530,27 +4541,6 @@ function isSafeForNumber(bi) {
|
|
|
4530
4541
|
return bi >= BigInt(Number.MIN_SAFE_INTEGER) && bi <= BigInt(Number.MAX_SAFE_INTEGER);
|
|
4531
4542
|
}
|
|
4532
4543
|
|
|
4533
|
-
// src/utils/response-validation.ts
|
|
4534
|
-
function collectResponses(responses) {
|
|
4535
|
-
const successfulResponses = responses.filter(
|
|
4536
|
-
(result) => result.status === "fulfilled"
|
|
4537
|
-
).map((result) => result.value);
|
|
4538
|
-
const failedResponses = responses.filter(
|
|
4539
|
-
(result) => result.status === "rejected"
|
|
4540
|
-
);
|
|
4541
|
-
if (failedResponses.length > 0) {
|
|
4542
|
-
const errors = failedResponses.map((result) => result.reason).join("\n");
|
|
4543
|
-
throw new NetworkError(
|
|
4544
|
-
`${failedResponses.length} out of ${responses.length} requests failed, please try again`,
|
|
4545
|
-
{
|
|
4546
|
-
errorCount: failedResponses.length,
|
|
4547
|
-
errors
|
|
4548
|
-
}
|
|
4549
|
-
);
|
|
4550
|
-
}
|
|
4551
|
-
return successfulResponses;
|
|
4552
|
-
}
|
|
4553
|
-
|
|
4554
4544
|
// src/utils/token-identifier.ts
|
|
4555
4545
|
import { bech32m as bech32m2 } from "@scure/base";
|
|
4556
4546
|
var Bech32mTokenIdentifierTokenIdentifierNetworkPrefix = {
|
|
@@ -4697,7 +4687,6 @@ function filterTokenBalanceForTokenIdentifier(tokenBalances, tokenIdentifier) {
|
|
|
4697
4687
|
}
|
|
4698
4688
|
|
|
4699
4689
|
// src/services/token-transactions.ts
|
|
4700
|
-
import { secp256k1 as secp256k19 } from "@noble/curves/secp256k1";
|
|
4701
4690
|
import {
|
|
4702
4691
|
bytesToHex as bytesToHex6,
|
|
4703
4692
|
bytesToNumberBE as bytesToNumberBE5,
|
|
@@ -4710,8 +4699,6 @@ import { sha256 as sha2568 } from "@noble/hashes/sha2";
|
|
|
4710
4699
|
import { bech32m as bech32m3 } from "@scure/base";
|
|
4711
4700
|
function hashTokenTransaction(tokenTransaction, partialHash = false) {
|
|
4712
4701
|
switch (tokenTransaction.version) {
|
|
4713
|
-
case 0:
|
|
4714
|
-
return hashTokenTransactionV0(tokenTransaction, partialHash);
|
|
4715
4702
|
case 1:
|
|
4716
4703
|
return hashTokenTransactionV1(tokenTransaction, partialHash);
|
|
4717
4704
|
case 2:
|
|
@@ -4723,13 +4710,43 @@ function hashTokenTransaction(tokenTransaction, partialHash = false) {
|
|
|
4723
4710
|
});
|
|
4724
4711
|
}
|
|
4725
4712
|
}
|
|
4726
|
-
function
|
|
4713
|
+
function hashTokenTransactionV1(tokenTransaction, partialHash = false) {
|
|
4727
4714
|
if (!tokenTransaction) {
|
|
4728
4715
|
throw new ValidationError("token transaction cannot be nil", {
|
|
4729
4716
|
field: "tokenTransaction"
|
|
4730
4717
|
});
|
|
4731
4718
|
}
|
|
4732
4719
|
let allHashes = [];
|
|
4720
|
+
const versionHashObj = sha2568.create();
|
|
4721
|
+
const versionBytes = new Uint8Array(4);
|
|
4722
|
+
new DataView(versionBytes.buffer).setUint32(
|
|
4723
|
+
0,
|
|
4724
|
+
tokenTransaction.version,
|
|
4725
|
+
false
|
|
4726
|
+
// false for big-endian
|
|
4727
|
+
);
|
|
4728
|
+
versionHashObj.update(versionBytes);
|
|
4729
|
+
allHashes.push(versionHashObj.digest());
|
|
4730
|
+
const typeHashObj = sha2568.create();
|
|
4731
|
+
const typeBytes = new Uint8Array(4);
|
|
4732
|
+
let transactionType = 0;
|
|
4733
|
+
if (tokenTransaction.tokenInputs?.$case === "mintInput") {
|
|
4734
|
+
transactionType = 2 /* TOKEN_TRANSACTION_TYPE_MINT */;
|
|
4735
|
+
} else if (tokenTransaction.tokenInputs?.$case === "transferInput") {
|
|
4736
|
+
transactionType = 3 /* TOKEN_TRANSACTION_TYPE_TRANSFER */;
|
|
4737
|
+
} else if (tokenTransaction.tokenInputs?.$case === "createInput") {
|
|
4738
|
+
transactionType = 1 /* TOKEN_TRANSACTION_TYPE_CREATE */;
|
|
4739
|
+
} else {
|
|
4740
|
+
throw new ValidationError(
|
|
4741
|
+
"token transaction must have exactly one input type",
|
|
4742
|
+
{
|
|
4743
|
+
field: "tokenInputs"
|
|
4744
|
+
}
|
|
4745
|
+
);
|
|
4746
|
+
}
|
|
4747
|
+
new DataView(typeBytes.buffer).setUint32(0, transactionType, false);
|
|
4748
|
+
typeHashObj.update(typeBytes);
|
|
4749
|
+
allHashes.push(typeHashObj.digest());
|
|
4733
4750
|
if (tokenTransaction.tokenInputs?.$case === "transferInput") {
|
|
4734
4751
|
if (!tokenTransaction.tokenInputs.transferInput.outputsToSpend) {
|
|
4735
4752
|
throw new ValidationError("outputs to spend cannot be null", {
|
|
@@ -4741,6 +4758,15 @@ function hashTokenTransactionV0(tokenTransaction, partialHash = false) {
|
|
|
4741
4758
|
field: "tokenInputs.transferInput.outputsToSpend"
|
|
4742
4759
|
});
|
|
4743
4760
|
}
|
|
4761
|
+
const outputsLenHashObj2 = sha2568.create();
|
|
4762
|
+
const outputsLenBytes2 = new Uint8Array(4);
|
|
4763
|
+
new DataView(outputsLenBytes2.buffer).setUint32(
|
|
4764
|
+
0,
|
|
4765
|
+
tokenTransaction.tokenInputs.transferInput.outputsToSpend.length,
|
|
4766
|
+
false
|
|
4767
|
+
);
|
|
4768
|
+
outputsLenHashObj2.update(outputsLenBytes2);
|
|
4769
|
+
allHashes.push(outputsLenHashObj2.digest());
|
|
4744
4770
|
for (const [
|
|
4745
4771
|
i,
|
|
4746
4772
|
output
|
|
@@ -4777,8 +4803,7 @@ function hashTokenTransactionV0(tokenTransaction, partialHash = false) {
|
|
|
4777
4803
|
hashObj2.update(voutBytes);
|
|
4778
4804
|
allHashes.push(hashObj2.digest());
|
|
4779
4805
|
}
|
|
4780
|
-
}
|
|
4781
|
-
if (tokenTransaction.tokenInputs?.$case === "mintInput") {
|
|
4806
|
+
} else if (tokenTransaction.tokenInputs?.$case === "mintInput") {
|
|
4782
4807
|
const hashObj2 = sha2568.create();
|
|
4783
4808
|
if (tokenTransaction.tokenInputs.mintInput.issuerPublicKey) {
|
|
4784
4809
|
const issuerPubKey = tokenTransaction.tokenInputs.mintInput.issuerPublicKey;
|
|
@@ -4791,32 +4816,20 @@ function hashTokenTransactionV0(tokenTransaction, partialHash = false) {
|
|
|
4791
4816
|
});
|
|
4792
4817
|
}
|
|
4793
4818
|
hashObj2.update(issuerPubKey);
|
|
4794
|
-
|
|
4795
|
-
const
|
|
4796
|
-
if (
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
timestampValue = v0MintInput.issuerProvidedTimestamp;
|
|
4800
|
-
}
|
|
4801
|
-
} else if ("clientCreatedTimestamp" in tokenTransaction && tokenTransaction.clientCreatedTimestamp) {
|
|
4802
|
-
timestampValue = tokenTransaction.clientCreatedTimestamp.getTime();
|
|
4803
|
-
}
|
|
4804
|
-
if (timestampValue != 0) {
|
|
4805
|
-
const timestampBytes = new Uint8Array(8);
|
|
4806
|
-
new DataView(timestampBytes.buffer).setBigUint64(
|
|
4807
|
-
0,
|
|
4808
|
-
BigInt(timestampValue),
|
|
4809
|
-
true
|
|
4810
|
-
// true for little-endian to match Go implementation
|
|
4819
|
+
allHashes.push(hashObj2.digest());
|
|
4820
|
+
const tokenIdentifierHashObj = sha2568.create();
|
|
4821
|
+
if (tokenTransaction.tokenInputs.mintInput.tokenIdentifier) {
|
|
4822
|
+
tokenIdentifierHashObj.update(
|
|
4823
|
+
tokenTransaction.tokenInputs.mintInput.tokenIdentifier
|
|
4811
4824
|
);
|
|
4812
|
-
|
|
4825
|
+
} else {
|
|
4826
|
+
tokenIdentifierHashObj.update(new Uint8Array(32));
|
|
4813
4827
|
}
|
|
4814
|
-
allHashes.push(
|
|
4828
|
+
allHashes.push(tokenIdentifierHashObj.digest());
|
|
4815
4829
|
}
|
|
4816
|
-
}
|
|
4817
|
-
if (tokenTransaction.tokenInputs?.$case === "createInput") {
|
|
4818
|
-
const issuerPubKeyHashObj = sha2568.create();
|
|
4830
|
+
} else if (tokenTransaction.tokenInputs?.$case === "createInput") {
|
|
4819
4831
|
const createInput = tokenTransaction.tokenInputs.createInput;
|
|
4832
|
+
const issuerPubKeyHashObj = sha2568.create();
|
|
4820
4833
|
if (!createInput.issuerPublicKey || createInput.issuerPublicKey.length === 0) {
|
|
4821
4834
|
throw new ValidationError("issuer public key cannot be nil or empty", {
|
|
4822
4835
|
field: "tokenInputs.createInput.issuerPublicKey"
|
|
@@ -4838,10 +4851,8 @@ function hashTokenTransactionV0(tokenTransaction, partialHash = false) {
|
|
|
4838
4851
|
actualLength: createInput.tokenName.length
|
|
4839
4852
|
});
|
|
4840
4853
|
}
|
|
4841
|
-
const tokenNameBytes = new Uint8Array(20);
|
|
4842
4854
|
const tokenNameEncoder = new TextEncoder();
|
|
4843
|
-
|
|
4844
|
-
tokenNameHashObj.update(tokenNameBytes);
|
|
4855
|
+
tokenNameHashObj.update(tokenNameEncoder.encode(createInput.tokenName));
|
|
4845
4856
|
allHashes.push(tokenNameHashObj.digest());
|
|
4846
4857
|
const tokenTickerHashObj = sha2568.create();
|
|
4847
4858
|
if (!createInput.tokenTicker || createInput.tokenTicker.length === 0) {
|
|
@@ -4857,10 +4868,10 @@ function hashTokenTransactionV0(tokenTransaction, partialHash = false) {
|
|
|
4857
4868
|
actualLength: createInput.tokenTicker.length
|
|
4858
4869
|
});
|
|
4859
4870
|
}
|
|
4860
|
-
const tokenTickerBytes = new Uint8Array(6);
|
|
4861
4871
|
const tokenTickerEncoder = new TextEncoder();
|
|
4862
|
-
|
|
4863
|
-
|
|
4872
|
+
tokenTickerHashObj.update(
|
|
4873
|
+
tokenTickerEncoder.encode(createInput.tokenTicker)
|
|
4874
|
+
);
|
|
4864
4875
|
allHashes.push(tokenTickerHashObj.digest());
|
|
4865
4876
|
const decimalsHashObj = sha2568.create();
|
|
4866
4877
|
const decimalsBytes = new Uint8Array(4);
|
|
@@ -4888,8 +4899,9 @@ function hashTokenTransactionV0(tokenTransaction, partialHash = false) {
|
|
|
4888
4899
|
maxSupplyHashObj.update(createInput.maxSupply);
|
|
4889
4900
|
allHashes.push(maxSupplyHashObj.digest());
|
|
4890
4901
|
const isFreezableHashObj = sha2568.create();
|
|
4891
|
-
|
|
4892
|
-
|
|
4902
|
+
isFreezableHashObj.update(
|
|
4903
|
+
new Uint8Array([createInput.isFreezable ? 1 : 0])
|
|
4904
|
+
);
|
|
4893
4905
|
allHashes.push(isFreezableHashObj.digest());
|
|
4894
4906
|
const creationEntityHashObj = sha2568.create();
|
|
4895
4907
|
if (!partialHash && createInput.creationEntityPublicKey) {
|
|
@@ -4902,11 +4914,15 @@ function hashTokenTransactionV0(tokenTransaction, partialHash = false) {
|
|
|
4902
4914
|
field: "tokenOutputs"
|
|
4903
4915
|
});
|
|
4904
4916
|
}
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4917
|
+
const outputsLenHashObj = sha2568.create();
|
|
4918
|
+
const outputsLenBytes = new Uint8Array(4);
|
|
4919
|
+
new DataView(outputsLenBytes.buffer).setUint32(
|
|
4920
|
+
0,
|
|
4921
|
+
tokenTransaction.tokenOutputs.length,
|
|
4922
|
+
false
|
|
4923
|
+
);
|
|
4924
|
+
outputsLenHashObj.update(outputsLenBytes);
|
|
4925
|
+
allHashes.push(outputsLenHashObj.digest());
|
|
4910
4926
|
for (const [i, output] of tokenTransaction.tokenOutputs.entries()) {
|
|
4911
4927
|
if (!output) {
|
|
4912
4928
|
throw new ValidationError(`output cannot be null at index ${i}`, {
|
|
@@ -4965,18 +4981,16 @@ function hashTokenTransactionV0(tokenTransaction, partialHash = false) {
|
|
|
4965
4981
|
);
|
|
4966
4982
|
hashObj2.update(locktimeBytes);
|
|
4967
4983
|
}
|
|
4968
|
-
if (output.tokenPublicKey) {
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
`token public key at index ${i} cannot be empty`,
|
|
4972
|
-
{
|
|
4973
|
-
field: `tokenOutputs[${i}].tokenPublicKey`,
|
|
4974
|
-
index: i
|
|
4975
|
-
}
|
|
4976
|
-
);
|
|
4977
|
-
}
|
|
4984
|
+
if (!output.tokenPublicKey || output.tokenPublicKey.length === 0) {
|
|
4985
|
+
hashObj2.update(new Uint8Array(33));
|
|
4986
|
+
} else {
|
|
4978
4987
|
hashObj2.update(output.tokenPublicKey);
|
|
4979
4988
|
}
|
|
4989
|
+
if (!output.tokenIdentifier || output.tokenIdentifier.length === 0) {
|
|
4990
|
+
hashObj2.update(new Uint8Array(32));
|
|
4991
|
+
} else {
|
|
4992
|
+
hashObj2.update(output.tokenIdentifier);
|
|
4993
|
+
}
|
|
4980
4994
|
if (output.tokenAmount) {
|
|
4981
4995
|
if (output.tokenAmount.length === 0) {
|
|
4982
4996
|
throw new ValidationError(
|
|
@@ -5017,6 +5031,15 @@ function hashTokenTransactionV0(tokenTransaction, partialHash = false) {
|
|
|
5017
5031
|
}
|
|
5018
5032
|
return a.length - b.length;
|
|
5019
5033
|
});
|
|
5034
|
+
const operatorLenHashObj = sha2568.create();
|
|
5035
|
+
const operatorLenBytes = new Uint8Array(4);
|
|
5036
|
+
new DataView(operatorLenBytes.buffer).setUint32(
|
|
5037
|
+
0,
|
|
5038
|
+
sortedPubKeys.length,
|
|
5039
|
+
false
|
|
5040
|
+
);
|
|
5041
|
+
operatorLenHashObj.update(operatorLenBytes);
|
|
5042
|
+
allHashes.push(operatorLenHashObj.digest());
|
|
5020
5043
|
for (const [i, pubKey] of sortedPubKeys.entries()) {
|
|
5021
5044
|
if (!pubKey) {
|
|
5022
5045
|
throw new ValidationError(
|
|
@@ -5050,6 +5073,38 @@ function hashTokenTransactionV0(tokenTransaction, partialHash = false) {
|
|
|
5050
5073
|
);
|
|
5051
5074
|
hashObj.update(networkBytes);
|
|
5052
5075
|
allHashes.push(hashObj.digest());
|
|
5076
|
+
const clientTimestampHashObj = sha2568.create();
|
|
5077
|
+
const clientCreatedTs = tokenTransaction.clientCreatedTimestamp;
|
|
5078
|
+
if (!clientCreatedTs) {
|
|
5079
|
+
throw new ValidationError(
|
|
5080
|
+
"client created timestamp cannot be null for V1 token transactions",
|
|
5081
|
+
{
|
|
5082
|
+
field: "clientCreatedTimestamp"
|
|
5083
|
+
}
|
|
5084
|
+
);
|
|
5085
|
+
}
|
|
5086
|
+
const clientUnixTime = clientCreatedTs.getTime();
|
|
5087
|
+
const clientTimestampBytes = new Uint8Array(8);
|
|
5088
|
+
new DataView(clientTimestampBytes.buffer).setBigUint64(
|
|
5089
|
+
0,
|
|
5090
|
+
BigInt(clientUnixTime),
|
|
5091
|
+
false
|
|
5092
|
+
);
|
|
5093
|
+
clientTimestampHashObj.update(clientTimestampBytes);
|
|
5094
|
+
allHashes.push(clientTimestampHashObj.digest());
|
|
5095
|
+
if (!partialHash) {
|
|
5096
|
+
const expiryHashObj = sha2568.create();
|
|
5097
|
+
const expiryTimeBytes = new Uint8Array(8);
|
|
5098
|
+
const expiryUnixTime = tokenTransaction.expiryTime ? Math.floor(tokenTransaction.expiryTime.getTime() / 1e3) : 0;
|
|
5099
|
+
new DataView(expiryTimeBytes.buffer).setBigUint64(
|
|
5100
|
+
0,
|
|
5101
|
+
BigInt(expiryUnixTime),
|
|
5102
|
+
false
|
|
5103
|
+
// false for big-endian
|
|
5104
|
+
);
|
|
5105
|
+
expiryHashObj.update(expiryTimeBytes);
|
|
5106
|
+
allHashes.push(expiryHashObj.digest());
|
|
5107
|
+
}
|
|
5053
5108
|
const finalHashObj = sha2568.create();
|
|
5054
5109
|
const concatenatedHashes = new Uint8Array(
|
|
5055
5110
|
allHashes.reduce((sum, hash) => sum + hash.length, 0)
|
|
@@ -5062,7 +5117,7 @@ function hashTokenTransactionV0(tokenTransaction, partialHash = false) {
|
|
|
5062
5117
|
finalHashObj.update(concatenatedHashes);
|
|
5063
5118
|
return finalHashObj.digest();
|
|
5064
5119
|
}
|
|
5065
|
-
function
|
|
5120
|
+
function hashTokenTransactionV2(tokenTransaction, partialHash = false) {
|
|
5066
5121
|
if (!tokenTransaction) {
|
|
5067
5122
|
throw new ValidationError("token transaction cannot be nil", {
|
|
5068
5123
|
field: "tokenTransaction"
|
|
@@ -5457,6 +5512,72 @@ function hashTokenTransactionV1(tokenTransaction, partialHash = false) {
|
|
|
5457
5512
|
expiryHashObj.update(expiryTimeBytes);
|
|
5458
5513
|
allHashes.push(expiryHashObj.digest());
|
|
5459
5514
|
}
|
|
5515
|
+
const attachments = tokenTransaction.invoiceAttachments;
|
|
5516
|
+
const lenHash = sha2568.create();
|
|
5517
|
+
const lenBytes = new Uint8Array(4);
|
|
5518
|
+
new DataView(lenBytes.buffer).setUint32(
|
|
5519
|
+
0,
|
|
5520
|
+
attachments ? attachments.length : 0,
|
|
5521
|
+
false
|
|
5522
|
+
);
|
|
5523
|
+
lenHash.update(lenBytes);
|
|
5524
|
+
allHashes.push(lenHash.digest());
|
|
5525
|
+
const sortedInvoices = [];
|
|
5526
|
+
if (attachments) {
|
|
5527
|
+
for (let i = 0; i < attachments.length; i++) {
|
|
5528
|
+
const attachment = attachments[i];
|
|
5529
|
+
if (!attachment) {
|
|
5530
|
+
throw new ValidationError(
|
|
5531
|
+
`invoice attachment at index ${i} cannot be null`,
|
|
5532
|
+
{
|
|
5533
|
+
field: `invoiceAttachments[${i}]`,
|
|
5534
|
+
index: i
|
|
5535
|
+
}
|
|
5536
|
+
);
|
|
5537
|
+
}
|
|
5538
|
+
const invoice = attachment.sparkInvoice;
|
|
5539
|
+
let idBytes;
|
|
5540
|
+
try {
|
|
5541
|
+
const decoded = bech32m3.decode(invoice, 500);
|
|
5542
|
+
const payload = SparkAddress.decode(bech32m3.fromWords(decoded.words));
|
|
5543
|
+
if (!payload.sparkInvoiceFields || !payload.sparkInvoiceFields.id) {
|
|
5544
|
+
throw new Error("missing spark invoice fields or id");
|
|
5545
|
+
}
|
|
5546
|
+
idBytes = payload.sparkInvoiceFields.id;
|
|
5547
|
+
} catch (err) {
|
|
5548
|
+
throw new ValidationError(
|
|
5549
|
+
`invalid invoice at ${i}`,
|
|
5550
|
+
{
|
|
5551
|
+
field: `invoiceAttachments[${i}].sparkInvoice`,
|
|
5552
|
+
index: i,
|
|
5553
|
+
value: invoice
|
|
5554
|
+
},
|
|
5555
|
+
err
|
|
5556
|
+
);
|
|
5557
|
+
}
|
|
5558
|
+
if (!idBytes || idBytes.length !== 16) {
|
|
5559
|
+
throw new ValidationError(`invalid invoice id at ${i}`, {
|
|
5560
|
+
field: `invoiceAttachments[${i}].sparkInvoice`,
|
|
5561
|
+
index: i
|
|
5562
|
+
});
|
|
5563
|
+
}
|
|
5564
|
+
sortedInvoices.push({ id: idBytes, raw: invoice });
|
|
5565
|
+
}
|
|
5566
|
+
}
|
|
5567
|
+
sortedInvoices.sort((a, b) => {
|
|
5568
|
+
for (let j = 0; j < a.id.length && j < b.id.length; j++) {
|
|
5569
|
+
const av = a.id[j];
|
|
5570
|
+
const bv = b.id[j];
|
|
5571
|
+
if (av !== bv) return av - bv;
|
|
5572
|
+
}
|
|
5573
|
+
return a.id.length - b.id.length;
|
|
5574
|
+
});
|
|
5575
|
+
const encoder = new TextEncoder();
|
|
5576
|
+
for (const k of sortedInvoices) {
|
|
5577
|
+
const h = sha2568.create();
|
|
5578
|
+
h.update(encoder.encode(k.raw));
|
|
5579
|
+
allHashes.push(h.digest());
|
|
5580
|
+
}
|
|
5460
5581
|
const finalHashObj = sha2568.create();
|
|
5461
5582
|
const concatenatedHashes = new Uint8Array(
|
|
5462
5583
|
allHashes.reduce((sum, hash) => sum + hash.length, 0)
|
|
@@ -5469,798 +5590,64 @@ function hashTokenTransactionV1(tokenTransaction, partialHash = false) {
|
|
|
5469
5590
|
finalHashObj.update(concatenatedHashes);
|
|
5470
5591
|
return finalHashObj.digest();
|
|
5471
5592
|
}
|
|
5472
|
-
function
|
|
5473
|
-
if (!
|
|
5474
|
-
throw new ValidationError(
|
|
5475
|
-
|
|
5476
|
-
});
|
|
5477
|
-
}
|
|
5478
|
-
let allHashes = [];
|
|
5479
|
-
const versionHashObj = sha2568.create();
|
|
5480
|
-
const versionBytes = new Uint8Array(4);
|
|
5481
|
-
new DataView(versionBytes.buffer).setUint32(
|
|
5482
|
-
0,
|
|
5483
|
-
tokenTransaction.version,
|
|
5484
|
-
false
|
|
5485
|
-
// false for big-endian
|
|
5486
|
-
);
|
|
5487
|
-
versionHashObj.update(versionBytes);
|
|
5488
|
-
allHashes.push(versionHashObj.digest());
|
|
5489
|
-
const typeHashObj = sha2568.create();
|
|
5490
|
-
const typeBytes = new Uint8Array(4);
|
|
5491
|
-
let transactionType = 0;
|
|
5492
|
-
if (tokenTransaction.tokenInputs?.$case === "mintInput") {
|
|
5493
|
-
transactionType = 2 /* TOKEN_TRANSACTION_TYPE_MINT */;
|
|
5494
|
-
} else if (tokenTransaction.tokenInputs?.$case === "transferInput") {
|
|
5495
|
-
transactionType = 3 /* TOKEN_TRANSACTION_TYPE_TRANSFER */;
|
|
5496
|
-
} else if (tokenTransaction.tokenInputs?.$case === "createInput") {
|
|
5497
|
-
transactionType = 1 /* TOKEN_TRANSACTION_TYPE_CREATE */;
|
|
5498
|
-
} else {
|
|
5499
|
-
throw new ValidationError(
|
|
5500
|
-
"token transaction must have exactly one input type",
|
|
5501
|
-
{
|
|
5502
|
-
field: "tokenInputs"
|
|
5503
|
-
}
|
|
5504
|
-
);
|
|
5505
|
-
}
|
|
5506
|
-
new DataView(typeBytes.buffer).setUint32(0, transactionType, false);
|
|
5507
|
-
typeHashObj.update(typeBytes);
|
|
5508
|
-
allHashes.push(typeHashObj.digest());
|
|
5509
|
-
if (tokenTransaction.tokenInputs?.$case === "transferInput") {
|
|
5510
|
-
if (!tokenTransaction.tokenInputs.transferInput.outputsToSpend) {
|
|
5511
|
-
throw new ValidationError("outputs to spend cannot be null", {
|
|
5512
|
-
field: "tokenInputs.transferInput.outputsToSpend"
|
|
5513
|
-
});
|
|
5514
|
-
}
|
|
5515
|
-
if (tokenTransaction.tokenInputs.transferInput.outputsToSpend.length === 0) {
|
|
5516
|
-
throw new ValidationError("outputs to spend cannot be empty", {
|
|
5517
|
-
field: "tokenInputs.transferInput.outputsToSpend"
|
|
5518
|
-
});
|
|
5519
|
-
}
|
|
5520
|
-
const outputsLenHashObj2 = sha2568.create();
|
|
5521
|
-
const outputsLenBytes2 = new Uint8Array(4);
|
|
5522
|
-
new DataView(outputsLenBytes2.buffer).setUint32(
|
|
5523
|
-
0,
|
|
5524
|
-
tokenTransaction.tokenInputs.transferInput.outputsToSpend.length,
|
|
5525
|
-
false
|
|
5526
|
-
);
|
|
5527
|
-
outputsLenHashObj2.update(outputsLenBytes2);
|
|
5528
|
-
allHashes.push(outputsLenHashObj2.digest());
|
|
5529
|
-
for (const [
|
|
5530
|
-
i,
|
|
5531
|
-
output
|
|
5532
|
-
] of tokenTransaction.tokenInputs.transferInput.outputsToSpend.entries()) {
|
|
5533
|
-
if (!output) {
|
|
5534
|
-
throw new ValidationError(`output cannot be null at index ${i}`, {
|
|
5535
|
-
field: `tokenInputs.transferInput.outputsToSpend[${i}]`,
|
|
5536
|
-
index: i
|
|
5537
|
-
});
|
|
5538
|
-
}
|
|
5539
|
-
const hashObj2 = sha2568.create();
|
|
5540
|
-
if (output.prevTokenTransactionHash) {
|
|
5541
|
-
const prevHash = output.prevTokenTransactionHash;
|
|
5542
|
-
if (output.prevTokenTransactionHash.length !== 32) {
|
|
5543
|
-
throw new ValidationError(
|
|
5544
|
-
`invalid previous transaction hash length at index ${i}`,
|
|
5545
|
-
{
|
|
5546
|
-
field: `tokenInputs.transferInput.outputsToSpend[${i}].prevTokenTransactionHash`,
|
|
5547
|
-
value: prevHash,
|
|
5548
|
-
expectedLength: 32,
|
|
5549
|
-
actualLength: prevHash.length,
|
|
5550
|
-
index: i
|
|
5551
|
-
}
|
|
5552
|
-
);
|
|
5553
|
-
}
|
|
5554
|
-
hashObj2.update(output.prevTokenTransactionHash);
|
|
5555
|
-
}
|
|
5556
|
-
const voutBytes = new Uint8Array(4);
|
|
5557
|
-
new DataView(voutBytes.buffer).setUint32(
|
|
5558
|
-
0,
|
|
5559
|
-
output.prevTokenTransactionVout,
|
|
5560
|
-
false
|
|
5561
|
-
);
|
|
5562
|
-
hashObj2.update(voutBytes);
|
|
5563
|
-
allHashes.push(hashObj2.digest());
|
|
5564
|
-
}
|
|
5565
|
-
} else if (tokenTransaction.tokenInputs?.$case === "mintInput") {
|
|
5566
|
-
const hashObj2 = sha2568.create();
|
|
5567
|
-
if (tokenTransaction.tokenInputs.mintInput.issuerPublicKey) {
|
|
5568
|
-
const issuerPubKey = tokenTransaction.tokenInputs.mintInput.issuerPublicKey;
|
|
5569
|
-
if (issuerPubKey.length === 0) {
|
|
5570
|
-
throw new ValidationError("issuer public key cannot be empty", {
|
|
5571
|
-
field: "tokenInputs.mintInput.issuerPublicKey",
|
|
5572
|
-
value: issuerPubKey,
|
|
5573
|
-
expectedLength: 1,
|
|
5574
|
-
actualLength: 0
|
|
5575
|
-
});
|
|
5576
|
-
}
|
|
5577
|
-
hashObj2.update(issuerPubKey);
|
|
5578
|
-
allHashes.push(hashObj2.digest());
|
|
5579
|
-
const tokenIdentifierHashObj = sha2568.create();
|
|
5580
|
-
if (tokenTransaction.tokenInputs.mintInput.tokenIdentifier) {
|
|
5581
|
-
tokenIdentifierHashObj.update(
|
|
5582
|
-
tokenTransaction.tokenInputs.mintInput.tokenIdentifier
|
|
5583
|
-
);
|
|
5584
|
-
} else {
|
|
5585
|
-
tokenIdentifierHashObj.update(new Uint8Array(32));
|
|
5586
|
-
}
|
|
5587
|
-
allHashes.push(tokenIdentifierHashObj.digest());
|
|
5588
|
-
}
|
|
5589
|
-
} else if (tokenTransaction.tokenInputs?.$case === "createInput") {
|
|
5590
|
-
const createInput = tokenTransaction.tokenInputs.createInput;
|
|
5591
|
-
const issuerPubKeyHashObj = sha2568.create();
|
|
5592
|
-
if (!createInput.issuerPublicKey || createInput.issuerPublicKey.length === 0) {
|
|
5593
|
-
throw new ValidationError("issuer public key cannot be nil or empty", {
|
|
5594
|
-
field: "tokenInputs.createInput.issuerPublicKey"
|
|
5595
|
-
});
|
|
5596
|
-
}
|
|
5597
|
-
issuerPubKeyHashObj.update(createInput.issuerPublicKey);
|
|
5598
|
-
allHashes.push(issuerPubKeyHashObj.digest());
|
|
5599
|
-
const tokenNameHashObj = sha2568.create();
|
|
5600
|
-
if (!createInput.tokenName || createInput.tokenName.length === 0) {
|
|
5601
|
-
throw new ValidationError("token name cannot be empty", {
|
|
5602
|
-
field: "tokenInputs.createInput.tokenName"
|
|
5603
|
-
});
|
|
5604
|
-
}
|
|
5605
|
-
if (createInput.tokenName.length > 20) {
|
|
5606
|
-
throw new ValidationError("token name cannot be longer than 20 bytes", {
|
|
5607
|
-
field: "tokenInputs.createInput.tokenName",
|
|
5608
|
-
value: createInput.tokenName,
|
|
5609
|
-
expectedLength: 20,
|
|
5610
|
-
actualLength: createInput.tokenName.length
|
|
5611
|
-
});
|
|
5612
|
-
}
|
|
5613
|
-
const tokenNameEncoder = new TextEncoder();
|
|
5614
|
-
tokenNameHashObj.update(tokenNameEncoder.encode(createInput.tokenName));
|
|
5615
|
-
allHashes.push(tokenNameHashObj.digest());
|
|
5616
|
-
const tokenTickerHashObj = sha2568.create();
|
|
5617
|
-
if (!createInput.tokenTicker || createInput.tokenTicker.length === 0) {
|
|
5618
|
-
throw new ValidationError("token ticker cannot be empty", {
|
|
5619
|
-
field: "tokenInputs.createInput.tokenTicker"
|
|
5620
|
-
});
|
|
5621
|
-
}
|
|
5622
|
-
if (createInput.tokenTicker.length > 6) {
|
|
5623
|
-
throw new ValidationError("token ticker cannot be longer than 6 bytes", {
|
|
5624
|
-
field: "tokenInputs.createInput.tokenTicker",
|
|
5625
|
-
value: createInput.tokenTicker,
|
|
5626
|
-
expectedLength: 6,
|
|
5627
|
-
actualLength: createInput.tokenTicker.length
|
|
5628
|
-
});
|
|
5629
|
-
}
|
|
5630
|
-
const tokenTickerEncoder = new TextEncoder();
|
|
5631
|
-
tokenTickerHashObj.update(
|
|
5632
|
-
tokenTickerEncoder.encode(createInput.tokenTicker)
|
|
5633
|
-
);
|
|
5634
|
-
allHashes.push(tokenTickerHashObj.digest());
|
|
5635
|
-
const decimalsHashObj = sha2568.create();
|
|
5636
|
-
const decimalsBytes = new Uint8Array(4);
|
|
5637
|
-
new DataView(decimalsBytes.buffer).setUint32(
|
|
5638
|
-
0,
|
|
5639
|
-
createInput.decimals,
|
|
5640
|
-
false
|
|
5641
|
-
);
|
|
5642
|
-
decimalsHashObj.update(decimalsBytes);
|
|
5643
|
-
allHashes.push(decimalsHashObj.digest());
|
|
5644
|
-
const maxSupplyHashObj = sha2568.create();
|
|
5645
|
-
if (!createInput.maxSupply) {
|
|
5646
|
-
throw new ValidationError("max supply cannot be nil", {
|
|
5647
|
-
field: "tokenInputs.createInput.maxSupply"
|
|
5648
|
-
});
|
|
5649
|
-
}
|
|
5650
|
-
if (createInput.maxSupply.length !== 16) {
|
|
5651
|
-
throw new ValidationError("max supply must be exactly 16 bytes", {
|
|
5652
|
-
field: "tokenInputs.createInput.maxSupply",
|
|
5653
|
-
value: createInput.maxSupply,
|
|
5654
|
-
expectedLength: 16,
|
|
5655
|
-
actualLength: createInput.maxSupply.length
|
|
5656
|
-
});
|
|
5657
|
-
}
|
|
5658
|
-
maxSupplyHashObj.update(createInput.maxSupply);
|
|
5659
|
-
allHashes.push(maxSupplyHashObj.digest());
|
|
5660
|
-
const isFreezableHashObj = sha2568.create();
|
|
5661
|
-
isFreezableHashObj.update(
|
|
5662
|
-
new Uint8Array([createInput.isFreezable ? 1 : 0])
|
|
5663
|
-
);
|
|
5664
|
-
allHashes.push(isFreezableHashObj.digest());
|
|
5665
|
-
const creationEntityHashObj = sha2568.create();
|
|
5666
|
-
if (!partialHash && createInput.creationEntityPublicKey) {
|
|
5667
|
-
creationEntityHashObj.update(createInput.creationEntityPublicKey);
|
|
5668
|
-
}
|
|
5669
|
-
allHashes.push(creationEntityHashObj.digest());
|
|
5670
|
-
}
|
|
5671
|
-
if (!tokenTransaction.tokenOutputs) {
|
|
5672
|
-
throw new ValidationError("token outputs cannot be null", {
|
|
5673
|
-
field: "tokenOutputs"
|
|
5674
|
-
});
|
|
5675
|
-
}
|
|
5676
|
-
const outputsLenHashObj = sha2568.create();
|
|
5677
|
-
const outputsLenBytes = new Uint8Array(4);
|
|
5678
|
-
new DataView(outputsLenBytes.buffer).setUint32(
|
|
5679
|
-
0,
|
|
5680
|
-
tokenTransaction.tokenOutputs.length,
|
|
5681
|
-
false
|
|
5682
|
-
);
|
|
5683
|
-
outputsLenHashObj.update(outputsLenBytes);
|
|
5684
|
-
allHashes.push(outputsLenHashObj.digest());
|
|
5685
|
-
for (const [i, output] of tokenTransaction.tokenOutputs.entries()) {
|
|
5686
|
-
if (!output) {
|
|
5687
|
-
throw new ValidationError(`output cannot be null at index ${i}`, {
|
|
5688
|
-
field: `tokenOutputs[${i}]`,
|
|
5689
|
-
index: i
|
|
5690
|
-
});
|
|
5691
|
-
}
|
|
5692
|
-
const hashObj2 = sha2568.create();
|
|
5693
|
-
if (output.id && !partialHash) {
|
|
5694
|
-
if (output.id.length === 0) {
|
|
5695
|
-
throw new ValidationError(`output ID at index ${i} cannot be empty`, {
|
|
5696
|
-
field: `tokenOutputs[${i}].id`,
|
|
5697
|
-
index: i
|
|
5698
|
-
});
|
|
5699
|
-
}
|
|
5700
|
-
hashObj2.update(new TextEncoder().encode(output.id));
|
|
5701
|
-
}
|
|
5702
|
-
if (output.ownerPublicKey) {
|
|
5703
|
-
if (output.ownerPublicKey.length === 0) {
|
|
5704
|
-
throw new ValidationError(
|
|
5705
|
-
`owner public key at index ${i} cannot be empty`,
|
|
5706
|
-
{
|
|
5707
|
-
field: `tokenOutputs[${i}].ownerPublicKey`,
|
|
5708
|
-
index: i
|
|
5709
|
-
}
|
|
5710
|
-
);
|
|
5711
|
-
}
|
|
5712
|
-
hashObj2.update(output.ownerPublicKey);
|
|
5713
|
-
}
|
|
5714
|
-
if (!partialHash) {
|
|
5715
|
-
const revPubKey = output.revocationCommitment;
|
|
5716
|
-
if (revPubKey) {
|
|
5717
|
-
if (revPubKey.length === 0) {
|
|
5718
|
-
throw new ValidationError(
|
|
5719
|
-
`revocation commitment at index ${i} cannot be empty`,
|
|
5720
|
-
{
|
|
5721
|
-
field: `tokenOutputs[${i}].revocationCommitment`,
|
|
5722
|
-
index: i
|
|
5723
|
-
}
|
|
5724
|
-
);
|
|
5725
|
-
}
|
|
5726
|
-
hashObj2.update(revPubKey);
|
|
5727
|
-
}
|
|
5728
|
-
const bondBytes = new Uint8Array(8);
|
|
5729
|
-
new DataView(bondBytes.buffer).setBigUint64(
|
|
5730
|
-
0,
|
|
5731
|
-
BigInt(output.withdrawBondSats),
|
|
5732
|
-
false
|
|
5733
|
-
);
|
|
5734
|
-
hashObj2.update(bondBytes);
|
|
5735
|
-
const locktimeBytes = new Uint8Array(8);
|
|
5736
|
-
new DataView(locktimeBytes.buffer).setBigUint64(
|
|
5737
|
-
0,
|
|
5738
|
-
BigInt(output.withdrawRelativeBlockLocktime),
|
|
5739
|
-
false
|
|
5740
|
-
);
|
|
5741
|
-
hashObj2.update(locktimeBytes);
|
|
5742
|
-
}
|
|
5743
|
-
if (!output.tokenPublicKey || output.tokenPublicKey.length === 0) {
|
|
5744
|
-
hashObj2.update(new Uint8Array(33));
|
|
5745
|
-
} else {
|
|
5746
|
-
hashObj2.update(output.tokenPublicKey);
|
|
5747
|
-
}
|
|
5748
|
-
if (!output.tokenIdentifier || output.tokenIdentifier.length === 0) {
|
|
5749
|
-
hashObj2.update(new Uint8Array(32));
|
|
5750
|
-
} else {
|
|
5751
|
-
hashObj2.update(output.tokenIdentifier);
|
|
5752
|
-
}
|
|
5753
|
-
if (output.tokenAmount) {
|
|
5754
|
-
if (output.tokenAmount.length === 0) {
|
|
5755
|
-
throw new ValidationError(
|
|
5756
|
-
`token amount at index ${i} cannot be empty`,
|
|
5757
|
-
{
|
|
5758
|
-
field: `tokenOutputs[${i}].tokenAmount`,
|
|
5759
|
-
index: i
|
|
5760
|
-
}
|
|
5761
|
-
);
|
|
5762
|
-
}
|
|
5763
|
-
if (output.tokenAmount.length > 16) {
|
|
5764
|
-
throw new ValidationError(
|
|
5765
|
-
`token amount at index ${i} exceeds maximum length`,
|
|
5766
|
-
{
|
|
5767
|
-
field: `tokenOutputs[${i}].tokenAmount`,
|
|
5768
|
-
value: output.tokenAmount,
|
|
5769
|
-
expectedLength: 16,
|
|
5770
|
-
actualLength: output.tokenAmount.length,
|
|
5771
|
-
index: i
|
|
5772
|
-
}
|
|
5773
|
-
);
|
|
5774
|
-
}
|
|
5775
|
-
hashObj2.update(output.tokenAmount);
|
|
5776
|
-
}
|
|
5777
|
-
allHashes.push(hashObj2.digest());
|
|
5778
|
-
}
|
|
5779
|
-
if (!tokenTransaction.sparkOperatorIdentityPublicKeys) {
|
|
5780
|
-
throw new ValidationError(
|
|
5781
|
-
"spark operator identity public keys cannot be null",
|
|
5782
|
-
{}
|
|
5783
|
-
);
|
|
5784
|
-
}
|
|
5785
|
-
const sortedPubKeys = [
|
|
5786
|
-
...tokenTransaction.sparkOperatorIdentityPublicKeys || []
|
|
5787
|
-
].sort((a, b) => {
|
|
5788
|
-
for (let i = 0; i < a.length && i < b.length; i++) {
|
|
5789
|
-
if (a[i] !== b[i]) return a[i] - b[i];
|
|
5790
|
-
}
|
|
5791
|
-
return a.length - b.length;
|
|
5792
|
-
});
|
|
5793
|
-
const operatorLenHashObj = sha2568.create();
|
|
5794
|
-
const operatorLenBytes = new Uint8Array(4);
|
|
5795
|
-
new DataView(operatorLenBytes.buffer).setUint32(
|
|
5796
|
-
0,
|
|
5797
|
-
sortedPubKeys.length,
|
|
5798
|
-
false
|
|
5799
|
-
);
|
|
5800
|
-
operatorLenHashObj.update(operatorLenBytes);
|
|
5801
|
-
allHashes.push(operatorLenHashObj.digest());
|
|
5802
|
-
for (const [i, pubKey] of sortedPubKeys.entries()) {
|
|
5803
|
-
if (!pubKey) {
|
|
5804
|
-
throw new ValidationError(
|
|
5805
|
-
`operator public key at index ${i} cannot be null`,
|
|
5806
|
-
{
|
|
5807
|
-
field: `sparkOperatorIdentityPublicKeys[${i}]`,
|
|
5808
|
-
index: i
|
|
5809
|
-
}
|
|
5810
|
-
);
|
|
5811
|
-
}
|
|
5812
|
-
if (pubKey.length === 0) {
|
|
5813
|
-
throw new ValidationError(
|
|
5814
|
-
`operator public key at index ${i} cannot be empty`,
|
|
5815
|
-
{
|
|
5816
|
-
field: `sparkOperatorIdentityPublicKeys[${i}]`,
|
|
5817
|
-
index: i
|
|
5818
|
-
}
|
|
5819
|
-
);
|
|
5820
|
-
}
|
|
5821
|
-
const hashObj2 = sha2568.create();
|
|
5822
|
-
hashObj2.update(pubKey);
|
|
5823
|
-
allHashes.push(hashObj2.digest());
|
|
5824
|
-
}
|
|
5825
|
-
const hashObj = sha2568.create();
|
|
5826
|
-
let networkBytes = new Uint8Array(4);
|
|
5827
|
-
new DataView(networkBytes.buffer).setUint32(
|
|
5828
|
-
0,
|
|
5829
|
-
tokenTransaction.network.valueOf(),
|
|
5830
|
-
false
|
|
5831
|
-
// false for big-endian
|
|
5832
|
-
);
|
|
5833
|
-
hashObj.update(networkBytes);
|
|
5834
|
-
allHashes.push(hashObj.digest());
|
|
5835
|
-
const clientTimestampHashObj = sha2568.create();
|
|
5836
|
-
const clientCreatedTs = tokenTransaction.clientCreatedTimestamp;
|
|
5837
|
-
if (!clientCreatedTs) {
|
|
5838
|
-
throw new ValidationError(
|
|
5839
|
-
"client created timestamp cannot be null for V1 token transactions",
|
|
5840
|
-
{
|
|
5841
|
-
field: "clientCreatedTimestamp"
|
|
5842
|
-
}
|
|
5843
|
-
);
|
|
5844
|
-
}
|
|
5845
|
-
const clientUnixTime = clientCreatedTs.getTime();
|
|
5846
|
-
const clientTimestampBytes = new Uint8Array(8);
|
|
5847
|
-
new DataView(clientTimestampBytes.buffer).setBigUint64(
|
|
5848
|
-
0,
|
|
5849
|
-
BigInt(clientUnixTime),
|
|
5850
|
-
false
|
|
5851
|
-
);
|
|
5852
|
-
clientTimestampHashObj.update(clientTimestampBytes);
|
|
5853
|
-
allHashes.push(clientTimestampHashObj.digest());
|
|
5854
|
-
if (!partialHash) {
|
|
5855
|
-
const expiryHashObj = sha2568.create();
|
|
5856
|
-
const expiryTimeBytes = new Uint8Array(8);
|
|
5857
|
-
const expiryUnixTime = tokenTransaction.expiryTime ? Math.floor(tokenTransaction.expiryTime.getTime() / 1e3) : 0;
|
|
5858
|
-
new DataView(expiryTimeBytes.buffer).setBigUint64(
|
|
5859
|
-
0,
|
|
5860
|
-
BigInt(expiryUnixTime),
|
|
5861
|
-
false
|
|
5862
|
-
// false for big-endian
|
|
5863
|
-
);
|
|
5864
|
-
expiryHashObj.update(expiryTimeBytes);
|
|
5865
|
-
allHashes.push(expiryHashObj.digest());
|
|
5866
|
-
}
|
|
5867
|
-
const attachments = tokenTransaction.invoiceAttachments;
|
|
5868
|
-
const lenHash = sha2568.create();
|
|
5869
|
-
const lenBytes = new Uint8Array(4);
|
|
5870
|
-
new DataView(lenBytes.buffer).setUint32(
|
|
5871
|
-
0,
|
|
5872
|
-
attachments ? attachments.length : 0,
|
|
5873
|
-
false
|
|
5874
|
-
);
|
|
5875
|
-
lenHash.update(lenBytes);
|
|
5876
|
-
allHashes.push(lenHash.digest());
|
|
5877
|
-
const sortedInvoices = [];
|
|
5878
|
-
if (attachments) {
|
|
5879
|
-
for (let i = 0; i < attachments.length; i++) {
|
|
5880
|
-
const attachment = attachments[i];
|
|
5881
|
-
if (!attachment) {
|
|
5882
|
-
throw new ValidationError(
|
|
5883
|
-
`invoice attachment at index ${i} cannot be null`,
|
|
5884
|
-
{
|
|
5885
|
-
field: `invoiceAttachments[${i}]`,
|
|
5886
|
-
index: i
|
|
5887
|
-
}
|
|
5888
|
-
);
|
|
5889
|
-
}
|
|
5890
|
-
const invoice = attachment.sparkInvoice;
|
|
5891
|
-
let idBytes;
|
|
5892
|
-
try {
|
|
5893
|
-
const decoded = bech32m3.decode(invoice, 500);
|
|
5894
|
-
const payload = SparkAddress.decode(bech32m3.fromWords(decoded.words));
|
|
5895
|
-
if (!payload.sparkInvoiceFields || !payload.sparkInvoiceFields.id) {
|
|
5896
|
-
throw new Error("missing spark invoice fields or id");
|
|
5897
|
-
}
|
|
5898
|
-
idBytes = payload.sparkInvoiceFields.id;
|
|
5899
|
-
} catch (err) {
|
|
5900
|
-
throw new ValidationError(
|
|
5901
|
-
`invalid invoice at ${i}`,
|
|
5902
|
-
{
|
|
5903
|
-
field: `invoiceAttachments[${i}].sparkInvoice`,
|
|
5904
|
-
index: i,
|
|
5905
|
-
value: invoice
|
|
5906
|
-
},
|
|
5907
|
-
err
|
|
5908
|
-
);
|
|
5909
|
-
}
|
|
5910
|
-
if (!idBytes || idBytes.length !== 16) {
|
|
5911
|
-
throw new ValidationError(`invalid invoice id at ${i}`, {
|
|
5912
|
-
field: `invoiceAttachments[${i}].sparkInvoice`,
|
|
5913
|
-
index: i
|
|
5914
|
-
});
|
|
5915
|
-
}
|
|
5916
|
-
sortedInvoices.push({ id: idBytes, raw: invoice });
|
|
5917
|
-
}
|
|
5918
|
-
}
|
|
5919
|
-
sortedInvoices.sort((a, b) => {
|
|
5920
|
-
for (let j = 0; j < a.id.length && j < b.id.length; j++) {
|
|
5921
|
-
const av = a.id[j];
|
|
5922
|
-
const bv = b.id[j];
|
|
5923
|
-
if (av !== bv) return av - bv;
|
|
5924
|
-
}
|
|
5925
|
-
return a.id.length - b.id.length;
|
|
5926
|
-
});
|
|
5927
|
-
const encoder = new TextEncoder();
|
|
5928
|
-
for (const k of sortedInvoices) {
|
|
5929
|
-
const h = sha2568.create();
|
|
5930
|
-
h.update(encoder.encode(k.raw));
|
|
5931
|
-
allHashes.push(h.digest());
|
|
5932
|
-
}
|
|
5933
|
-
const finalHashObj = sha2568.create();
|
|
5934
|
-
const concatenatedHashes = new Uint8Array(
|
|
5935
|
-
allHashes.reduce((sum, hash) => sum + hash.length, 0)
|
|
5936
|
-
);
|
|
5937
|
-
let offset = 0;
|
|
5938
|
-
for (const hash of allHashes) {
|
|
5939
|
-
concatenatedHashes.set(hash, offset);
|
|
5940
|
-
offset += hash.length;
|
|
5941
|
-
}
|
|
5942
|
-
finalHashObj.update(concatenatedHashes);
|
|
5943
|
-
return finalHashObj.digest();
|
|
5944
|
-
}
|
|
5945
|
-
function hashOperatorSpecificTokenTransactionSignablePayload(payload) {
|
|
5946
|
-
if (!payload) {
|
|
5947
|
-
throw new ValidationError(
|
|
5948
|
-
"operator specific token transaction signable payload cannot be null",
|
|
5949
|
-
{
|
|
5950
|
-
field: "payload"
|
|
5951
|
-
}
|
|
5952
|
-
);
|
|
5953
|
-
}
|
|
5954
|
-
let allHashes = [];
|
|
5955
|
-
if (payload.finalTokenTransactionHash) {
|
|
5956
|
-
const hashObj2 = sha2568.create();
|
|
5957
|
-
if (payload.finalTokenTransactionHash.length !== 32) {
|
|
5958
|
-
throw new ValidationError(`invalid final token transaction hash length`, {
|
|
5959
|
-
field: "finalTokenTransactionHash",
|
|
5960
|
-
value: payload.finalTokenTransactionHash,
|
|
5961
|
-
expectedLength: 32,
|
|
5962
|
-
actualLength: payload.finalTokenTransactionHash.length
|
|
5963
|
-
});
|
|
5964
|
-
}
|
|
5965
|
-
hashObj2.update(payload.finalTokenTransactionHash);
|
|
5966
|
-
allHashes.push(hashObj2.digest());
|
|
5967
|
-
}
|
|
5968
|
-
if (!payload.operatorIdentityPublicKey) {
|
|
5969
|
-
throw new ValidationError("operator identity public key cannot be null", {
|
|
5970
|
-
field: "operatorIdentityPublicKey"
|
|
5971
|
-
});
|
|
5972
|
-
}
|
|
5973
|
-
if (payload.operatorIdentityPublicKey.length === 0) {
|
|
5974
|
-
throw new ValidationError("operator identity public key cannot be empty", {
|
|
5975
|
-
field: "operatorIdentityPublicKey"
|
|
5976
|
-
});
|
|
5977
|
-
}
|
|
5978
|
-
const hashObj = sha2568.create();
|
|
5979
|
-
hashObj.update(payload.operatorIdentityPublicKey);
|
|
5980
|
-
allHashes.push(hashObj.digest());
|
|
5981
|
-
const finalHashObj = sha2568.create();
|
|
5982
|
-
const concatenatedHashes = new Uint8Array(
|
|
5983
|
-
allHashes.reduce((sum, hash) => sum + hash.length, 0)
|
|
5984
|
-
);
|
|
5985
|
-
let offset = 0;
|
|
5986
|
-
for (const hash of allHashes) {
|
|
5987
|
-
concatenatedHashes.set(hash, offset);
|
|
5988
|
-
offset += hash.length;
|
|
5989
|
-
}
|
|
5990
|
-
finalHashObj.update(concatenatedHashes);
|
|
5991
|
-
return finalHashObj.digest();
|
|
5992
|
-
}
|
|
5993
|
-
|
|
5994
|
-
// src/utils/token-keyshares.ts
|
|
5995
|
-
import { secp256k1 as secp256k18 } from "@noble/curves/secp256k1";
|
|
5996
|
-
function recoverRevocationSecretFromKeyshares(keyshares, threshold) {
|
|
5997
|
-
const shares = keyshares.map((keyshare) => ({
|
|
5998
|
-
fieldModulus: BigInt("0x" + secp256k18.CURVE.n.toString(16)),
|
|
5999
|
-
// secp256k1 curve order
|
|
6000
|
-
threshold,
|
|
6001
|
-
index: BigInt(keyshare.operatorIndex),
|
|
6002
|
-
share: BigInt(
|
|
6003
|
-
"0x" + Buffer.from(keyshare.keyshare.keyshare).toString("hex")
|
|
6004
|
-
),
|
|
6005
|
-
proofs: []
|
|
6006
|
-
}));
|
|
6007
|
-
const recoveredSecret = recoverSecret(shares);
|
|
6008
|
-
return bigIntToPrivateKey(recoveredSecret);
|
|
6009
|
-
}
|
|
6010
|
-
|
|
6011
|
-
// src/utils/token-transaction-validation.ts
|
|
6012
|
-
function areByteArraysEqual(a, b) {
|
|
6013
|
-
if (a.length !== b.length) {
|
|
6014
|
-
return false;
|
|
6015
|
-
}
|
|
6016
|
-
return a.every((byte, index) => byte === b[index]);
|
|
6017
|
-
}
|
|
6018
|
-
function hasDuplicates(array) {
|
|
6019
|
-
return new Set(array).size !== array.length;
|
|
6020
|
-
}
|
|
6021
|
-
function validateTokenTransactionV0(finalTokenTransaction, partialTokenTransaction, signingOperators, keyshareInfo, expectedWithdrawBondSats, expectedWithdrawRelativeBlockLocktime, expectedThreshold) {
|
|
6022
|
-
if (finalTokenTransaction.network !== partialTokenTransaction.network) {
|
|
6023
|
-
throw new InternalValidationError(
|
|
6024
|
-
"Network mismatch in response token transaction",
|
|
6025
|
-
{
|
|
6026
|
-
value: finalTokenTransaction.network,
|
|
6027
|
-
expected: partialTokenTransaction.network
|
|
6028
|
-
}
|
|
6029
|
-
);
|
|
6030
|
-
}
|
|
6031
|
-
if (!finalTokenTransaction.tokenInputs) {
|
|
6032
|
-
throw new InternalValidationError(
|
|
6033
|
-
"Token inputs missing in final transaction",
|
|
6034
|
-
{
|
|
6035
|
-
value: finalTokenTransaction
|
|
6036
|
-
}
|
|
6037
|
-
);
|
|
6038
|
-
}
|
|
6039
|
-
if (!partialTokenTransaction.tokenInputs) {
|
|
6040
|
-
throw new InternalValidationError(
|
|
6041
|
-
"Token inputs missing in partial transaction",
|
|
6042
|
-
{
|
|
6043
|
-
value: partialTokenTransaction
|
|
6044
|
-
}
|
|
6045
|
-
);
|
|
6046
|
-
}
|
|
6047
|
-
if (finalTokenTransaction.tokenInputs.$case !== partialTokenTransaction.tokenInputs.$case) {
|
|
6048
|
-
throw new InternalValidationError(
|
|
6049
|
-
`Transaction type mismatch: final transaction has ${finalTokenTransaction.tokenInputs.$case}, partial transaction has ${partialTokenTransaction.tokenInputs.$case}`,
|
|
6050
|
-
{
|
|
6051
|
-
value: finalTokenTransaction.tokenInputs.$case,
|
|
6052
|
-
expected: partialTokenTransaction.tokenInputs.$case
|
|
6053
|
-
}
|
|
6054
|
-
);
|
|
6055
|
-
}
|
|
6056
|
-
if (finalTokenTransaction.sparkOperatorIdentityPublicKeys.length !== partialTokenTransaction.sparkOperatorIdentityPublicKeys.length) {
|
|
6057
|
-
throw new InternalValidationError(
|
|
6058
|
-
"Spark operator identity public keys count mismatch",
|
|
6059
|
-
{
|
|
6060
|
-
value: finalTokenTransaction.sparkOperatorIdentityPublicKeys.length,
|
|
6061
|
-
expected: partialTokenTransaction.sparkOperatorIdentityPublicKeys.length
|
|
6062
|
-
}
|
|
6063
|
-
);
|
|
6064
|
-
}
|
|
6065
|
-
if (partialTokenTransaction.tokenInputs.$case === "mintInput" && finalTokenTransaction.tokenInputs.$case === "mintInput") {
|
|
6066
|
-
const finalMintInput = finalTokenTransaction.tokenInputs.mintInput;
|
|
6067
|
-
const partialMintInput = partialTokenTransaction.tokenInputs.mintInput;
|
|
6068
|
-
if (!areByteArraysEqual(
|
|
6069
|
-
finalMintInput.issuerPublicKey,
|
|
6070
|
-
partialMintInput.issuerPublicKey
|
|
6071
|
-
)) {
|
|
6072
|
-
throw new InternalValidationError(
|
|
6073
|
-
"Issuer public key mismatch in mint input",
|
|
6074
|
-
{
|
|
6075
|
-
value: finalMintInput.issuerPublicKey.toString(),
|
|
6076
|
-
expected: partialMintInput.issuerPublicKey.toString()
|
|
6077
|
-
}
|
|
6078
|
-
);
|
|
6079
|
-
}
|
|
6080
|
-
} else if (partialTokenTransaction.tokenInputs.$case === "transferInput" && finalTokenTransaction.tokenInputs.$case === "transferInput") {
|
|
6081
|
-
const finalTransferInput = finalTokenTransaction.tokenInputs.transferInput;
|
|
6082
|
-
const partialTransferInput = partialTokenTransaction.tokenInputs.transferInput;
|
|
6083
|
-
if (finalTransferInput.outputsToSpend.length !== partialTransferInput.outputsToSpend.length) {
|
|
6084
|
-
throw new InternalValidationError(
|
|
6085
|
-
"Outputs to spend count mismatch in transfer input",
|
|
6086
|
-
{
|
|
6087
|
-
value: finalTransferInput.outputsToSpend.length,
|
|
6088
|
-
expected: partialTransferInput.outputsToSpend.length
|
|
6089
|
-
}
|
|
6090
|
-
);
|
|
6091
|
-
}
|
|
6092
|
-
for (let i = 0; i < finalTransferInput.outputsToSpend.length; i++) {
|
|
6093
|
-
const finalOutput = finalTransferInput.outputsToSpend[i];
|
|
6094
|
-
const partialOutput = partialTransferInput.outputsToSpend[i];
|
|
6095
|
-
if (!finalOutput) {
|
|
6096
|
-
throw new InternalValidationError(
|
|
6097
|
-
"Token output to spend missing in final transaction",
|
|
6098
|
-
{
|
|
6099
|
-
outputIndex: i,
|
|
6100
|
-
value: finalOutput
|
|
6101
|
-
}
|
|
6102
|
-
);
|
|
6103
|
-
}
|
|
6104
|
-
if (!partialOutput) {
|
|
6105
|
-
throw new InternalValidationError(
|
|
6106
|
-
"Token output to spend missing in partial transaction",
|
|
6107
|
-
{
|
|
6108
|
-
outputIndex: i,
|
|
6109
|
-
value: partialOutput
|
|
6110
|
-
}
|
|
6111
|
-
);
|
|
6112
|
-
}
|
|
6113
|
-
if (!areByteArraysEqual(
|
|
6114
|
-
finalOutput.prevTokenTransactionHash,
|
|
6115
|
-
partialOutput.prevTokenTransactionHash
|
|
6116
|
-
)) {
|
|
6117
|
-
throw new InternalValidationError(
|
|
6118
|
-
"Previous token transaction hash mismatch in transfer input",
|
|
6119
|
-
{
|
|
6120
|
-
outputIndex: i,
|
|
6121
|
-
value: finalOutput.prevTokenTransactionHash.toString(),
|
|
6122
|
-
expected: partialOutput.prevTokenTransactionHash.toString()
|
|
6123
|
-
}
|
|
6124
|
-
);
|
|
6125
|
-
}
|
|
6126
|
-
if (finalOutput.prevTokenTransactionVout !== partialOutput.prevTokenTransactionVout) {
|
|
6127
|
-
throw new InternalValidationError(
|
|
6128
|
-
"Previous token transaction vout mismatch in transfer input",
|
|
6129
|
-
{
|
|
6130
|
-
outputIndex: i,
|
|
6131
|
-
value: finalOutput.prevTokenTransactionVout,
|
|
6132
|
-
expected: partialOutput.prevTokenTransactionVout
|
|
6133
|
-
}
|
|
6134
|
-
);
|
|
6135
|
-
}
|
|
6136
|
-
}
|
|
6137
|
-
}
|
|
6138
|
-
if (finalTokenTransaction.tokenOutputs.length !== partialTokenTransaction.tokenOutputs.length) {
|
|
6139
|
-
throw new InternalValidationError("Token outputs count mismatch", {
|
|
6140
|
-
value: finalTokenTransaction.tokenOutputs.length,
|
|
6141
|
-
expected: partialTokenTransaction.tokenOutputs.length
|
|
6142
|
-
});
|
|
6143
|
-
}
|
|
6144
|
-
for (let i = 0; i < finalTokenTransaction.tokenOutputs.length; i++) {
|
|
6145
|
-
const finalOutput = finalTokenTransaction.tokenOutputs[i];
|
|
6146
|
-
const partialOutput = partialTokenTransaction.tokenOutputs[i];
|
|
6147
|
-
if (!finalOutput) {
|
|
6148
|
-
throw new InternalValidationError(
|
|
6149
|
-
"Token output missing in final transaction",
|
|
6150
|
-
{
|
|
6151
|
-
outputIndex: i,
|
|
6152
|
-
value: finalOutput
|
|
6153
|
-
}
|
|
6154
|
-
);
|
|
6155
|
-
}
|
|
6156
|
-
if (!partialOutput) {
|
|
6157
|
-
throw new InternalValidationError(
|
|
6158
|
-
"Token output missing in partial transaction",
|
|
6159
|
-
{
|
|
6160
|
-
outputIndex: i,
|
|
6161
|
-
value: partialOutput
|
|
6162
|
-
}
|
|
6163
|
-
);
|
|
6164
|
-
}
|
|
6165
|
-
if (!areByteArraysEqual(
|
|
6166
|
-
finalOutput.ownerPublicKey,
|
|
6167
|
-
partialOutput.ownerPublicKey
|
|
6168
|
-
)) {
|
|
6169
|
-
throw new InternalValidationError(
|
|
6170
|
-
"Owner public key mismatch in token output",
|
|
6171
|
-
{
|
|
6172
|
-
outputIndex: i,
|
|
6173
|
-
value: finalOutput.ownerPublicKey.toString(),
|
|
6174
|
-
expected: partialOutput.ownerPublicKey.toString()
|
|
6175
|
-
}
|
|
6176
|
-
);
|
|
6177
|
-
}
|
|
6178
|
-
if (finalOutput.tokenPublicKey !== void 0 && partialOutput.tokenPublicKey !== void 0 && !areByteArraysEqual(
|
|
6179
|
-
finalOutput.tokenPublicKey,
|
|
6180
|
-
partialOutput.tokenPublicKey
|
|
6181
|
-
)) {
|
|
6182
|
-
throw new InternalValidationError(
|
|
6183
|
-
"Token public key mismatch in token output",
|
|
6184
|
-
{
|
|
6185
|
-
outputIndex: i,
|
|
6186
|
-
value: finalOutput.tokenPublicKey?.toString(),
|
|
6187
|
-
expected: partialOutput.tokenPublicKey?.toString()
|
|
6188
|
-
}
|
|
6189
|
-
);
|
|
6190
|
-
}
|
|
6191
|
-
if (!areByteArraysEqual(finalOutput.tokenAmount, partialOutput.tokenAmount)) {
|
|
6192
|
-
throw new InternalValidationError(
|
|
6193
|
-
"Token amount mismatch in token output",
|
|
6194
|
-
{
|
|
6195
|
-
outputIndex: i,
|
|
6196
|
-
value: finalOutput.tokenAmount.toString(),
|
|
6197
|
-
expected: partialOutput.tokenAmount.toString()
|
|
6198
|
-
}
|
|
6199
|
-
);
|
|
6200
|
-
}
|
|
6201
|
-
if (finalOutput.withdrawBondSats !== void 0) {
|
|
6202
|
-
if (finalOutput.withdrawBondSats !== expectedWithdrawBondSats) {
|
|
6203
|
-
throw new InternalValidationError(
|
|
6204
|
-
"Withdraw bond sats mismatch in token output",
|
|
6205
|
-
{
|
|
6206
|
-
outputIndex: i,
|
|
6207
|
-
value: finalOutput.withdrawBondSats,
|
|
6208
|
-
expected: expectedWithdrawBondSats
|
|
6209
|
-
}
|
|
6210
|
-
);
|
|
6211
|
-
}
|
|
6212
|
-
}
|
|
6213
|
-
if (finalOutput.withdrawRelativeBlockLocktime !== void 0) {
|
|
6214
|
-
if (finalOutput.withdrawRelativeBlockLocktime !== expectedWithdrawRelativeBlockLocktime) {
|
|
6215
|
-
throw new InternalValidationError(
|
|
6216
|
-
"Withdraw relative block locktime mismatch in token output",
|
|
6217
|
-
{
|
|
6218
|
-
outputIndex: i,
|
|
6219
|
-
value: finalOutput.withdrawRelativeBlockLocktime,
|
|
6220
|
-
expected: expectedWithdrawRelativeBlockLocktime
|
|
6221
|
-
}
|
|
6222
|
-
);
|
|
6223
|
-
}
|
|
6224
|
-
}
|
|
6225
|
-
if (keyshareInfo.threshold !== expectedThreshold) {
|
|
6226
|
-
throw new InternalValidationError(
|
|
6227
|
-
"Threshold mismatch: expected " + expectedThreshold + " but got " + keyshareInfo.threshold,
|
|
6228
|
-
{
|
|
6229
|
-
field: "threshold",
|
|
6230
|
-
value: keyshareInfo.threshold,
|
|
6231
|
-
expected: expectedThreshold
|
|
6232
|
-
}
|
|
6233
|
-
);
|
|
6234
|
-
}
|
|
6235
|
-
}
|
|
6236
|
-
if (keyshareInfo.ownerIdentifiers.length !== Object.keys(signingOperators).length) {
|
|
6237
|
-
throw new InternalValidationError(
|
|
6238
|
-
`Keyshare operator count (${keyshareInfo.ownerIdentifiers.length}) does not match signing operator count (${Object.keys(signingOperators).length})`,
|
|
6239
|
-
{
|
|
6240
|
-
keyshareInfo: keyshareInfo.ownerIdentifiers.length,
|
|
6241
|
-
signingOperators: Object.keys(signingOperators).length
|
|
6242
|
-
}
|
|
6243
|
-
);
|
|
6244
|
-
}
|
|
6245
|
-
if (hasDuplicates(keyshareInfo.ownerIdentifiers)) {
|
|
6246
|
-
throw new InternalValidationError(
|
|
6247
|
-
"Duplicate ownerIdentifiers found in keyshareInfo",
|
|
5593
|
+
function hashOperatorSpecificTokenTransactionSignablePayload(payload) {
|
|
5594
|
+
if (!payload) {
|
|
5595
|
+
throw new ValidationError(
|
|
5596
|
+
"operator specific token transaction signable payload cannot be null",
|
|
6248
5597
|
{
|
|
6249
|
-
|
|
5598
|
+
field: "payload"
|
|
6250
5599
|
}
|
|
6251
5600
|
);
|
|
6252
5601
|
}
|
|
6253
|
-
|
|
6254
|
-
|
|
6255
|
-
|
|
6256
|
-
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
|
|
5602
|
+
let allHashes = [];
|
|
5603
|
+
if (payload.finalTokenTransactionHash) {
|
|
5604
|
+
const hashObj2 = sha2568.create();
|
|
5605
|
+
if (payload.finalTokenTransactionHash.length !== 32) {
|
|
5606
|
+
throw new ValidationError(`invalid final token transaction hash length`, {
|
|
5607
|
+
field: "finalTokenTransactionHash",
|
|
5608
|
+
value: payload.finalTokenTransactionHash,
|
|
5609
|
+
expectedLength: 32,
|
|
5610
|
+
actualLength: payload.finalTokenTransactionHash.length
|
|
5611
|
+
});
|
|
6262
5612
|
}
|
|
5613
|
+
hashObj2.update(payload.finalTokenTransactionHash);
|
|
5614
|
+
allHashes.push(hashObj2.digest());
|
|
5615
|
+
}
|
|
5616
|
+
if (!payload.operatorIdentityPublicKey) {
|
|
5617
|
+
throw new ValidationError("operator identity public key cannot be null", {
|
|
5618
|
+
field: "operatorIdentityPublicKey"
|
|
5619
|
+
});
|
|
5620
|
+
}
|
|
5621
|
+
if (payload.operatorIdentityPublicKey.length === 0) {
|
|
5622
|
+
throw new ValidationError("operator identity public key cannot be empty", {
|
|
5623
|
+
field: "operatorIdentityPublicKey"
|
|
5624
|
+
});
|
|
5625
|
+
}
|
|
5626
|
+
const hashObj = sha2568.create();
|
|
5627
|
+
hashObj.update(payload.operatorIdentityPublicKey);
|
|
5628
|
+
allHashes.push(hashObj.digest());
|
|
5629
|
+
const finalHashObj = sha2568.create();
|
|
5630
|
+
const concatenatedHashes = new Uint8Array(
|
|
5631
|
+
allHashes.reduce((sum, hash) => sum + hash.length, 0)
|
|
5632
|
+
);
|
|
5633
|
+
let offset = 0;
|
|
5634
|
+
for (const hash of allHashes) {
|
|
5635
|
+
concatenatedHashes.set(hash, offset);
|
|
5636
|
+
offset += hash.length;
|
|
5637
|
+
}
|
|
5638
|
+
finalHashObj.update(concatenatedHashes);
|
|
5639
|
+
return finalHashObj.digest();
|
|
5640
|
+
}
|
|
5641
|
+
|
|
5642
|
+
// src/utils/token-transaction-validation.ts
|
|
5643
|
+
function areByteArraysEqual(a, b) {
|
|
5644
|
+
if (a.length !== b.length) {
|
|
5645
|
+
return false;
|
|
6263
5646
|
}
|
|
5647
|
+
return a.every((byte, index) => byte === b[index]);
|
|
5648
|
+
}
|
|
5649
|
+
function hasDuplicates(array) {
|
|
5650
|
+
return new Set(array).size !== array.length;
|
|
6264
5651
|
}
|
|
6265
5652
|
function validateTokenTransaction(finalTokenTransaction, partialTokenTransaction, signingOperators, keyshareInfo, expectedWithdrawBondSats, expectedWithdrawRelativeBlockLocktime, expectedThreshold) {
|
|
6266
5653
|
if (finalTokenTransaction.network !== partialTokenTransaction.network) {
|
|
@@ -6614,16 +6001,6 @@ var TokenTransactionService = class {
|
|
|
6614
6001
|
tokenIdentifier,
|
|
6615
6002
|
this.config.getNetworkType()
|
|
6616
6003
|
).tokenIdentifier;
|
|
6617
|
-
let tokenPublicKey;
|
|
6618
|
-
if (this.config.getTokenTransactionVersion() === "V0") {
|
|
6619
|
-
const tokenClient = await this.connectionManager.createSparkTokenClient(
|
|
6620
|
-
this.config.getCoordinatorAddress()
|
|
6621
|
-
);
|
|
6622
|
-
const tokenMetadata = await tokenClient.query_token_metadata({
|
|
6623
|
-
tokenIdentifiers: [rawTokenIdentifier]
|
|
6624
|
-
});
|
|
6625
|
-
tokenPublicKey = tokenMetadata.tokenMetadata[0].issuerPublicKey;
|
|
6626
|
-
}
|
|
6627
6004
|
let sparkInvoices = [];
|
|
6628
6005
|
const tokenOutputData = receiverOutputs.map((transfer) => {
|
|
6629
6006
|
const receiverAddress = decodeSparkAddress(
|
|
@@ -6633,7 +6010,7 @@ var TokenTransactionService = class {
|
|
|
6633
6010
|
if (receiverAddress.sparkInvoiceFields) {
|
|
6634
6011
|
sparkInvoices.push(transfer.receiverSparkAddress);
|
|
6635
6012
|
}
|
|
6636
|
-
if (
|
|
6013
|
+
if (receiverAddress.sparkInvoiceFields) {
|
|
6637
6014
|
return {
|
|
6638
6015
|
receiverPublicKey: hexToBytes6(receiverAddress.identityPublicKey),
|
|
6639
6016
|
rawTokenIdentifier,
|
|
@@ -6644,24 +6021,14 @@ var TokenTransactionService = class {
|
|
|
6644
6021
|
return {
|
|
6645
6022
|
receiverPublicKey: hexToBytes6(receiverAddress.identityPublicKey),
|
|
6646
6023
|
rawTokenIdentifier,
|
|
6647
|
-
tokenPublicKey,
|
|
6648
|
-
// Remove for full v0 deprecation
|
|
6649
6024
|
tokenAmount: transfer.tokenAmount
|
|
6650
6025
|
};
|
|
6651
6026
|
});
|
|
6652
|
-
|
|
6653
|
-
|
|
6654
|
-
|
|
6655
|
-
|
|
6656
|
-
|
|
6657
|
-
);
|
|
6658
|
-
} else {
|
|
6659
|
-
tokenTransaction = await this.constructTransferTokenTransaction(
|
|
6660
|
-
outputsToUse,
|
|
6661
|
-
tokenOutputData,
|
|
6662
|
-
sparkInvoices
|
|
6663
|
-
);
|
|
6664
|
-
}
|
|
6027
|
+
const tokenTransaction = await this.constructTransferTokenTransaction(
|
|
6028
|
+
outputsToUse,
|
|
6029
|
+
tokenOutputData,
|
|
6030
|
+
sparkInvoices
|
|
6031
|
+
);
|
|
6665
6032
|
const txId = await this.broadcastTokenTransaction(
|
|
6666
6033
|
tokenTransaction,
|
|
6667
6034
|
outputsToUse.map((output) => output.output.ownerPublicKey),
|
|
@@ -6669,44 +6036,6 @@ var TokenTransactionService = class {
|
|
|
6669
6036
|
);
|
|
6670
6037
|
return txId;
|
|
6671
6038
|
}
|
|
6672
|
-
async constructTransferTokenTransactionV0(selectedOutputs, tokenOutputData) {
|
|
6673
|
-
selectedOutputs.sort(
|
|
6674
|
-
(a, b) => a.previousTransactionVout - b.previousTransactionVout
|
|
6675
|
-
);
|
|
6676
|
-
const availableTokenAmount = sumAvailableTokens(selectedOutputs);
|
|
6677
|
-
const totalRequestedAmount = tokenOutputData.reduce(
|
|
6678
|
-
(sum, output) => sum + output.tokenAmount,
|
|
6679
|
-
0n
|
|
6680
|
-
);
|
|
6681
|
-
const tokenOutputs = tokenOutputData.map((output) => ({
|
|
6682
|
-
ownerPublicKey: output.receiverPublicKey,
|
|
6683
|
-
tokenPublicKey: output.tokenPublicKey,
|
|
6684
|
-
tokenAmount: numberToBytesBE2(output.tokenAmount, 16)
|
|
6685
|
-
}));
|
|
6686
|
-
if (availableTokenAmount > totalRequestedAmount) {
|
|
6687
|
-
const changeAmount = availableTokenAmount - totalRequestedAmount;
|
|
6688
|
-
const firstTokenPublicKey = tokenOutputData[0].tokenPublicKey;
|
|
6689
|
-
tokenOutputs.push({
|
|
6690
|
-
ownerPublicKey: await this.config.signer.getIdentityPublicKey(),
|
|
6691
|
-
tokenPublicKey: firstTokenPublicKey,
|
|
6692
|
-
tokenAmount: numberToBytesBE2(changeAmount, 16)
|
|
6693
|
-
});
|
|
6694
|
-
}
|
|
6695
|
-
return {
|
|
6696
|
-
network: this.config.getNetworkProto(),
|
|
6697
|
-
tokenInputs: {
|
|
6698
|
-
$case: "transferInput",
|
|
6699
|
-
transferInput: {
|
|
6700
|
-
outputsToSpend: selectedOutputs.map((output) => ({
|
|
6701
|
-
prevTokenTransactionHash: output.previousTransactionHash,
|
|
6702
|
-
prevTokenTransactionVout: output.previousTransactionVout
|
|
6703
|
-
}))
|
|
6704
|
-
}
|
|
6705
|
-
},
|
|
6706
|
-
tokenOutputs,
|
|
6707
|
-
sparkOperatorIdentityPublicKeys: this.collectOperatorIdentityPublicKeys()
|
|
6708
|
-
};
|
|
6709
|
-
}
|
|
6710
6039
|
async constructTransferTokenTransaction(selectedOutputs, tokenOutputData, sparkInvoices) {
|
|
6711
6040
|
selectedOutputs.sort(
|
|
6712
6041
|
(a, b) => a.previousTransactionVout - b.previousTransactionVout
|
|
@@ -6753,229 +6082,27 @@ var TokenTransactionService = class {
|
|
|
6753
6082
|
}
|
|
6754
6083
|
collectOperatorIdentityPublicKeys() {
|
|
6755
6084
|
const operatorKeys = [];
|
|
6756
|
-
for (const [_, operator] of Object.entries(
|
|
6757
|
-
this.config.getSigningOperators()
|
|
6758
|
-
)) {
|
|
6759
|
-
operatorKeys.push(hexToBytes6(operator.identityPublicKey));
|
|
6760
|
-
}
|
|
6761
|
-
return operatorKeys;
|
|
6762
|
-
}
|
|
6763
|
-
async broadcastTokenTransaction(tokenTransaction, outputsToSpendSigningPublicKeys, outputsToSpendCommitments) {
|
|
6764
|
-
const signingOperators = this.config.getSigningOperators();
|
|
6765
|
-
if (!isTokenTransaction(tokenTransaction)) {
|
|
6766
|
-
return this.broadcastTokenTransactionV0(
|
|
6767
|
-
tokenTransaction,
|
|
6768
|
-
signingOperators,
|
|
6769
|
-
outputsToSpendSigningPublicKeys,
|
|
6770
|
-
outputsToSpendCommitments
|
|
6771
|
-
);
|
|
6772
|
-
} else {
|
|
6773
|
-
return this.broadcastTokenTransactionV1(
|
|
6774
|
-
tokenTransaction,
|
|
6775
|
-
signingOperators,
|
|
6776
|
-
outputsToSpendSigningPublicKeys,
|
|
6777
|
-
outputsToSpendCommitments
|
|
6778
|
-
);
|
|
6779
|
-
}
|
|
6780
|
-
}
|
|
6781
|
-
async broadcastTokenTransactionV0(tokenTransaction, signingOperators, outputsToSpendSigningPublicKeys, outputsToSpendCommitments) {
|
|
6782
|
-
const { finalTokenTransaction, finalTokenTransactionHash, threshold } = await this.startTokenTransactionV0(
|
|
6783
|
-
tokenTransaction,
|
|
6784
|
-
signingOperators,
|
|
6785
|
-
outputsToSpendSigningPublicKeys,
|
|
6786
|
-
outputsToSpendCommitments
|
|
6787
|
-
);
|
|
6788
|
-
const { successfulSignatures } = await this.signTokenTransactionV0(
|
|
6789
|
-
finalTokenTransaction,
|
|
6790
|
-
finalTokenTransactionHash,
|
|
6791
|
-
signingOperators
|
|
6792
|
-
);
|
|
6793
|
-
if (finalTokenTransaction.tokenInputs.$case === "transferInput") {
|
|
6794
|
-
const outputsToSpend = finalTokenTransaction.tokenInputs.transferInput.outputsToSpend;
|
|
6795
|
-
const errors = [];
|
|
6796
|
-
const revocationSecrets = [];
|
|
6797
|
-
for (let outputIndex = 0; outputIndex < outputsToSpend.length; outputIndex++) {
|
|
6798
|
-
const outputKeyshares = successfulSignatures.map(({ identifier, response }) => ({
|
|
6799
|
-
operatorIndex: parseInt(identifier, 16),
|
|
6800
|
-
keyshare: response.revocationKeyshares[outputIndex]
|
|
6801
|
-
}));
|
|
6802
|
-
if (outputKeyshares.length < threshold) {
|
|
6803
|
-
errors.push(
|
|
6804
|
-
new ValidationError("Insufficient keyshares", {
|
|
6805
|
-
field: "outputKeyshares",
|
|
6806
|
-
value: outputKeyshares.length,
|
|
6807
|
-
expected: threshold,
|
|
6808
|
-
index: outputIndex
|
|
6809
|
-
})
|
|
6810
|
-
);
|
|
6811
|
-
}
|
|
6812
|
-
const seenIndices = /* @__PURE__ */ new Set();
|
|
6813
|
-
for (const { operatorIndex } of outputKeyshares) {
|
|
6814
|
-
if (seenIndices.has(operatorIndex)) {
|
|
6815
|
-
errors.push(
|
|
6816
|
-
new ValidationError("Duplicate operator index", {
|
|
6817
|
-
field: "outputKeyshares",
|
|
6818
|
-
value: operatorIndex,
|
|
6819
|
-
expected: "Unique operator index",
|
|
6820
|
-
index: outputIndex
|
|
6821
|
-
})
|
|
6822
|
-
);
|
|
6823
|
-
}
|
|
6824
|
-
seenIndices.add(operatorIndex);
|
|
6825
|
-
}
|
|
6826
|
-
const revocationSecret = recoverRevocationSecretFromKeyshares(
|
|
6827
|
-
outputKeyshares,
|
|
6828
|
-
threshold
|
|
6829
|
-
);
|
|
6830
|
-
const derivedRevocationCommitment = secp256k19.getPublicKey(
|
|
6831
|
-
revocationSecret,
|
|
6832
|
-
true
|
|
6833
|
-
);
|
|
6834
|
-
if (!outputsToSpendCommitments || !outputsToSpendCommitments[outputIndex] || !derivedRevocationCommitment.every(
|
|
6835
|
-
(byte, i) => byte === outputsToSpendCommitments[outputIndex][i]
|
|
6836
|
-
)) {
|
|
6837
|
-
errors.push(
|
|
6838
|
-
new InternalValidationError(
|
|
6839
|
-
"Revocation commitment verification failed",
|
|
6840
|
-
{
|
|
6841
|
-
field: "revocationCommitment",
|
|
6842
|
-
value: derivedRevocationCommitment,
|
|
6843
|
-
expected: bytesToHex6(outputsToSpendCommitments[outputIndex]),
|
|
6844
|
-
outputIndex
|
|
6845
|
-
}
|
|
6846
|
-
)
|
|
6847
|
-
);
|
|
6848
|
-
}
|
|
6849
|
-
revocationSecrets.push({
|
|
6850
|
-
inputIndex: outputIndex,
|
|
6851
|
-
revocationSecret
|
|
6852
|
-
});
|
|
6853
|
-
}
|
|
6854
|
-
if (errors.length > 0) {
|
|
6855
|
-
throw new ValidationError(
|
|
6856
|
-
"Multiple validation errors occurred across outputs",
|
|
6857
|
-
{
|
|
6858
|
-
field: "outputValidation",
|
|
6859
|
-
value: errors
|
|
6860
|
-
}
|
|
6861
|
-
);
|
|
6862
|
-
}
|
|
6863
|
-
await this.finalizeTokenTransaction(
|
|
6864
|
-
finalTokenTransaction,
|
|
6865
|
-
revocationSecrets,
|
|
6866
|
-
threshold
|
|
6867
|
-
);
|
|
6868
|
-
}
|
|
6869
|
-
return bytesToHex6(finalTokenTransactionHash);
|
|
6870
|
-
}
|
|
6871
|
-
async broadcastTokenTransactionV1(tokenTransaction, signingOperators, outputsToSpendSigningPublicKeys, outputsToSpendCommitments) {
|
|
6872
|
-
const { finalTokenTransaction, finalTokenTransactionHash, threshold } = await this.startTokenTransaction(
|
|
6873
|
-
tokenTransaction,
|
|
6874
|
-
signingOperators,
|
|
6875
|
-
outputsToSpendSigningPublicKeys,
|
|
6876
|
-
outputsToSpendCommitments
|
|
6877
|
-
);
|
|
6878
|
-
await this.signTokenTransaction(
|
|
6879
|
-
finalTokenTransaction,
|
|
6880
|
-
finalTokenTransactionHash,
|
|
6881
|
-
signingOperators
|
|
6882
|
-
);
|
|
6883
|
-
return bytesToHex6(finalTokenTransactionHash);
|
|
6884
|
-
}
|
|
6885
|
-
async startTokenTransactionV0(tokenTransaction, signingOperators, outputsToSpendSigningPublicKeys, outputsToSpendCommitments) {
|
|
6886
|
-
const sparkClient = await this.connectionManager.createSparkClient(
|
|
6887
|
-
this.config.getCoordinatorAddress()
|
|
6888
|
-
);
|
|
6889
|
-
const partialTokenTransactionHash = hashTokenTransactionV0(
|
|
6890
|
-
tokenTransaction,
|
|
6891
|
-
true
|
|
6892
|
-
);
|
|
6893
|
-
const ownerSignaturesWithIndex = [];
|
|
6894
|
-
if (tokenTransaction.tokenInputs.$case === "mintInput") {
|
|
6895
|
-
const issuerPublicKey = tokenTransaction.tokenInputs.mintInput.issuerPublicKey;
|
|
6896
|
-
if (!issuerPublicKey) {
|
|
6897
|
-
throw new ValidationError("Invalid mint input", {
|
|
6898
|
-
field: "issuerPublicKey",
|
|
6899
|
-
value: null,
|
|
6900
|
-
expected: "Non-null issuer public key"
|
|
6901
|
-
});
|
|
6902
|
-
}
|
|
6903
|
-
const ownerSignature = await this.signMessageWithKey(
|
|
6904
|
-
partialTokenTransactionHash,
|
|
6905
|
-
issuerPublicKey
|
|
6906
|
-
);
|
|
6907
|
-
ownerSignaturesWithIndex.push({
|
|
6908
|
-
signature: ownerSignature,
|
|
6909
|
-
inputIndex: 0
|
|
6910
|
-
});
|
|
6911
|
-
} else if (tokenTransaction.tokenInputs.$case === "transferInput") {
|
|
6912
|
-
if (!outputsToSpendSigningPublicKeys || !outputsToSpendCommitments) {
|
|
6913
|
-
throw new ValidationError("Invalid transfer input", {
|
|
6914
|
-
field: "outputsToSpend",
|
|
6915
|
-
value: {
|
|
6916
|
-
signingPublicKeys: outputsToSpendSigningPublicKeys,
|
|
6917
|
-
revocationPublicKeys: outputsToSpendCommitments
|
|
6918
|
-
},
|
|
6919
|
-
expected: "Non-null signing and revocation public keys"
|
|
6920
|
-
});
|
|
6921
|
-
}
|
|
6922
|
-
for (const [i, key] of outputsToSpendSigningPublicKeys.entries()) {
|
|
6923
|
-
if (!key) {
|
|
6924
|
-
throw new ValidationError("Invalid signing key", {
|
|
6925
|
-
field: "outputsToSpendSigningPublicKeys",
|
|
6926
|
-
value: i,
|
|
6927
|
-
expected: "Non-null signing key"
|
|
6928
|
-
});
|
|
6929
|
-
}
|
|
6930
|
-
const ownerSignature = await this.signMessageWithKey(
|
|
6931
|
-
partialTokenTransactionHash,
|
|
6932
|
-
key
|
|
6933
|
-
);
|
|
6934
|
-
ownerSignaturesWithIndex.push({
|
|
6935
|
-
signature: ownerSignature,
|
|
6936
|
-
inputIndex: i
|
|
6937
|
-
});
|
|
6938
|
-
}
|
|
6939
|
-
}
|
|
6940
|
-
const startResponse = await sparkClient.start_token_transaction(
|
|
6941
|
-
{
|
|
6942
|
-
identityPublicKey: await this.config.signer.getIdentityPublicKey(),
|
|
6943
|
-
partialTokenTransaction: tokenTransaction,
|
|
6944
|
-
tokenTransactionSignatures: {
|
|
6945
|
-
ownerSignatures: ownerSignaturesWithIndex
|
|
6946
|
-
}
|
|
6947
|
-
},
|
|
6948
|
-
{
|
|
6949
|
-
retry: true,
|
|
6950
|
-
retryableStatuses: ["UNKNOWN", "UNAVAILABLE", "CANCELLED", "INTERNAL"],
|
|
6951
|
-
retryMaxAttempts: 3
|
|
6952
|
-
}
|
|
6953
|
-
);
|
|
6954
|
-
if (!startResponse.finalTokenTransaction) {
|
|
6955
|
-
throw new Error("Final token transaction missing in start response");
|
|
6956
|
-
}
|
|
6957
|
-
if (!startResponse.keyshareInfo) {
|
|
6958
|
-
throw new Error("Keyshare info missing in start response");
|
|
6085
|
+
for (const [_, operator] of Object.entries(
|
|
6086
|
+
this.config.getSigningOperators()
|
|
6087
|
+
)) {
|
|
6088
|
+
operatorKeys.push(hexToBytes6(operator.identityPublicKey));
|
|
6959
6089
|
}
|
|
6960
|
-
|
|
6961
|
-
|
|
6090
|
+
return operatorKeys;
|
|
6091
|
+
}
|
|
6092
|
+
async broadcastTokenTransaction(tokenTransaction, outputsToSpendSigningPublicKeys, outputsToSpendCommitments) {
|
|
6093
|
+
const signingOperators = this.config.getSigningOperators();
|
|
6094
|
+
const { finalTokenTransaction, finalTokenTransactionHash, threshold } = await this.startTokenTransaction(
|
|
6962
6095
|
tokenTransaction,
|
|
6963
6096
|
signingOperators,
|
|
6964
|
-
|
|
6965
|
-
|
|
6966
|
-
this.config.getExpectedWithdrawRelativeBlockLocktime(),
|
|
6967
|
-
this.config.getThreshold()
|
|
6968
|
-
);
|
|
6969
|
-
const finalTokenTransaction = startResponse.finalTokenTransaction;
|
|
6970
|
-
const finalTokenTransactionHash = hashTokenTransactionV0(
|
|
6971
|
-
finalTokenTransaction,
|
|
6972
|
-
false
|
|
6097
|
+
outputsToSpendSigningPublicKeys,
|
|
6098
|
+
outputsToSpendCommitments
|
|
6973
6099
|
);
|
|
6974
|
-
|
|
6100
|
+
await this.signTokenTransaction(
|
|
6975
6101
|
finalTokenTransaction,
|
|
6976
6102
|
finalTokenTransactionHash,
|
|
6977
|
-
|
|
6978
|
-
|
|
6103
|
+
signingOperators
|
|
6104
|
+
);
|
|
6105
|
+
return bytesToHex6(finalTokenTransactionHash);
|
|
6979
6106
|
}
|
|
6980
6107
|
async startTokenTransaction(tokenTransaction, signingOperators, outputsToSpendSigningPublicKeys, outputsToSpendCommitments) {
|
|
6981
6108
|
const sparkClient = await this.connectionManager.createSparkTokenClient(
|
|
@@ -7096,103 +6223,6 @@ var TokenTransactionService = class {
|
|
|
7096
6223
|
threshold: startResponse.keyshareInfo.threshold
|
|
7097
6224
|
};
|
|
7098
6225
|
}
|
|
7099
|
-
async signTokenTransactionV0(finalTokenTransaction, finalTokenTransactionHash, signingOperators) {
|
|
7100
|
-
const soSignatures = await Promise.allSettled(
|
|
7101
|
-
Object.entries(signingOperators).map(
|
|
7102
|
-
async ([identifier, operator], index) => {
|
|
7103
|
-
const internalSparkClient = await this.connectionManager.createSparkClient(operator.address);
|
|
7104
|
-
const identityPublicKey = await this.config.signer.getIdentityPublicKey();
|
|
7105
|
-
const payload = {
|
|
7106
|
-
finalTokenTransactionHash,
|
|
7107
|
-
operatorIdentityPublicKey: hexToBytes6(operator.identityPublicKey)
|
|
7108
|
-
};
|
|
7109
|
-
const payloadHash = await hashOperatorSpecificTokenTransactionSignablePayload(payload);
|
|
7110
|
-
let operatorSpecificSignatures = [];
|
|
7111
|
-
if (finalTokenTransaction.tokenInputs.$case === "mintInput") {
|
|
7112
|
-
const issuerPublicKey = finalTokenTransaction.tokenInputs.mintInput.issuerPublicKey;
|
|
7113
|
-
if (!issuerPublicKey) {
|
|
7114
|
-
throw new ValidationError("Invalid mint input", {
|
|
7115
|
-
field: "issuerPublicKey",
|
|
7116
|
-
value: null,
|
|
7117
|
-
expected: "Non-null issuer public key"
|
|
7118
|
-
});
|
|
7119
|
-
}
|
|
7120
|
-
const ownerSignature = await this.signMessageWithKey(
|
|
7121
|
-
payloadHash,
|
|
7122
|
-
issuerPublicKey
|
|
7123
|
-
);
|
|
7124
|
-
operatorSpecificSignatures.push({
|
|
7125
|
-
ownerSignature: {
|
|
7126
|
-
signature: ownerSignature,
|
|
7127
|
-
inputIndex: 0
|
|
7128
|
-
},
|
|
7129
|
-
payload
|
|
7130
|
-
});
|
|
7131
|
-
}
|
|
7132
|
-
if (finalTokenTransaction.tokenInputs.$case === "transferInput") {
|
|
7133
|
-
const transferInput = finalTokenTransaction.tokenInputs.transferInput;
|
|
7134
|
-
for (let i = 0; i < transferInput.outputsToSpend.length; i++) {
|
|
7135
|
-
let ownerSignature;
|
|
7136
|
-
if (this.config.getTokenSignatures() === "SCHNORR") {
|
|
7137
|
-
ownerSignature = await this.config.signer.signSchnorrWithIdentityKey(
|
|
7138
|
-
payloadHash
|
|
7139
|
-
);
|
|
7140
|
-
} else {
|
|
7141
|
-
ownerSignature = await this.config.signer.signMessageWithIdentityKey(
|
|
7142
|
-
payloadHash
|
|
7143
|
-
);
|
|
7144
|
-
}
|
|
7145
|
-
operatorSpecificSignatures.push({
|
|
7146
|
-
ownerSignature: {
|
|
7147
|
-
signature: ownerSignature,
|
|
7148
|
-
inputIndex: i
|
|
7149
|
-
},
|
|
7150
|
-
payload
|
|
7151
|
-
});
|
|
7152
|
-
}
|
|
7153
|
-
}
|
|
7154
|
-
try {
|
|
7155
|
-
const response = await internalSparkClient.sign_token_transaction(
|
|
7156
|
-
{
|
|
7157
|
-
finalTokenTransaction,
|
|
7158
|
-
operatorSpecificSignatures,
|
|
7159
|
-
identityPublicKey
|
|
7160
|
-
},
|
|
7161
|
-
{
|
|
7162
|
-
retry: true,
|
|
7163
|
-
retryableStatuses: [
|
|
7164
|
-
"UNKNOWN",
|
|
7165
|
-
"UNAVAILABLE",
|
|
7166
|
-
"CANCELLED",
|
|
7167
|
-
"INTERNAL"
|
|
7168
|
-
],
|
|
7169
|
-
retryMaxAttempts: 3
|
|
7170
|
-
}
|
|
7171
|
-
);
|
|
7172
|
-
return {
|
|
7173
|
-
index,
|
|
7174
|
-
identifier,
|
|
7175
|
-
response
|
|
7176
|
-
};
|
|
7177
|
-
} catch (error) {
|
|
7178
|
-
throw new NetworkError(
|
|
7179
|
-
"Failed to sign token transaction",
|
|
7180
|
-
{
|
|
7181
|
-
operation: "sign_token_transaction",
|
|
7182
|
-
errorCount: 1,
|
|
7183
|
-
errors: error instanceof Error ? error.message : String(error)
|
|
7184
|
-
},
|
|
7185
|
-
error
|
|
7186
|
-
);
|
|
7187
|
-
}
|
|
7188
|
-
}
|
|
7189
|
-
)
|
|
7190
|
-
);
|
|
7191
|
-
const successfulSignatures = collectResponses(soSignatures);
|
|
7192
|
-
return {
|
|
7193
|
-
successfulSignatures
|
|
7194
|
-
};
|
|
7195
|
-
}
|
|
7196
6226
|
async signTokenTransaction(finalTokenTransaction, finalTokenTransactionHash, signingOperators) {
|
|
7197
6227
|
const coordinatorClient = await this.connectionManager.createSparkTokenClient(
|
|
7198
6228
|
this.config.getCoordinatorAddress()
|
|
@@ -7264,54 +6294,6 @@ var TokenTransactionService = class {
|
|
|
7264
6294
|
);
|
|
7265
6295
|
}
|
|
7266
6296
|
}
|
|
7267
|
-
if (this.config.getTokenTransactionVersion() === "V0") {
|
|
7268
|
-
return this.fetchOwnedTokenOutputsV0(params);
|
|
7269
|
-
} else {
|
|
7270
|
-
return this.fetchOwnedTokenOutputsV1(params);
|
|
7271
|
-
}
|
|
7272
|
-
}
|
|
7273
|
-
async queryTokenTransactions(params) {
|
|
7274
|
-
if (this.config.getTokenTransactionVersion() === "V0") {
|
|
7275
|
-
return this.queryTokenTransactionsV0(params);
|
|
7276
|
-
} else {
|
|
7277
|
-
return this.queryTokenTransactionsV1(params);
|
|
7278
|
-
}
|
|
7279
|
-
}
|
|
7280
|
-
async fetchOwnedTokenOutputsV0(params) {
|
|
7281
|
-
const {
|
|
7282
|
-
ownerPublicKeys,
|
|
7283
|
-
issuerPublicKeys: tokenPublicKeys = [],
|
|
7284
|
-
tokenIdentifiers = []
|
|
7285
|
-
} = params;
|
|
7286
|
-
const sparkClient = await this.connectionManager.createSparkClient(
|
|
7287
|
-
this.config.getCoordinatorAddress()
|
|
7288
|
-
);
|
|
7289
|
-
try {
|
|
7290
|
-
const result = await sparkClient.query_token_outputs({
|
|
7291
|
-
ownerPublicKeys,
|
|
7292
|
-
tokenPublicKeys,
|
|
7293
|
-
tokenIdentifiers,
|
|
7294
|
-
network: this.config.getNetworkProto()
|
|
7295
|
-
});
|
|
7296
|
-
return result.outputsWithPreviousTransactionData;
|
|
7297
|
-
} catch (error) {
|
|
7298
|
-
throw new NetworkError(
|
|
7299
|
-
"Failed to fetch owned token outputs",
|
|
7300
|
-
{
|
|
7301
|
-
operation: "spark.query_token_outputs",
|
|
7302
|
-
errorCount: 1,
|
|
7303
|
-
errors: error instanceof Error ? error.message : String(error)
|
|
7304
|
-
},
|
|
7305
|
-
error
|
|
7306
|
-
);
|
|
7307
|
-
}
|
|
7308
|
-
}
|
|
7309
|
-
async fetchOwnedTokenOutputsV1(params) {
|
|
7310
|
-
const {
|
|
7311
|
-
ownerPublicKeys,
|
|
7312
|
-
issuerPublicKeys = [],
|
|
7313
|
-
tokenIdentifiers = []
|
|
7314
|
-
} = params;
|
|
7315
6297
|
const tokenClient = await this.connectionManager.createSparkTokenClient(
|
|
7316
6298
|
this.config.getCoordinatorAddress()
|
|
7317
6299
|
);
|
|
@@ -7352,75 +6334,7 @@ var TokenTransactionService = class {
|
|
|
7352
6334
|
);
|
|
7353
6335
|
}
|
|
7354
6336
|
}
|
|
7355
|
-
async
|
|
7356
|
-
const {
|
|
7357
|
-
ownerPublicKeys,
|
|
7358
|
-
issuerPublicKeys,
|
|
7359
|
-
tokenTransactionHashes,
|
|
7360
|
-
tokenIdentifiers,
|
|
7361
|
-
outputIds,
|
|
7362
|
-
pageSize,
|
|
7363
|
-
offset
|
|
7364
|
-
} = params;
|
|
7365
|
-
const sparkClient = await this.connectionManager.createSparkClient(
|
|
7366
|
-
this.config.getCoordinatorAddress()
|
|
7367
|
-
);
|
|
7368
|
-
let queryParams = {
|
|
7369
|
-
tokenPublicKeys: issuerPublicKeys?.map(hexToBytes6),
|
|
7370
|
-
ownerPublicKeys: ownerPublicKeys?.map(hexToBytes6),
|
|
7371
|
-
tokenIdentifiers: tokenIdentifiers?.map((identifier) => {
|
|
7372
|
-
const { tokenIdentifier } = decodeBech32mTokenIdentifier(
|
|
7373
|
-
identifier,
|
|
7374
|
-
this.config.getNetworkType()
|
|
7375
|
-
);
|
|
7376
|
-
return tokenIdentifier;
|
|
7377
|
-
}),
|
|
7378
|
-
tokenTransactionHashes: tokenTransactionHashes?.map(hexToBytes6),
|
|
7379
|
-
outputIds: outputIds || [],
|
|
7380
|
-
limit: pageSize,
|
|
7381
|
-
offset
|
|
7382
|
-
};
|
|
7383
|
-
try {
|
|
7384
|
-
const response = await sparkClient.query_token_transactions(queryParams);
|
|
7385
|
-
return {
|
|
7386
|
-
tokenTransactionsWithStatus: response.tokenTransactionsWithStatus.map(
|
|
7387
|
-
(tx) => {
|
|
7388
|
-
const v1TokenTransaction = {
|
|
7389
|
-
version: 1,
|
|
7390
|
-
network: tx.tokenTransaction.network,
|
|
7391
|
-
tokenInputs: tx.tokenTransaction.tokenInputs,
|
|
7392
|
-
tokenOutputs: tx.tokenTransaction.tokenOutputs,
|
|
7393
|
-
sparkOperatorIdentityPublicKeys: tx.tokenTransaction.sparkOperatorIdentityPublicKeys,
|
|
7394
|
-
expiryTime: void 0,
|
|
7395
|
-
// V0 doesn't have expiry time
|
|
7396
|
-
clientCreatedTimestamp: tx.tokenTransaction?.tokenInputs?.$case === "mintInput" ? new Date(
|
|
7397
|
-
tx.tokenTransaction.tokenInputs.mintInput.issuerProvidedTimestamp * 1e3
|
|
7398
|
-
) : /* @__PURE__ */ new Date(),
|
|
7399
|
-
invoiceAttachments: []
|
|
7400
|
-
};
|
|
7401
|
-
return {
|
|
7402
|
-
tokenTransaction: v1TokenTransaction,
|
|
7403
|
-
status: tx.status,
|
|
7404
|
-
confirmationMetadata: tx.confirmationMetadata,
|
|
7405
|
-
tokenTransactionHash: tx.tokenTransactionHash
|
|
7406
|
-
};
|
|
7407
|
-
}
|
|
7408
|
-
),
|
|
7409
|
-
offset: response.offset
|
|
7410
|
-
};
|
|
7411
|
-
} catch (error) {
|
|
7412
|
-
throw new NetworkError(
|
|
7413
|
-
"Failed to query token transactions",
|
|
7414
|
-
{
|
|
7415
|
-
operation: "spark.query_token_transactions",
|
|
7416
|
-
errorCount: 1,
|
|
7417
|
-
errors: error instanceof Error ? error.message : String(error)
|
|
7418
|
-
},
|
|
7419
|
-
error
|
|
7420
|
-
);
|
|
7421
|
-
}
|
|
7422
|
-
}
|
|
7423
|
-
async queryTokenTransactionsV1(params) {
|
|
6337
|
+
async queryTokenTransactions(params) {
|
|
7424
6338
|
const {
|
|
7425
6339
|
ownerPublicKeys,
|
|
7426
6340
|
issuerPublicKeys,
|
|
@@ -7533,50 +6447,6 @@ var TokenTransactionService = class {
|
|
|
7533
6447
|
});
|
|
7534
6448
|
}
|
|
7535
6449
|
}
|
|
7536
|
-
async finalizeTokenTransaction(finalTokenTransaction, revocationSecrets, threshold) {
|
|
7537
|
-
const signingOperators = this.config.getSigningOperators();
|
|
7538
|
-
const soResponses = await Promise.allSettled(
|
|
7539
|
-
Object.entries(signingOperators).map(async ([identifier, operator]) => {
|
|
7540
|
-
const internalSparkClient = await this.connectionManager.createSparkClient(operator.address);
|
|
7541
|
-
const identityPublicKey = await this.config.signer.getIdentityPublicKey();
|
|
7542
|
-
try {
|
|
7543
|
-
const response = await internalSparkClient.finalize_token_transaction(
|
|
7544
|
-
{
|
|
7545
|
-
finalTokenTransaction,
|
|
7546
|
-
revocationSecrets,
|
|
7547
|
-
identityPublicKey
|
|
7548
|
-
},
|
|
7549
|
-
{
|
|
7550
|
-
retry: true,
|
|
7551
|
-
retryableStatuses: [
|
|
7552
|
-
"UNKNOWN",
|
|
7553
|
-
"UNAVAILABLE",
|
|
7554
|
-
"CANCELLED",
|
|
7555
|
-
"INTERNAL"
|
|
7556
|
-
],
|
|
7557
|
-
retryMaxAttempts: 3
|
|
7558
|
-
}
|
|
7559
|
-
);
|
|
7560
|
-
return {
|
|
7561
|
-
identifier,
|
|
7562
|
-
response
|
|
7563
|
-
};
|
|
7564
|
-
} catch (error) {
|
|
7565
|
-
throw new NetworkError(
|
|
7566
|
-
"Failed to finalize token transaction",
|
|
7567
|
-
{
|
|
7568
|
-
operation: "finalize_token_transaction",
|
|
7569
|
-
errorCount: 1,
|
|
7570
|
-
errors: error instanceof Error ? error.message : String(error)
|
|
7571
|
-
},
|
|
7572
|
-
error
|
|
7573
|
-
);
|
|
7574
|
-
}
|
|
7575
|
-
})
|
|
7576
|
-
);
|
|
7577
|
-
collectResponses(soResponses);
|
|
7578
|
-
return finalTokenTransaction;
|
|
7579
|
-
}
|
|
7580
6450
|
async createSignaturesForOperators(finalTokenTransaction, finalTokenTransactionHash, signingOperators) {
|
|
7581
6451
|
const inputTtxoSignaturesPerOperator = [];
|
|
7582
6452
|
for (const [_, operator] of Object.entries(signingOperators)) {
|
|
@@ -7653,28 +6523,25 @@ var TokenTransactionService = class {
|
|
|
7653
6523
|
return inputTtxoSignaturesPerOperator;
|
|
7654
6524
|
}
|
|
7655
6525
|
};
|
|
7656
|
-
function isTokenTransaction(tokenTransaction) {
|
|
7657
|
-
return "version" in tokenTransaction && "expiryTime" in tokenTransaction;
|
|
7658
|
-
}
|
|
7659
6526
|
|
|
7660
6527
|
// src/utils/adaptor-signature.ts
|
|
7661
6528
|
import { mod } from "@noble/curves/abstract/modular";
|
|
7662
|
-
import { schnorr as schnorr5, secp256k1 as
|
|
6529
|
+
import { schnorr as schnorr5, secp256k1 as secp256k18 } from "@noble/curves/secp256k1";
|
|
7663
6530
|
import { bytesToNumberBE as bytesToNumberBE6, numberToBytesBE as numberToBytesBE3 } from "@noble/curves/utils";
|
|
7664
6531
|
function generateSignatureFromExistingAdaptor(signature, adaptorPrivateKeyBytes) {
|
|
7665
6532
|
const { r, s } = parseSignature(signature);
|
|
7666
6533
|
const sBigInt = bytesToNumberBE6(s);
|
|
7667
6534
|
const tBigInt = bytesToNumberBE6(adaptorPrivateKeyBytes);
|
|
7668
|
-
const newS = mod(sBigInt - tBigInt,
|
|
6535
|
+
const newS = mod(sBigInt - tBigInt, secp256k18.CURVE.n);
|
|
7669
6536
|
const newSignature = new Uint8Array([...r, ...numberToBytesBE3(newS, 32)]);
|
|
7670
6537
|
return newSignature;
|
|
7671
6538
|
}
|
|
7672
6539
|
function generateAdaptorFromSignature(signature) {
|
|
7673
|
-
const adaptorPrivateKey =
|
|
6540
|
+
const adaptorPrivateKey = secp256k18.utils.randomPrivateKey();
|
|
7674
6541
|
const { r, s } = parseSignature(signature);
|
|
7675
6542
|
const sBigInt = bytesToNumberBE6(s);
|
|
7676
6543
|
const tBigInt = bytesToNumberBE6(adaptorPrivateKey);
|
|
7677
|
-
const newS = mod(sBigInt - tBigInt,
|
|
6544
|
+
const newS = mod(sBigInt - tBigInt, secp256k18.CURVE.n);
|
|
7678
6545
|
const newSignature = new Uint8Array([...r, ...numberToBytesBE3(newS, 32)]);
|
|
7679
6546
|
return {
|
|
7680
6547
|
adaptorSignature: newSignature,
|
|
@@ -7694,7 +6561,7 @@ function applyAdaptorToSignature(pubkey, hash, signature, adaptorPrivateKeyBytes
|
|
|
7694
6561
|
const { r, s } = parseSignature(signature);
|
|
7695
6562
|
const sBigInt = bytesToNumberBE6(s);
|
|
7696
6563
|
const adaptorPrivateKey = bytesToNumberBE6(adaptorPrivateKeyBytes);
|
|
7697
|
-
const newS = mod(sBigInt + adaptorPrivateKey,
|
|
6564
|
+
const newS = mod(sBigInt + adaptorPrivateKey, secp256k18.CURVE.n);
|
|
7698
6565
|
const newSig = new Uint8Array([...r, ...numberToBytesBE3(newS, 32)]);
|
|
7699
6566
|
try {
|
|
7700
6567
|
if (schnorr5.verify(newSig, hash, pubkey)) {
|
|
@@ -7703,7 +6570,7 @@ function applyAdaptorToSignature(pubkey, hash, signature, adaptorPrivateKeyBytes
|
|
|
7703
6570
|
} catch (e) {
|
|
7704
6571
|
console.error("[applyAdaptorToSignature] Addition verification failed:", e);
|
|
7705
6572
|
}
|
|
7706
|
-
const altS = mod(sBigInt - adaptorPrivateKey,
|
|
6573
|
+
const altS = mod(sBigInt - adaptorPrivateKey, secp256k18.CURVE.n);
|
|
7707
6574
|
const altSig = new Uint8Array([...r, ...numberToBytesBE3(altS, 32)]);
|
|
7708
6575
|
try {
|
|
7709
6576
|
if (schnorr5.verify(altSig, hash, pubkey)) {
|
|
@@ -7733,18 +6600,18 @@ function schnorrVerifyWithAdaptor(signature, hash, pubKeyBytes, adaptorPubkey, i
|
|
|
7733
6600
|
if (commitmenet.length > 32) {
|
|
7734
6601
|
throw new Error("hash of (r || P || m) too big");
|
|
7735
6602
|
}
|
|
7736
|
-
const e = mod(bytesToNumberBE6(commitmenet),
|
|
7737
|
-
const negE = mod(-e,
|
|
7738
|
-
const sG =
|
|
6603
|
+
const e = mod(bytesToNumberBE6(commitmenet), secp256k18.CURVE.n);
|
|
6604
|
+
const negE = mod(-e, secp256k18.CURVE.n);
|
|
6605
|
+
const sG = secp256k18.Point.BASE.multiplyUnsafe(bytesToNumberBE6(s));
|
|
7739
6606
|
const eP = pubKey.multiplyUnsafe(negE);
|
|
7740
6607
|
const R = sG.add(eP);
|
|
7741
6608
|
if (R.is0()) {
|
|
7742
6609
|
throw new Error("R is zero");
|
|
7743
6610
|
}
|
|
7744
6611
|
R.assertValidity();
|
|
7745
|
-
const adaptorPoint =
|
|
6612
|
+
const adaptorPoint = secp256k18.Point.fromHex(adaptorPubkey);
|
|
7746
6613
|
const newR = R.add(adaptorPoint);
|
|
7747
|
-
if (!inbound && newR.equals(
|
|
6614
|
+
if (!inbound && newR.equals(secp256k18.Point.ZERO)) {
|
|
7748
6615
|
throw new Error("calculated R point is the point at infinity");
|
|
7749
6616
|
}
|
|
7750
6617
|
newR.assertValidity();
|
|
@@ -7772,23 +6639,23 @@ function parseSignature(signature) {
|
|
|
7772
6639
|
}
|
|
7773
6640
|
const r = signature.slice(0, 32);
|
|
7774
6641
|
const s = signature.slice(32, 64);
|
|
7775
|
-
if (bytesToNumberBE6(r) >=
|
|
6642
|
+
if (bytesToNumberBE6(r) >= secp256k18.CURVE.Fp.ORDER) {
|
|
7776
6643
|
throw new ValidationError("Invalid signature: r >= field prime", {
|
|
7777
6644
|
rValue: bytesToNumberBE6(r),
|
|
7778
|
-
fieldPrime:
|
|
6645
|
+
fieldPrime: secp256k18.CURVE.Fp.ORDER
|
|
7779
6646
|
});
|
|
7780
6647
|
}
|
|
7781
|
-
if (bytesToNumberBE6(s) >=
|
|
6648
|
+
if (bytesToNumberBE6(s) >= secp256k18.CURVE.n) {
|
|
7782
6649
|
throw new ValidationError("Invalid signature: s >= group order", {
|
|
7783
6650
|
sValue: bytesToNumberBE6(s),
|
|
7784
|
-
groupOrder:
|
|
6651
|
+
groupOrder: secp256k18.CURVE.n
|
|
7785
6652
|
});
|
|
7786
6653
|
}
|
|
7787
6654
|
return { r, s };
|
|
7788
6655
|
}
|
|
7789
6656
|
|
|
7790
6657
|
// src/tests/utils/test-faucet.ts
|
|
7791
|
-
import { schnorr as schnorr6, secp256k1 as
|
|
6658
|
+
import { schnorr as schnorr6, secp256k1 as secp256k19 } from "@noble/curves/secp256k1";
|
|
7792
6659
|
import { bytesToHex as bytesToHex7, hexToBytes as hexToBytes7 } from "@noble/curves/utils";
|
|
7793
6660
|
import * as btc3 from "@scure/btc-signer";
|
|
7794
6661
|
import { Address as Address2, OutScript as OutScript2, SigHash as SigHash2, Transaction as Transaction4 } from "@scure/btc-signer";
|
|
@@ -7832,7 +6699,7 @@ var BitcoinFaucet = class _BitcoinFaucet {
|
|
|
7832
6699
|
this.username = username;
|
|
7833
6700
|
this.password = password;
|
|
7834
6701
|
this.miningAddress = getP2TRAddressFromPublicKey(
|
|
7835
|
-
|
|
6702
|
+
secp256k19.getPublicKey(STATIC_MINING_KEY),
|
|
7836
6703
|
4 /* LOCAL */
|
|
7837
6704
|
);
|
|
7838
6705
|
}
|
|
@@ -7871,7 +6738,7 @@ var BitcoinFaucet = class _BitcoinFaucet {
|
|
|
7871
6738
|
});
|
|
7872
6739
|
}
|
|
7873
6740
|
async refill() {
|
|
7874
|
-
const minerPubKey =
|
|
6741
|
+
const minerPubKey = secp256k19.getPublicKey(STATIC_MINING_KEY);
|
|
7875
6742
|
const address2 = getP2TRAddressFromPublicKey(minerPubKey, 4 /* LOCAL */);
|
|
7876
6743
|
const scanResult = await this.call("scantxoutset", [
|
|
7877
6744
|
"start",
|
|
@@ -7920,7 +6787,7 @@ var BitcoinFaucet = class _BitcoinFaucet {
|
|
|
7920
6787
|
txid: selectedUtxo.txid,
|
|
7921
6788
|
index: selectedUtxo.vout
|
|
7922
6789
|
});
|
|
7923
|
-
const faucetPubKey =
|
|
6790
|
+
const faucetPubKey = secp256k19.getPublicKey(STATIC_FAUCET_KEY);
|
|
7924
6791
|
const script = getP2TRScriptFromPublicKey(faucetPubKey, 4 /* LOCAL */);
|
|
7925
6792
|
for (let i = 0; i < numCoinsToCreate; i++) {
|
|
7926
6793
|
splitTx.addOutput({
|
|
@@ -7981,7 +6848,7 @@ var BitcoinFaucet = class _BitcoinFaucet {
|
|
|
7981
6848
|
await this.broadcastTx(bytesToHex7(signedTx.extract()));
|
|
7982
6849
|
}
|
|
7983
6850
|
async signFaucetCoin(unsignedTx, fundingTxOut, key) {
|
|
7984
|
-
const pubKey =
|
|
6851
|
+
const pubKey = secp256k19.getPublicKey(key);
|
|
7985
6852
|
const internalKey = pubKey.slice(1);
|
|
7986
6853
|
const script = getP2TRScriptFromPublicKey(pubKey, 4 /* LOCAL */);
|
|
7987
6854
|
unsignedTx.updateInput(0, {
|
|
@@ -8064,8 +6931,8 @@ var BitcoinFaucet = class _BitcoinFaucet {
|
|
|
8064
6931
|
return response;
|
|
8065
6932
|
}
|
|
8066
6933
|
async getNewAddress() {
|
|
8067
|
-
const key =
|
|
8068
|
-
const pubKey =
|
|
6934
|
+
const key = secp256k19.utils.randomPrivateKey();
|
|
6935
|
+
const pubKey = secp256k19.getPublicKey(key);
|
|
8069
6936
|
return getP2TRAddressFromPublicKey(pubKey, 4 /* LOCAL */);
|
|
8070
6937
|
}
|
|
8071
6938
|
async sendToAddress(address2, amount, blocksToGenerate = 1) {
|
|
@@ -8086,8 +6953,8 @@ var BitcoinFaucet = class _BitcoinFaucet {
|
|
|
8086
6953
|
});
|
|
8087
6954
|
const changeAmount = availableAmount - amount;
|
|
8088
6955
|
if (changeAmount > 0) {
|
|
8089
|
-
const changeKey =
|
|
8090
|
-
const changePubKey =
|
|
6956
|
+
const changeKey = secp256k19.utils.randomPrivateKey();
|
|
6957
|
+
const changePubKey = secp256k19.getPublicKey(changeKey);
|
|
8091
6958
|
const changeScript = getP2TRScriptFromPublicKey(
|
|
8092
6959
|
changePubKey,
|
|
8093
6960
|
4 /* LOCAL */
|
|
@@ -8100,8 +6967,8 @@ var BitcoinFaucet = class _BitcoinFaucet {
|
|
|
8100
6967
|
const signedTx = await this.signFaucetCoin(tx, coin.txout, coin.key);
|
|
8101
6968
|
const txHex = bytesToHex7(signedTx.extract());
|
|
8102
6969
|
await this.broadcastTx(txHex);
|
|
8103
|
-
const randomKey =
|
|
8104
|
-
const randomPubKey =
|
|
6970
|
+
const randomKey = secp256k19.utils.randomPrivateKey();
|
|
6971
|
+
const randomPubKey = secp256k19.getPublicKey(randomKey);
|
|
8105
6972
|
const randomAddress = getP2TRAddressFromPublicKey(
|
|
8106
6973
|
randomPubKey,
|
|
8107
6974
|
4 /* LOCAL */
|
|
@@ -8126,19 +6993,18 @@ var SparkWalletEvent = {
|
|
|
8126
6993
|
|
|
8127
6994
|
// src/spark-wallet/spark-wallet.ts
|
|
8128
6995
|
import { isNode as isNode3, isObject as isObject2, mapCurrencyAmount } from "@lightsparkdev/core";
|
|
8129
|
-
import { secp256k1 as
|
|
6996
|
+
import { secp256k1 as secp256k112 } from "@noble/curves/secp256k1";
|
|
8130
6997
|
import {
|
|
8131
6998
|
bytesToHex as bytesToHex10,
|
|
8132
6999
|
bytesToNumberBE as bytesToNumberBE8,
|
|
8133
7000
|
equalBytes as equalBytes6,
|
|
8134
|
-
hexToBytes as hexToBytes11
|
|
8135
|
-
numberToVarBytesBE
|
|
7001
|
+
hexToBytes as hexToBytes11
|
|
8136
7002
|
} from "@noble/curves/utils";
|
|
8137
7003
|
import { validateMnemonic } from "@scure/bip39";
|
|
8138
7004
|
import { wordlist as wordlist2 } from "@scure/bip39/wordlists/english";
|
|
8139
7005
|
import { Address as Address3, OutScript as OutScript3, Transaction as Transaction7 } from "@scure/btc-signer";
|
|
8140
7006
|
import { Mutex } from "async-mutex";
|
|
8141
|
-
import { uuidv7 as uuidv74
|
|
7007
|
+
import { uuidv7 as uuidv74 } from "uuidv7";
|
|
8142
7008
|
|
|
8143
7009
|
// src/graphql/client.ts
|
|
8144
7010
|
import {
|
|
@@ -9261,7 +8127,7 @@ import { Transaction as Transaction6 } from "@scure/btc-signer";
|
|
|
9261
8127
|
import { uuidv7 as uuidv72 } from "uuidv7";
|
|
9262
8128
|
|
|
9263
8129
|
// src/services/transfer.ts
|
|
9264
|
-
import { secp256k1 as
|
|
8130
|
+
import { secp256k1 as secp256k110 } from "@noble/curves/secp256k1";
|
|
9265
8131
|
import {
|
|
9266
8132
|
bytesToHex as bytesToHex9,
|
|
9267
8133
|
equalBytes as equalBytes5,
|
|
@@ -9569,7 +8435,7 @@ var BaseTransferService = class {
|
|
|
9569
8435
|
first: leaf.keyDerivation,
|
|
9570
8436
|
second: leaf.newKeyDerivation,
|
|
9571
8437
|
receiverPublicKey: receiverEciesPubKey.toBytes(),
|
|
9572
|
-
curveOrder:
|
|
8438
|
+
curveOrder: secp256k110.CURVE.n,
|
|
9573
8439
|
threshold: this.config.getThreshold(),
|
|
9574
8440
|
numShares: Object.keys(signingOperators).length
|
|
9575
8441
|
});
|
|
@@ -9579,7 +8445,7 @@ var BaseTransferService = class {
|
|
|
9579
8445
|
if (!share) {
|
|
9580
8446
|
throw new Error(`Share not found for operator ${operator.id}`);
|
|
9581
8447
|
}
|
|
9582
|
-
const pubkeyTweak =
|
|
8448
|
+
const pubkeyTweak = secp256k110.getPublicKey(
|
|
9583
8449
|
numberToBytesBE4(share.share, 32),
|
|
9584
8450
|
true
|
|
9585
8451
|
);
|
|
@@ -9711,7 +8577,7 @@ var TransferService = class extends BaseTransferService {
|
|
|
9711
8577
|
...leaf.secretCipher
|
|
9712
8578
|
]);
|
|
9713
8579
|
const payloadHash = sha25610(payload);
|
|
9714
|
-
if (!
|
|
8580
|
+
if (!secp256k110.verify(
|
|
9715
8581
|
leaf.signature,
|
|
9716
8582
|
payloadHash,
|
|
9717
8583
|
transfer.senderIdentityPublicKey
|
|
@@ -10005,7 +8871,7 @@ var TransferService = class extends BaseTransferService {
|
|
|
10005
8871
|
{
|
|
10006
8872
|
first: leaf.keyDerivation,
|
|
10007
8873
|
second: leaf.newKeyDerivation,
|
|
10008
|
-
curveOrder:
|
|
8874
|
+
curveOrder: secp256k110.CURVE.n,
|
|
10009
8875
|
threshold: this.config.getThreshold(),
|
|
10010
8876
|
numShares: Object.keys(signingOperators).length
|
|
10011
8877
|
}
|
|
@@ -10016,7 +8882,7 @@ var TransferService = class extends BaseTransferService {
|
|
|
10016
8882
|
if (!share) {
|
|
10017
8883
|
throw new Error(`Share not found for operator ${operator.id}`);
|
|
10018
8884
|
}
|
|
10019
|
-
const pubkeyTweak =
|
|
8885
|
+
const pubkeyTweak = secp256k110.getPublicKey(
|
|
10020
8886
|
numberToBytesBE4(share.share, 32)
|
|
10021
8887
|
);
|
|
10022
8888
|
pubkeySharesTweak.set(identifier, pubkeyTweak);
|
|
@@ -10988,7 +9854,7 @@ var CoopExitService = class extends BaseTransferService {
|
|
|
10988
9854
|
};
|
|
10989
9855
|
|
|
10990
9856
|
// src/services/lightning.ts
|
|
10991
|
-
import { secp256k1 as
|
|
9857
|
+
import { secp256k1 as secp256k111 } from "@noble/curves/secp256k1";
|
|
10992
9858
|
import {
|
|
10993
9859
|
bytesToNumberBE as bytesToNumberBE7,
|
|
10994
9860
|
hexToBytes as hexToBytes9,
|
|
@@ -11079,7 +9945,7 @@ var LightningService = class {
|
|
|
11079
9945
|
const crypto = getCrypto();
|
|
11080
9946
|
const randBytes = crypto.getRandomValues(new Uint8Array(32));
|
|
11081
9947
|
const preimage = numberToBytesBE5(
|
|
11082
|
-
bytesToNumberBE7(randBytes) %
|
|
9948
|
+
bytesToNumberBE7(randBytes) % secp256k111.CURVE.n,
|
|
11083
9949
|
32
|
|
11084
9950
|
);
|
|
11085
9951
|
return await this.createLightningInvoiceWithPreImage({
|
|
@@ -11116,7 +9982,7 @@ var LightningService = class {
|
|
|
11116
9982
|
}
|
|
11117
9983
|
const shares = await this.config.signer.splitSecretWithProofs({
|
|
11118
9984
|
secret: preimage,
|
|
11119
|
-
curveOrder:
|
|
9985
|
+
curveOrder: secp256k111.CURVE.n,
|
|
11120
9986
|
threshold: this.config.getThreshold(),
|
|
11121
9987
|
numShares: Object.keys(this.config.getSigningOperators()).length
|
|
11122
9988
|
});
|
|
@@ -12108,47 +10974,7 @@ var SparkWallet = class _SparkWallet extends EventEmitter {
|
|
|
12108
10974
|
senderPublicKey,
|
|
12109
10975
|
expiryTime
|
|
12110
10976
|
}) {
|
|
12111
|
-
|
|
12112
|
-
if (amount && (amount < 0 || amount > MAX_SATS_AMOUNT)) {
|
|
12113
|
-
throw new ValidationError(
|
|
12114
|
-
`Amount must be between 0 and ${MAX_SATS_AMOUNT} sats`,
|
|
12115
|
-
{
|
|
12116
|
-
field: "amount",
|
|
12117
|
-
value: amount,
|
|
12118
|
-
expected: `less than or equal to ${MAX_SATS_AMOUNT}`
|
|
12119
|
-
}
|
|
12120
|
-
);
|
|
12121
|
-
}
|
|
12122
|
-
const protoPayment = {
|
|
12123
|
-
$case: "satsPayment",
|
|
12124
|
-
satsPayment: {
|
|
12125
|
-
amount
|
|
12126
|
-
}
|
|
12127
|
-
};
|
|
12128
|
-
const invoiceFields = {
|
|
12129
|
-
version: 1,
|
|
12130
|
-
id: uuidv7obj().bytes,
|
|
12131
|
-
paymentType: protoPayment,
|
|
12132
|
-
memo,
|
|
12133
|
-
senderPublicKey: senderPublicKey ? hexToBytes11(senderPublicKey) : void 0,
|
|
12134
|
-
expiryTime: expiryTime ?? void 0
|
|
12135
|
-
};
|
|
12136
|
-
validateSparkInvoiceFields(invoiceFields);
|
|
12137
|
-
const identityPublicKey = await this.config.signer.getIdentityPublicKey();
|
|
12138
|
-
const hash = HashSparkInvoice(
|
|
12139
|
-
invoiceFields,
|
|
12140
|
-
identityPublicKey,
|
|
12141
|
-
this.config.getNetworkType()
|
|
12142
|
-
);
|
|
12143
|
-
const signature = await this.config.signer.signSchnorrWithIdentityKey(hash);
|
|
12144
|
-
return encodeSparkAddressWithSignature(
|
|
12145
|
-
{
|
|
12146
|
-
identityPublicKey: bytesToHex10(identityPublicKey),
|
|
12147
|
-
network: this.config.getNetworkType(),
|
|
12148
|
-
sparkInvoiceFields: invoiceFields
|
|
12149
|
-
},
|
|
12150
|
-
signature
|
|
12151
|
-
);
|
|
10977
|
+
throw new NotImplementedError("Invoice functionality is not enabled");
|
|
12152
10978
|
}
|
|
12153
10979
|
/**
|
|
12154
10980
|
* Creates a Spark invoice for a tokens payment on Spark.
|
|
@@ -12168,52 +10994,7 @@ var SparkWallet = class _SparkWallet extends EventEmitter {
|
|
|
12168
10994
|
senderPublicKey,
|
|
12169
10995
|
expiryTime
|
|
12170
10996
|
}) {
|
|
12171
|
-
|
|
12172
|
-
if (amount && (amount < 0 || amount > MAX_UINT128)) {
|
|
12173
|
-
throw new ValidationError(`Amount must be between 0 and ${MAX_UINT128}`, {
|
|
12174
|
-
field: "amount",
|
|
12175
|
-
value: amount,
|
|
12176
|
-
expected: `greater than or equal to 0 and less than or equal to ${MAX_UINT128}`
|
|
12177
|
-
});
|
|
12178
|
-
}
|
|
12179
|
-
let decodedTokenIdentifier = void 0;
|
|
12180
|
-
if (tokenIdentifier) {
|
|
12181
|
-
decodedTokenIdentifier = decodeBech32mTokenIdentifier(
|
|
12182
|
-
tokenIdentifier,
|
|
12183
|
-
this.config.getNetworkType()
|
|
12184
|
-
).tokenIdentifier;
|
|
12185
|
-
}
|
|
12186
|
-
const protoPayment = {
|
|
12187
|
-
$case: "tokensPayment",
|
|
12188
|
-
tokensPayment: {
|
|
12189
|
-
tokenIdentifier: decodedTokenIdentifier ?? void 0,
|
|
12190
|
-
amount: amount ? numberToVarBytesBE(amount) : void 0
|
|
12191
|
-
}
|
|
12192
|
-
};
|
|
12193
|
-
const invoiceFields = {
|
|
12194
|
-
version: 1,
|
|
12195
|
-
id: uuidv7obj().bytes,
|
|
12196
|
-
paymentType: protoPayment,
|
|
12197
|
-
memo: memo ?? void 0,
|
|
12198
|
-
senderPublicKey: senderPublicKey ? hexToBytes11(senderPublicKey) : void 0,
|
|
12199
|
-
expiryTime: expiryTime ?? void 0
|
|
12200
|
-
};
|
|
12201
|
-
validateSparkInvoiceFields(invoiceFields);
|
|
12202
|
-
const identityPublicKey = await this.config.signer.getIdentityPublicKey();
|
|
12203
|
-
const hash = HashSparkInvoice(
|
|
12204
|
-
invoiceFields,
|
|
12205
|
-
identityPublicKey,
|
|
12206
|
-
this.config.getNetworkType()
|
|
12207
|
-
);
|
|
12208
|
-
const signature = await this.config.signer.signSchnorrWithIdentityKey(hash);
|
|
12209
|
-
return encodeSparkAddressWithSignature(
|
|
12210
|
-
{
|
|
12211
|
-
identityPublicKey: bytesToHex10(identityPublicKey),
|
|
12212
|
-
network: this.config.getNetworkType(),
|
|
12213
|
-
sparkInvoiceFields: invoiceFields
|
|
12214
|
-
},
|
|
12215
|
-
signature
|
|
12216
|
-
);
|
|
10997
|
+
throw new NotImplementedError("Invoice functionality is not enabled");
|
|
12217
10998
|
}
|
|
12218
10999
|
/**
|
|
12219
11000
|
* Initializes the wallet using either a mnemonic phrase or a raw seed.
|
|
@@ -12538,7 +11319,7 @@ var SparkWallet = class _SparkWallet extends EventEmitter {
|
|
|
12538
11319
|
}
|
|
12539
11320
|
const sspClient = this.getSspClient();
|
|
12540
11321
|
const cpfpAdaptorPubkey = bytesToHex10(
|
|
12541
|
-
|
|
11322
|
+
secp256k112.getPublicKey(cpfpAdaptorPrivateKey)
|
|
12542
11323
|
);
|
|
12543
11324
|
if (!cpfpAdaptorPubkey) {
|
|
12544
11325
|
throw new Error("Failed to generate CPFP adaptor pubkey");
|
|
@@ -12546,13 +11327,13 @@ var SparkWallet = class _SparkWallet extends EventEmitter {
|
|
|
12546
11327
|
let directAdaptorPubkey;
|
|
12547
11328
|
if (directAdaptorPrivateKey.length > 0) {
|
|
12548
11329
|
directAdaptorPubkey = bytesToHex10(
|
|
12549
|
-
|
|
11330
|
+
secp256k112.getPublicKey(directAdaptorPrivateKey)
|
|
12550
11331
|
);
|
|
12551
11332
|
}
|
|
12552
11333
|
let directFromCpfpAdaptorPubkey;
|
|
12553
11334
|
if (directFromCpfpAdaptorPrivateKey.length > 0) {
|
|
12554
11335
|
directFromCpfpAdaptorPubkey = bytesToHex10(
|
|
12555
|
-
|
|
11336
|
+
secp256k112.getPublicKey(directFromCpfpAdaptorPrivateKey)
|
|
12556
11337
|
);
|
|
12557
11338
|
}
|
|
12558
11339
|
let request = null;
|
|
@@ -14167,10 +12948,30 @@ var SparkWallet = class _SparkWallet extends EventEmitter {
|
|
|
14167
12948
|
if (!invoice2) {
|
|
14168
12949
|
throw new Error("Failed to create lightning invoice");
|
|
14169
12950
|
}
|
|
12951
|
+
const decodedInvoice = decodeInvoice(invoice2.invoice.encodedInvoice);
|
|
12952
|
+
if (invoice2.invoice.paymentHash !== bytesToHex10(paymentHash) || decodedInvoice.paymentHash !== bytesToHex10(paymentHash)) {
|
|
12953
|
+
throw new ValidationError("Payment hash mismatch", {
|
|
12954
|
+
field: "paymentHash",
|
|
12955
|
+
value: invoice2.invoice.paymentHash,
|
|
12956
|
+
expected: bytesToHex10(paymentHash)
|
|
12957
|
+
});
|
|
12958
|
+
}
|
|
12959
|
+
if (decodedInvoice.amountMSats === null && amountSats2 !== 0) {
|
|
12960
|
+
throw new ValidationError("Amount mismatch", {
|
|
12961
|
+
field: "amountMSats",
|
|
12962
|
+
value: "null",
|
|
12963
|
+
expected: amountSats2 * 1e3
|
|
12964
|
+
});
|
|
12965
|
+
}
|
|
12966
|
+
if (decodedInvoice.amountMSats !== null && decodedInvoice.amountMSats !== BigInt(amountSats2 * 1e3)) {
|
|
12967
|
+
throw new ValidationError("Amount mismatch", {
|
|
12968
|
+
field: "amountMSats",
|
|
12969
|
+
value: decodedInvoice.amountMSats,
|
|
12970
|
+
expected: amountSats2 * 1e3
|
|
12971
|
+
});
|
|
12972
|
+
}
|
|
14170
12973
|
if (includeSparkAddress) {
|
|
14171
|
-
const sparkFallbackAddress =
|
|
14172
|
-
invoice2.invoice.encodedInvoice
|
|
14173
|
-
).fallbackAddress;
|
|
12974
|
+
const sparkFallbackAddress = decodedInvoice.fallbackAddress;
|
|
14174
12975
|
if (!sparkFallbackAddress) {
|
|
14175
12976
|
throw new ValidationError(
|
|
14176
12977
|
"No spark fallback address found in lightning invoice",
|
|
@@ -14192,6 +12993,14 @@ var SparkWallet = class _SparkWallet extends EventEmitter {
|
|
|
14192
12993
|
}
|
|
14193
12994
|
);
|
|
14194
12995
|
}
|
|
12996
|
+
} else if (decodedInvoice.fallbackAddress !== void 0) {
|
|
12997
|
+
throw new ValidationError(
|
|
12998
|
+
"Spark fallback address found in lightning invoice but includeSparkAddress is false",
|
|
12999
|
+
{
|
|
13000
|
+
field: "sparkFallbackAddress",
|
|
13001
|
+
value: decodedInvoice.fallbackAddress
|
|
13002
|
+
}
|
|
13003
|
+
);
|
|
14195
13004
|
}
|
|
14196
13005
|
return invoice2;
|
|
14197
13006
|
};
|
|
@@ -14359,91 +13168,7 @@ var SparkWallet = class _SparkWallet extends EventEmitter {
|
|
|
14359
13168
|
});
|
|
14360
13169
|
}
|
|
14361
13170
|
async fulfillSparkInvoice(sparkInvoices) {
|
|
14362
|
-
|
|
14363
|
-
throw new ValidationError("No Spark invoices provided", {
|
|
14364
|
-
field: "sparkInvoices",
|
|
14365
|
-
value: sparkInvoices,
|
|
14366
|
-
expected: "Non-empty array"
|
|
14367
|
-
});
|
|
14368
|
-
}
|
|
14369
|
-
const satsTransactionSuccess = [];
|
|
14370
|
-
const satsTransactionErrors = [];
|
|
14371
|
-
const tokenTransactionSuccess = [];
|
|
14372
|
-
const tokenTransactionErrors = [];
|
|
14373
|
-
const { satsInvoices, tokenInvoices, invalidInvoices } = await this.groupSparkInvoicesByPaymentType(sparkInvoices);
|
|
14374
|
-
if (invalidInvoices.length > 0) {
|
|
14375
|
-
return {
|
|
14376
|
-
satsTransactionSuccess,
|
|
14377
|
-
satsTransactionErrors,
|
|
14378
|
-
tokenTransactionSuccess,
|
|
14379
|
-
tokenTransactionErrors,
|
|
14380
|
-
invalidInvoices
|
|
14381
|
-
};
|
|
14382
|
-
}
|
|
14383
|
-
if (tokenInvoices.size > 0) {
|
|
14384
|
-
await this.syncTokenOutputs();
|
|
14385
|
-
const tokenTransferTasks = [];
|
|
14386
|
-
for (const [identifierHex, decodedInvoices] of tokenInvoices.entries()) {
|
|
14387
|
-
const tokenIdentifier = hexToBytes11(identifierHex);
|
|
14388
|
-
const tokenIdB32 = encodeBech32mTokenIdentifier({
|
|
14389
|
-
tokenIdentifier,
|
|
14390
|
-
network: this.config.getNetworkType()
|
|
14391
|
-
});
|
|
14392
|
-
const receiverOutputs = decodedInvoices.map((d) => ({
|
|
14393
|
-
tokenIdentifier: tokenIdB32,
|
|
14394
|
-
tokenAmount: d.amount,
|
|
14395
|
-
receiverSparkAddress: d.invoice
|
|
14396
|
-
}));
|
|
14397
|
-
tokenTransferTasks.push(
|
|
14398
|
-
this.tokenTransactionService.tokenTransfer({ tokenOutputs: this.tokenOutputs, receiverOutputs }).then((txid) => ({
|
|
14399
|
-
ok: true,
|
|
14400
|
-
tokenIdentifier: tokenIdB32,
|
|
14401
|
-
txid
|
|
14402
|
-
})).catch((e) => ({
|
|
14403
|
-
ok: false,
|
|
14404
|
-
tokenIdentifier: tokenIdB32,
|
|
14405
|
-
error: e instanceof Error ? e : new Error(String(e))
|
|
14406
|
-
}))
|
|
14407
|
-
);
|
|
14408
|
-
}
|
|
14409
|
-
const results = await Promise.all(tokenTransferTasks);
|
|
14410
|
-
for (const r of results) {
|
|
14411
|
-
if (r.ok) {
|
|
14412
|
-
tokenTransactionSuccess.push({
|
|
14413
|
-
tokenIdentifier: r.tokenIdentifier,
|
|
14414
|
-
txid: r.txid
|
|
14415
|
-
});
|
|
14416
|
-
} else {
|
|
14417
|
-
tokenTransactionErrors.push({
|
|
14418
|
-
tokenIdentifier: r.tokenIdentifier,
|
|
14419
|
-
error: r.error
|
|
14420
|
-
});
|
|
14421
|
-
}
|
|
14422
|
-
}
|
|
14423
|
-
}
|
|
14424
|
-
if (satsInvoices.length > 0) {
|
|
14425
|
-
const transfers = await this.transferWithInvoice(satsInvoices);
|
|
14426
|
-
for (const transfer of transfers) {
|
|
14427
|
-
if (transfer.ok) {
|
|
14428
|
-
satsTransactionSuccess.push({
|
|
14429
|
-
invoice: transfer.param.sparkInvoice ?? "",
|
|
14430
|
-
transferResponse: transfer.transfer
|
|
14431
|
-
});
|
|
14432
|
-
} else {
|
|
14433
|
-
satsTransactionErrors.push({
|
|
14434
|
-
invoice: transfer.param.sparkInvoice ?? "",
|
|
14435
|
-
error: transfer.error
|
|
14436
|
-
});
|
|
14437
|
-
}
|
|
14438
|
-
}
|
|
14439
|
-
}
|
|
14440
|
-
return {
|
|
14441
|
-
satsTransactionSuccess,
|
|
14442
|
-
satsTransactionErrors,
|
|
14443
|
-
tokenTransactionSuccess,
|
|
14444
|
-
tokenTransactionErrors,
|
|
14445
|
-
invalidInvoices
|
|
14446
|
-
};
|
|
13171
|
+
throw new NotImplementedError("Invoice functionality is not enabled");
|
|
14447
13172
|
}
|
|
14448
13173
|
async groupSparkInvoicesByPaymentType(sparkInvoices) {
|
|
14449
13174
|
const satsInvoices = [];
|
|
@@ -14580,6 +13305,9 @@ var SparkWallet = class _SparkWallet extends EventEmitter {
|
|
|
14580
13305
|
});
|
|
14581
13306
|
return { satsInvoices, tokenInvoices, invalidInvoices };
|
|
14582
13307
|
}
|
|
13308
|
+
async querySparkInvoices(invoices) {
|
|
13309
|
+
throw new NotImplementedError("Invoice functionality is not enabled");
|
|
13310
|
+
}
|
|
14583
13311
|
/**
|
|
14584
13312
|
* Gets fee estimate for sending Lightning payments.
|
|
14585
13313
|
*
|
|
@@ -15641,6 +14369,27 @@ async function isTxBroadcast(txid, baseUrl, network) {
|
|
|
15641
14369
|
return true;
|
|
15642
14370
|
}
|
|
15643
14371
|
|
|
14372
|
+
// src/utils/response-validation.ts
|
|
14373
|
+
function collectResponses(responses) {
|
|
14374
|
+
const successfulResponses = responses.filter(
|
|
14375
|
+
(result) => result.status === "fulfilled"
|
|
14376
|
+
).map((result) => result.value);
|
|
14377
|
+
const failedResponses = responses.filter(
|
|
14378
|
+
(result) => result.status === "rejected"
|
|
14379
|
+
);
|
|
14380
|
+
if (failedResponses.length > 0) {
|
|
14381
|
+
const errors = failedResponses.map((result) => result.reason).join("\n");
|
|
14382
|
+
throw new NetworkError(
|
|
14383
|
+
`${failedResponses.length} out of ${responses.length} requests failed, please try again`,
|
|
14384
|
+
{
|
|
14385
|
+
errorCount: failedResponses.length,
|
|
14386
|
+
errors
|
|
14387
|
+
}
|
|
14388
|
+
);
|
|
14389
|
+
}
|
|
14390
|
+
return successfulResponses;
|
|
14391
|
+
}
|
|
14392
|
+
|
|
15644
14393
|
// src/utils/unilateral-exit.ts
|
|
15645
14394
|
import { bytesToHex as bytesToHex11, hexToBytes as hexToBytes12 } from "@noble/curves/utils";
|
|
15646
14395
|
import { ripemd160 } from "@noble/hashes/legacy";
|
|
@@ -16157,6 +14906,7 @@ __export(utils_exports, {
|
|
|
16157
14906
|
addPublicKeys: () => addPublicKeys,
|
|
16158
14907
|
applyAdaptorToSignature: () => applyAdaptorToSignature,
|
|
16159
14908
|
applyAdditiveTweakToPublicKey: () => applyAdditiveTweakToPublicKey,
|
|
14909
|
+
assertBech32: () => assertBech32,
|
|
16160
14910
|
bech32mDecode: () => bech32mDecode,
|
|
16161
14911
|
bigIntToPrivateKey: () => bigIntToPrivateKey,
|
|
16162
14912
|
checkIfSelectedOutputsAreAvailable: () => checkIfSelectedOutputsAreAvailable,
|
|
@@ -16219,6 +14969,7 @@ __export(utils_exports, {
|
|
|
16219
14969
|
getTxId: () => getTxId,
|
|
16220
14970
|
getTxIdNoReverse: () => getTxIdNoReverse,
|
|
16221
14971
|
isEphemeralAnchorOutput: () => isEphemeralAnchorOutput,
|
|
14972
|
+
isLegacySparkAddress: () => isLegacySparkAddress,
|
|
16222
14973
|
isSafeForNumber: () => isSafeForNumber,
|
|
16223
14974
|
isTxBroadcast: () => isTxBroadcast,
|
|
16224
14975
|
isValidPublicKey: () => isValidPublicKey,
|
|
@@ -16331,15 +15082,16 @@ export {
|
|
|
16331
15082
|
encodeSparkAddress,
|
|
16332
15083
|
encodeSparkAddressWithSignature,
|
|
16333
15084
|
decodeSparkAddress,
|
|
15085
|
+
getNetworkFromSparkAddress,
|
|
15086
|
+
isLegacySparkAddress,
|
|
16334
15087
|
isValidSparkAddress,
|
|
16335
15088
|
isValidPublicKey,
|
|
16336
15089
|
validateSparkInvoiceFields,
|
|
16337
15090
|
validateSparkInvoiceSignature,
|
|
16338
|
-
getNetworkFromSparkAddress,
|
|
16339
15091
|
toProtoTimestamp,
|
|
15092
|
+
assertBech32,
|
|
16340
15093
|
bech32mDecode,
|
|
16341
15094
|
isSafeForNumber,
|
|
16342
|
-
collectResponses,
|
|
16343
15095
|
encodeBech32mTokenIdentifier,
|
|
16344
15096
|
decodeBech32mTokenIdentifier,
|
|
16345
15097
|
getNetworkFromBech32mTokenIdentifier,
|
|
@@ -16356,6 +15108,7 @@ export {
|
|
|
16356
15108
|
SparkWallet,
|
|
16357
15109
|
getLatestDepositTxId,
|
|
16358
15110
|
isTxBroadcast,
|
|
15111
|
+
collectResponses,
|
|
16359
15112
|
isEphemeralAnchorOutput,
|
|
16360
15113
|
constructUnilateralExitTxs,
|
|
16361
15114
|
constructUnilateralExitFeeBumpPackages,
|