@buildonspark/spark-sdk 0.1.44 → 0.1.46
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 +17 -0
- package/dist/{RequestLightningSendInput-BxbCtwpV.d.cts → RequestLightningSendInput-2cSh_In4.d.cts} +1 -1
- package/dist/{RequestLightningSendInput-RGel43ks.d.ts → RequestLightningSendInput-CN6BNg_g.d.ts} +1 -1
- package/dist/address/index.cjs +2 -2
- package/dist/address/index.d.cts +2 -2
- package/dist/address/index.d.ts +2 -2
- package/dist/address/index.js +2 -2
- package/dist/{chunk-EKFD62HN.js → chunk-4EMV7HHW.js} +2 -1
- package/dist/{chunk-4Q2ZDYYU.js → chunk-BGGEVUJK.js} +1157 -208
- package/dist/{chunk-CIZNCBKE.js → chunk-C2S227QR.js} +648 -45
- package/dist/{chunk-WPTRVD2V.js → chunk-DXR2PXJU.js} +15 -15
- package/dist/{chunk-NBCNYDWJ.js → chunk-HHNQ3ZHC.js} +2 -2
- package/dist/{chunk-DAXGVPVM.js → chunk-HSCLBJEL.js} +2 -2
- package/dist/{chunk-6AFUC5M2.js → chunk-HWJWKEIU.js} +8 -2
- package/dist/{chunk-A2ZLMH6I.js → chunk-JB64OQES.js} +259 -327
- package/dist/{chunk-KEKGSH7B.js → chunk-KMUMFYFX.js} +3 -3
- package/dist/chunk-LHRD2WT6.js +2374 -0
- package/dist/{chunk-HTMXTJRK.js → chunk-N5VZVCGJ.js} +4 -4
- package/dist/{chunk-SQKXGAIR.js → chunk-NTFKFRQ2.js} +1 -1
- package/dist/{chunk-K4BJARWM.js → chunk-OBFKIEMP.js} +1 -1
- package/dist/{chunk-UBT6EDVJ.js → chunk-OFCJFZ4I.js} +1 -1
- package/dist/{chunk-XX4RRWOX.js → chunk-UXDODSDT.js} +8 -10
- package/dist/graphql/objects/index.d.cts +5 -4
- package/dist/graphql/objects/index.d.ts +5 -4
- package/dist/index-CKL5DodV.d.cts +214 -0
- package/dist/index-COm59SPw.d.ts +214 -0
- package/dist/index.cjs +4026 -1315
- package/dist/index.d.cts +764 -19
- package/dist/index.d.ts +764 -19
- package/dist/index.js +23 -27
- package/dist/index.node.cjs +4026 -1319
- package/dist/index.node.d.cts +10 -8
- package/dist/index.node.d.ts +10 -8
- package/dist/index.node.js +23 -31
- package/dist/native/index.cjs +4027 -1316
- package/dist/native/index.d.cts +281 -85
- package/dist/native/index.d.ts +281 -85
- package/dist/native/index.js +4018 -1307
- package/dist/{network-CfxLnaot.d.cts → network-Css46DAz.d.cts} +1 -1
- package/dist/{network-CroCOQ0B.d.ts → network-hynb7iTZ.d.ts} +1 -1
- package/dist/proto/lrc20.cjs +222 -19
- package/dist/proto/lrc20.d.cts +1 -1
- package/dist/proto/lrc20.d.ts +1 -1
- package/dist/proto/lrc20.js +2 -2
- package/dist/proto/spark.cjs +1154 -205
- package/dist/proto/spark.d.cts +1 -1
- package/dist/proto/spark.d.ts +1 -1
- package/dist/proto/spark.js +3 -1
- package/dist/proto/spark_token.cjs +1377 -58
- package/dist/proto/spark_token.d.cts +153 -15
- package/dist/proto/spark_token.d.ts +153 -15
- package/dist/proto/spark_token.js +40 -4
- package/dist/{sdk-types-CTbTdDbE.d.ts → sdk-types-CKBsylfW.d.ts} +1 -1
- package/dist/{sdk-types-BeCBoozO.d.cts → sdk-types-Ct8xmN7l.d.cts} +1 -1
- package/dist/services/config.cjs +2 -2
- package/dist/services/config.d.cts +5 -4
- package/dist/services/config.d.ts +5 -4
- package/dist/services/config.js +6 -6
- package/dist/services/connection.cjs +2438 -262
- package/dist/services/connection.d.cts +5 -4
- package/dist/services/connection.d.ts +5 -4
- package/dist/services/connection.js +4 -4
- package/dist/services/index.cjs +5937 -3154
- package/dist/services/index.d.cts +7 -6
- package/dist/services/index.d.ts +7 -6
- package/dist/services/index.js +17 -15
- package/dist/services/lrc-connection.cjs +223 -20
- package/dist/services/lrc-connection.d.cts +5 -4
- package/dist/services/lrc-connection.d.ts +5 -4
- package/dist/services/lrc-connection.js +4 -4
- package/dist/services/token-transactions.cjs +840 -236
- package/dist/services/token-transactions.d.cts +25 -7
- package/dist/services/token-transactions.d.ts +25 -7
- package/dist/services/token-transactions.js +5 -4
- package/dist/services/wallet-config.cjs +3 -1
- package/dist/services/wallet-config.d.cts +7 -5
- package/dist/services/wallet-config.d.ts +7 -5
- package/dist/services/wallet-config.js +3 -1
- package/dist/signer/signer.cjs +1 -1
- package/dist/signer/signer.d.cts +3 -2
- package/dist/signer/signer.d.ts +3 -2
- package/dist/signer/signer.js +2 -2
- package/dist/{signer-D7vfYik9.d.ts → signer-BP6F__oR.d.cts} +2 -6
- package/dist/{signer-DaY8c60s.d.cts → signer-BVZJXcq7.d.ts} +2 -6
- package/dist/{spark-C4ZrsgjC.d.cts → spark-DbzGfse6.d.cts} +93 -15
- package/dist/{spark-C4ZrsgjC.d.ts → spark-DbzGfse6.d.ts} +93 -15
- package/dist/spark_bindings/native/index.cjs +183 -0
- package/dist/spark_bindings/native/index.d.cts +14 -0
- package/dist/spark_bindings/native/index.d.ts +14 -0
- package/dist/spark_bindings/native/index.js +141 -0
- package/dist/spark_bindings/wasm/index.cjs +1093 -0
- package/dist/spark_bindings/wasm/index.d.cts +47 -0
- package/dist/spark_bindings/wasm/index.d.ts +47 -0
- package/dist/{chunk-K4C4W5FC.js → spark_bindings/wasm/index.js} +7 -6
- package/dist/types/index.cjs +1156 -208
- package/dist/types/index.d.cts +5 -4
- package/dist/types/index.d.ts +5 -4
- package/dist/types/index.js +2 -2
- package/dist/types-C-Rp0Oo7.d.cts +46 -0
- package/dist/types-C-Rp0Oo7.d.ts +46 -0
- package/dist/utils/index.cjs +65 -13
- package/dist/utils/index.d.cts +14 -134
- package/dist/utils/index.d.ts +14 -134
- package/dist/utils/index.js +13 -13
- package/package.json +22 -2
- package/src/index.node.ts +0 -1
- package/src/index.ts +0 -1
- package/src/native/index.ts +1 -2
- package/src/proto/common.ts +5 -5
- package/src/proto/google/protobuf/descriptor.ts +34 -34
- package/src/proto/google/protobuf/duration.ts +2 -2
- package/src/proto/google/protobuf/empty.ts +2 -2
- package/src/proto/google/protobuf/timestamp.ts +2 -2
- package/src/proto/mock.ts +4 -4
- package/src/proto/spark.ts +1452 -185
- package/src/proto/spark_authn.ts +7 -7
- package/src/proto/spark_token.ts +1668 -105
- package/src/proto/validate/validate.ts +24 -24
- package/src/services/bolt11-spark.ts +62 -187
- package/src/services/coop-exit.ts +3 -0
- package/src/services/lrc20.ts +1 -1
- package/src/services/token-transactions.ts +197 -9
- package/src/services/transfer.ts +22 -0
- package/src/services/tree-creation.ts +13 -0
- package/src/services/wallet-config.ts +2 -2
- package/src/spark-wallet/spark-wallet.node.ts +0 -4
- package/src/spark-wallet/spark-wallet.ts +76 -108
- package/src/spark-wallet/types.ts +39 -3
- package/src/tests/bolt11-spark.test.ts +7 -15
- package/src/tests/integration/ssp/coop-exit.test.ts +7 -7
- package/src/tests/integration/swap.test.ts +453 -433
- package/src/tests/integration/transfer.test.ts +261 -248
- package/src/tests/token-identifier.test.ts +54 -0
- package/src/tests/tokens.test.ts +218 -23
- package/src/utils/token-hashing.ts +320 -44
- package/src/utils/token-identifier.ts +88 -0
- package/src/utils/token-transaction-validation.ts +350 -5
- package/src/utils/token-transactions.ts +12 -8
- package/src/utils/transaction.ts +0 -6
- package/dist/chunk-B3AMIGJG.js +0 -1073
- package/dist/index-CZmDdSts.d.cts +0 -829
- package/dist/index-ClIRO_3y.d.ts +0 -829
- package/dist/wasm-7OWFHDMS.js +0 -21
|
@@ -3,8 +3,12 @@ import { ValidationError } from "../errors/types.js";
|
|
|
3
3
|
import {
|
|
4
4
|
OperatorSpecificTokenTransactionSignablePayload,
|
|
5
5
|
TokenTransaction as TokenTransactionV0,
|
|
6
|
+
TokenMintInput as TokenMintInputV0,
|
|
6
7
|
} from "../proto/spark.js";
|
|
7
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
TokenTransaction,
|
|
10
|
+
TokenTransactionType,
|
|
11
|
+
} from "../proto/spark_token.js";
|
|
8
12
|
|
|
9
13
|
export function hashTokenTransaction(
|
|
10
14
|
tokenTransaction: TokenTransaction,
|
|
@@ -24,7 +28,7 @@ export function hashTokenTransaction(
|
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
export function hashTokenTransactionV0(
|
|
27
|
-
tokenTransaction: TokenTransactionV0,
|
|
31
|
+
tokenTransaction: TokenTransactionV0 | TokenTransaction,
|
|
28
32
|
partialHash: boolean = false,
|
|
29
33
|
): Uint8Array {
|
|
30
34
|
if (!tokenTransaction) {
|
|
@@ -111,15 +115,30 @@ export function hashTokenTransactionV0(
|
|
|
111
115
|
}
|
|
112
116
|
hashObj.update(issuerPubKey);
|
|
113
117
|
|
|
114
|
-
|
|
115
|
-
|
|
118
|
+
// Handle both TokenTransactionV0 (with issuerProvidedTimestamp) and TokenTransaction (with clientCreatedTimestamp)
|
|
119
|
+
let timestampValue = 0;
|
|
120
|
+
const mintInput = tokenTransaction.tokenInputs.mintInput!;
|
|
121
|
+
|
|
122
|
+
// Check if this is a TokenTransactionV0 (has issuerProvidedTimestamp)
|
|
123
|
+
if ("issuerProvidedTimestamp" in mintInput) {
|
|
124
|
+
const v0MintInput = mintInput as TokenMintInputV0;
|
|
125
|
+
if (v0MintInput.issuerProvidedTimestamp != 0) {
|
|
126
|
+
timestampValue = v0MintInput.issuerProvidedTimestamp;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// Check if this is a TokenTransaction (has clientCreatedTimestamp)
|
|
130
|
+
else if (
|
|
131
|
+
"clientCreatedTimestamp" in tokenTransaction &&
|
|
132
|
+
tokenTransaction.clientCreatedTimestamp
|
|
116
133
|
) {
|
|
134
|
+
timestampValue = tokenTransaction.clientCreatedTimestamp.getTime();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (timestampValue != 0) {
|
|
117
138
|
const timestampBytes = new Uint8Array(8);
|
|
118
139
|
new DataView(timestampBytes.buffer).setBigUint64(
|
|
119
140
|
0,
|
|
120
|
-
BigInt(
|
|
121
|
-
tokenTransaction.tokenInputs.mintInput!.issuerProvidedTimestamp,
|
|
122
|
-
),
|
|
141
|
+
BigInt(timestampValue),
|
|
123
142
|
true, // true for little-endian to match Go implementation
|
|
124
143
|
);
|
|
125
144
|
hashObj.update(timestampBytes);
|
|
@@ -128,6 +147,108 @@ export function hashTokenTransactionV0(
|
|
|
128
147
|
}
|
|
129
148
|
}
|
|
130
149
|
|
|
150
|
+
// Hash create input if a create
|
|
151
|
+
if (tokenTransaction.tokenInputs?.$case === "createInput") {
|
|
152
|
+
const issuerPubKeyHashObj = sha256.create();
|
|
153
|
+
const createInput = tokenTransaction.tokenInputs.createInput!;
|
|
154
|
+
|
|
155
|
+
// Hash issuer public key
|
|
156
|
+
if (
|
|
157
|
+
!createInput.issuerPublicKey ||
|
|
158
|
+
createInput.issuerPublicKey.length === 0
|
|
159
|
+
) {
|
|
160
|
+
throw new ValidationError("issuer public key cannot be nil or empty", {
|
|
161
|
+
field: "tokenInputs.createInput.issuerPublicKey",
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
issuerPubKeyHashObj.update(createInput.issuerPublicKey);
|
|
165
|
+
allHashes.push(issuerPubKeyHashObj.digest());
|
|
166
|
+
|
|
167
|
+
// Hash token name (fixed 20 bytes)
|
|
168
|
+
const tokenNameHashObj = sha256.create();
|
|
169
|
+
if (!createInput.tokenName || createInput.tokenName.length === 0) {
|
|
170
|
+
throw new ValidationError("token name cannot be empty", {
|
|
171
|
+
field: "tokenInputs.createInput.tokenName",
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
if (createInput.tokenName.length > 20) {
|
|
175
|
+
throw new ValidationError("token name cannot be longer than 20 bytes", {
|
|
176
|
+
field: "tokenInputs.createInput.tokenName",
|
|
177
|
+
value: createInput.tokenName,
|
|
178
|
+
expectedLength: 20,
|
|
179
|
+
actualLength: createInput.tokenName.length,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
const tokenNameBytes = new Uint8Array(20);
|
|
183
|
+
const tokenNameEncoder = new TextEncoder();
|
|
184
|
+
tokenNameBytes.set(tokenNameEncoder.encode(createInput.tokenName));
|
|
185
|
+
tokenNameHashObj.update(tokenNameBytes);
|
|
186
|
+
allHashes.push(tokenNameHashObj.digest());
|
|
187
|
+
|
|
188
|
+
// Hash token ticker (fixed 6 bytes)
|
|
189
|
+
const tokenTickerHashObj = sha256.create();
|
|
190
|
+
if (!createInput.tokenTicker || createInput.tokenTicker.length === 0) {
|
|
191
|
+
throw new ValidationError("token ticker cannot be empty", {
|
|
192
|
+
field: "tokenInputs.createInput.tokenTicker",
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
if (createInput.tokenTicker.length > 6) {
|
|
196
|
+
throw new ValidationError("token ticker cannot be longer than 6 bytes", {
|
|
197
|
+
field: "tokenInputs.createInput.tokenTicker",
|
|
198
|
+
value: createInput.tokenTicker,
|
|
199
|
+
expectedLength: 6,
|
|
200
|
+
actualLength: createInput.tokenTicker.length,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
const tokenTickerBytes = new Uint8Array(6);
|
|
204
|
+
const tokenTickerEncoder = new TextEncoder();
|
|
205
|
+
tokenTickerBytes.set(tokenTickerEncoder.encode(createInput.tokenTicker));
|
|
206
|
+
tokenTickerHashObj.update(tokenTickerBytes);
|
|
207
|
+
allHashes.push(tokenTickerHashObj.digest());
|
|
208
|
+
|
|
209
|
+
// Hash decimals
|
|
210
|
+
const decimalsHashObj = sha256.create();
|
|
211
|
+
const decimalsBytes = new Uint8Array(4);
|
|
212
|
+
new DataView(decimalsBytes.buffer).setUint32(
|
|
213
|
+
0,
|
|
214
|
+
createInput.decimals,
|
|
215
|
+
false,
|
|
216
|
+
);
|
|
217
|
+
decimalsHashObj.update(decimalsBytes);
|
|
218
|
+
allHashes.push(decimalsHashObj.digest());
|
|
219
|
+
|
|
220
|
+
// Hash max supply (fixed 16 bytes)
|
|
221
|
+
const maxSupplyHashObj = sha256.create();
|
|
222
|
+
if (!createInput.maxSupply) {
|
|
223
|
+
throw new ValidationError("max supply cannot be nil", {
|
|
224
|
+
field: "tokenInputs.createInput.maxSupply",
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
if (createInput.maxSupply.length !== 16) {
|
|
228
|
+
throw new ValidationError("max supply must be exactly 16 bytes", {
|
|
229
|
+
field: "tokenInputs.createInput.maxSupply",
|
|
230
|
+
value: createInput.maxSupply,
|
|
231
|
+
expectedLength: 16,
|
|
232
|
+
actualLength: createInput.maxSupply.length,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
maxSupplyHashObj.update(createInput.maxSupply);
|
|
236
|
+
allHashes.push(maxSupplyHashObj.digest());
|
|
237
|
+
|
|
238
|
+
// Hash is freezable
|
|
239
|
+
const isFreezableHashObj = sha256.create();
|
|
240
|
+
const isFreezableByte = new Uint8Array([createInput.isFreezable ? 1 : 0]);
|
|
241
|
+
isFreezableHashObj.update(isFreezableByte);
|
|
242
|
+
allHashes.push(isFreezableHashObj.digest());
|
|
243
|
+
|
|
244
|
+
// Hash creation entity public key (only for final hash)
|
|
245
|
+
const creationEntityHashObj = sha256.create();
|
|
246
|
+
if (!partialHash && createInput.creationEntityPublicKey) {
|
|
247
|
+
creationEntityHashObj.update(createInput.creationEntityPublicKey);
|
|
248
|
+
}
|
|
249
|
+
allHashes.push(creationEntityHashObj.digest());
|
|
250
|
+
}
|
|
251
|
+
|
|
131
252
|
// Hash token outputs
|
|
132
253
|
if (!tokenTransaction.tokenOutputs) {
|
|
133
254
|
throw new ValidationError("token outputs cannot be null", {
|
|
@@ -135,7 +256,12 @@ export function hashTokenTransactionV0(
|
|
|
135
256
|
});
|
|
136
257
|
}
|
|
137
258
|
|
|
138
|
-
if (
|
|
259
|
+
if (
|
|
260
|
+
tokenTransaction.tokenOutputs.length === 0 &&
|
|
261
|
+
tokenTransaction.tokenInputs?.$case !== "createInput"
|
|
262
|
+
) {
|
|
263
|
+
// Mint and transfer transactions must have at least one output, but create transactions
|
|
264
|
+
// are allowed to have none (they define metadata only).
|
|
139
265
|
throw new ValidationError("token outputs cannot be empty", {
|
|
140
266
|
field: "tokenOutputs",
|
|
141
267
|
});
|
|
@@ -337,7 +463,31 @@ export function hashTokenTransactionV1(
|
|
|
337
463
|
versionHashObj.update(versionBytes);
|
|
338
464
|
allHashes.push(versionHashObj.digest());
|
|
339
465
|
|
|
340
|
-
// Hash
|
|
466
|
+
// Hash transaction type
|
|
467
|
+
const typeHashObj = sha256.create();
|
|
468
|
+
const typeBytes = new Uint8Array(4);
|
|
469
|
+
let transactionType = 0;
|
|
470
|
+
|
|
471
|
+
if (tokenTransaction.tokenInputs?.$case === "mintInput") {
|
|
472
|
+
transactionType = TokenTransactionType.TOKEN_TRANSACTION_TYPE_MINT;
|
|
473
|
+
} else if (tokenTransaction.tokenInputs?.$case === "transferInput") {
|
|
474
|
+
transactionType = TokenTransactionType.TOKEN_TRANSACTION_TYPE_TRANSFER;
|
|
475
|
+
} else if (tokenTransaction.tokenInputs?.$case === "createInput") {
|
|
476
|
+
transactionType = TokenTransactionType.TOKEN_TRANSACTION_TYPE_CREATE;
|
|
477
|
+
} else {
|
|
478
|
+
throw new ValidationError(
|
|
479
|
+
"token transaction must have exactly one input type",
|
|
480
|
+
{
|
|
481
|
+
field: "tokenInputs",
|
|
482
|
+
},
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
new DataView(typeBytes.buffer).setUint32(0, transactionType, false);
|
|
487
|
+
typeHashObj.update(typeBytes);
|
|
488
|
+
allHashes.push(typeHashObj.digest());
|
|
489
|
+
|
|
490
|
+
// Hash token inputs based on type
|
|
341
491
|
if (tokenTransaction.tokenInputs?.$case === "transferInput") {
|
|
342
492
|
if (!tokenTransaction.tokenInputs.transferInput.outputsToSpend) {
|
|
343
493
|
throw new ValidationError("outputs to spend cannot be null", {
|
|
@@ -353,6 +503,17 @@ export function hashTokenTransactionV1(
|
|
|
353
503
|
});
|
|
354
504
|
}
|
|
355
505
|
|
|
506
|
+
// Hash outputs to spend length
|
|
507
|
+
const outputsLenHashObj = sha256.create();
|
|
508
|
+
const outputsLenBytes = new Uint8Array(4);
|
|
509
|
+
new DataView(outputsLenBytes.buffer).setUint32(
|
|
510
|
+
0,
|
|
511
|
+
tokenTransaction.tokenInputs.transferInput.outputsToSpend.length,
|
|
512
|
+
false,
|
|
513
|
+
);
|
|
514
|
+
outputsLenHashObj.update(outputsLenBytes);
|
|
515
|
+
allHashes.push(outputsLenHashObj.digest());
|
|
516
|
+
|
|
356
517
|
// Hash outputs to spend
|
|
357
518
|
for (const [
|
|
358
519
|
i,
|
|
@@ -394,10 +555,7 @@ export function hashTokenTransactionV1(
|
|
|
394
555
|
|
|
395
556
|
allHashes.push(hashObj.digest());
|
|
396
557
|
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
// Hash input issuance if a mint
|
|
400
|
-
if (tokenTransaction.tokenInputs?.$case === "mintInput") {
|
|
558
|
+
} else if (tokenTransaction.tokenInputs?.$case === "mintInput") {
|
|
401
559
|
const hashObj = sha256.create();
|
|
402
560
|
|
|
403
561
|
if (tokenTransaction.tokenInputs.mintInput!.issuerPublicKey) {
|
|
@@ -412,36 +570,135 @@ export function hashTokenTransactionV1(
|
|
|
412
570
|
});
|
|
413
571
|
}
|
|
414
572
|
hashObj.update(issuerPubKey);
|
|
573
|
+
allHashes.push(hashObj.digest());
|
|
415
574
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
new DataView(timestampBytes.buffer).setBigUint64(
|
|
421
|
-
0,
|
|
422
|
-
BigInt(
|
|
423
|
-
tokenTransaction.tokenInputs.mintInput!.issuerProvidedTimestamp,
|
|
424
|
-
),
|
|
425
|
-
true, // true for little-endian to match Go implementation
|
|
575
|
+
const tokenIdentifierHashObj = sha256.create();
|
|
576
|
+
if (tokenTransaction.tokenInputs.mintInput.tokenIdentifier) {
|
|
577
|
+
tokenIdentifierHashObj.update(
|
|
578
|
+
tokenTransaction.tokenInputs.mintInput.tokenIdentifier,
|
|
426
579
|
);
|
|
427
|
-
|
|
580
|
+
} else {
|
|
581
|
+
tokenIdentifierHashObj.update(new Uint8Array(32));
|
|
428
582
|
}
|
|
429
|
-
allHashes.push(
|
|
583
|
+
allHashes.push(tokenIdentifierHashObj.digest());
|
|
584
|
+
}
|
|
585
|
+
} else if (tokenTransaction.tokenInputs?.$case === "createInput") {
|
|
586
|
+
const createInput = tokenTransaction.tokenInputs.createInput!;
|
|
587
|
+
|
|
588
|
+
// Hash issuer public key
|
|
589
|
+
const issuerPubKeyHashObj = sha256.create();
|
|
590
|
+
if (
|
|
591
|
+
!createInput.issuerPublicKey ||
|
|
592
|
+
createInput.issuerPublicKey.length === 0
|
|
593
|
+
) {
|
|
594
|
+
throw new ValidationError("issuer public key cannot be nil or empty", {
|
|
595
|
+
field: "tokenInputs.createInput.issuerPublicKey",
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
issuerPubKeyHashObj.update(createInput.issuerPublicKey);
|
|
599
|
+
allHashes.push(issuerPubKeyHashObj.digest());
|
|
600
|
+
|
|
601
|
+
// Hash token name
|
|
602
|
+
const tokenNameHashObj = sha256.create();
|
|
603
|
+
if (!createInput.tokenName || createInput.tokenName.length === 0) {
|
|
604
|
+
throw new ValidationError("token name cannot be empty", {
|
|
605
|
+
field: "tokenInputs.createInput.tokenName",
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
if (createInput.tokenName.length > 20) {
|
|
609
|
+
throw new ValidationError("token name cannot be longer than 20 bytes", {
|
|
610
|
+
field: "tokenInputs.createInput.tokenName",
|
|
611
|
+
value: createInput.tokenName,
|
|
612
|
+
expectedLength: 20,
|
|
613
|
+
actualLength: createInput.tokenName.length,
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
const tokenNameEncoder = new TextEncoder();
|
|
617
|
+
tokenNameHashObj.update(tokenNameEncoder.encode(createInput.tokenName));
|
|
618
|
+
allHashes.push(tokenNameHashObj.digest());
|
|
619
|
+
|
|
620
|
+
// Hash token ticker
|
|
621
|
+
const tokenTickerHashObj = sha256.create();
|
|
622
|
+
if (!createInput.tokenTicker || createInput.tokenTicker.length === 0) {
|
|
623
|
+
throw new ValidationError("token ticker cannot be empty", {
|
|
624
|
+
field: "tokenInputs.createInput.tokenTicker",
|
|
625
|
+
});
|
|
430
626
|
}
|
|
627
|
+
if (createInput.tokenTicker.length > 6) {
|
|
628
|
+
throw new ValidationError("token ticker cannot be longer than 6 bytes", {
|
|
629
|
+
field: "tokenInputs.createInput.tokenTicker",
|
|
630
|
+
value: createInput.tokenTicker,
|
|
631
|
+
expectedLength: 6,
|
|
632
|
+
actualLength: createInput.tokenTicker.length,
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
const tokenTickerEncoder = new TextEncoder();
|
|
636
|
+
tokenTickerHashObj.update(
|
|
637
|
+
tokenTickerEncoder.encode(createInput.tokenTicker),
|
|
638
|
+
);
|
|
639
|
+
allHashes.push(tokenTickerHashObj.digest());
|
|
640
|
+
|
|
641
|
+
// Hash decimals
|
|
642
|
+
const decimalsHashObj = sha256.create();
|
|
643
|
+
const decimalsBytes = new Uint8Array(4);
|
|
644
|
+
new DataView(decimalsBytes.buffer).setUint32(
|
|
645
|
+
0,
|
|
646
|
+
createInput.decimals,
|
|
647
|
+
false,
|
|
648
|
+
);
|
|
649
|
+
decimalsHashObj.update(decimalsBytes);
|
|
650
|
+
allHashes.push(decimalsHashObj.digest());
|
|
651
|
+
|
|
652
|
+
// Hash max supply (fixed 16 bytes)
|
|
653
|
+
const maxSupplyHashObj = sha256.create();
|
|
654
|
+
if (!createInput.maxSupply) {
|
|
655
|
+
throw new ValidationError("max supply cannot be nil", {
|
|
656
|
+
field: "tokenInputs.createInput.maxSupply",
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
if (createInput.maxSupply.length !== 16) {
|
|
660
|
+
throw new ValidationError("max supply must be exactly 16 bytes", {
|
|
661
|
+
field: "tokenInputs.createInput.maxSupply",
|
|
662
|
+
value: createInput.maxSupply,
|
|
663
|
+
expectedLength: 16,
|
|
664
|
+
actualLength: createInput.maxSupply.length,
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
maxSupplyHashObj.update(createInput.maxSupply);
|
|
668
|
+
allHashes.push(maxSupplyHashObj.digest());
|
|
669
|
+
|
|
670
|
+
// Hash is freezable
|
|
671
|
+
const isFreezableHashObj = sha256.create();
|
|
672
|
+
isFreezableHashObj.update(
|
|
673
|
+
new Uint8Array([createInput.isFreezable ? 1 : 0]),
|
|
674
|
+
);
|
|
675
|
+
allHashes.push(isFreezableHashObj.digest());
|
|
676
|
+
|
|
677
|
+
// Hash creation entity public key (only for final hash)
|
|
678
|
+
const creationEntityHashObj = sha256.create();
|
|
679
|
+
if (!partialHash && createInput.creationEntityPublicKey) {
|
|
680
|
+
creationEntityHashObj.update(createInput.creationEntityPublicKey);
|
|
681
|
+
}
|
|
682
|
+
allHashes.push(creationEntityHashObj.digest());
|
|
431
683
|
}
|
|
432
684
|
|
|
433
|
-
// Hash token outputs
|
|
685
|
+
// Hash token outputs (length + contents)
|
|
434
686
|
if (!tokenTransaction.tokenOutputs) {
|
|
435
687
|
throw new ValidationError("token outputs cannot be null", {
|
|
436
688
|
field: "tokenOutputs",
|
|
437
689
|
});
|
|
438
690
|
}
|
|
439
691
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
692
|
+
// Hash outputs length
|
|
693
|
+
const outputsLenHashObj = sha256.create();
|
|
694
|
+
const outputsLenBytes = new Uint8Array(4);
|
|
695
|
+
new DataView(outputsLenBytes.buffer).setUint32(
|
|
696
|
+
0,
|
|
697
|
+
tokenTransaction.tokenOutputs.length,
|
|
698
|
+
false,
|
|
699
|
+
);
|
|
700
|
+
outputsLenHashObj.update(outputsLenBytes);
|
|
701
|
+
allHashes.push(outputsLenHashObj.digest());
|
|
445
702
|
|
|
446
703
|
for (const [i, output] of tokenTransaction.tokenOutputs.entries()) {
|
|
447
704
|
if (!output) {
|
|
@@ -508,18 +765,20 @@ export function hashTokenTransactionV1(
|
|
|
508
765
|
hashObj.update(locktimeBytes);
|
|
509
766
|
}
|
|
510
767
|
|
|
511
|
-
if
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
{
|
|
516
|
-
field: `tokenOutputs[${i}].tokenPublicKey`,
|
|
517
|
-
index: i,
|
|
518
|
-
},
|
|
519
|
-
);
|
|
520
|
-
}
|
|
768
|
+
// Hash token public key (33 bytes if present, otherwise 33 zero bytes)
|
|
769
|
+
if (!output.tokenPublicKey || output.tokenPublicKey.length === 0) {
|
|
770
|
+
hashObj.update(new Uint8Array(33));
|
|
771
|
+
} else {
|
|
521
772
|
hashObj.update(output.tokenPublicKey);
|
|
522
773
|
}
|
|
774
|
+
|
|
775
|
+
// Hash token identifier (32 bytes if present, otherwise 32 zero bytes)
|
|
776
|
+
if (!output.tokenIdentifier || output.tokenIdentifier.length === 0) {
|
|
777
|
+
hashObj.update(new Uint8Array(32));
|
|
778
|
+
} else {
|
|
779
|
+
hashObj.update(output.tokenIdentifier);
|
|
780
|
+
}
|
|
781
|
+
|
|
523
782
|
if (output.tokenAmount) {
|
|
524
783
|
if (output.tokenAmount.length === 0) {
|
|
525
784
|
throw new ValidationError(
|
|
@@ -566,6 +825,17 @@ export function hashTokenTransactionV1(
|
|
|
566
825
|
return a.length - b.length;
|
|
567
826
|
});
|
|
568
827
|
|
|
828
|
+
// Hash spark operator identity public keys length
|
|
829
|
+
const operatorLenHashObj = sha256.create();
|
|
830
|
+
const operatorLenBytes = new Uint8Array(4);
|
|
831
|
+
new DataView(operatorLenBytes.buffer).setUint32(
|
|
832
|
+
0,
|
|
833
|
+
sortedPubKeys.length,
|
|
834
|
+
false,
|
|
835
|
+
);
|
|
836
|
+
operatorLenHashObj.update(operatorLenBytes);
|
|
837
|
+
allHashes.push(operatorLenHashObj.digest());
|
|
838
|
+
|
|
569
839
|
// Hash spark operator identity public keys
|
|
570
840
|
for (const [i, pubKey] of sortedPubKeys.entries()) {
|
|
571
841
|
if (!pubKey) {
|
|
@@ -606,9 +876,15 @@ export function hashTokenTransactionV1(
|
|
|
606
876
|
const clientTimestampHashObj = sha256.create();
|
|
607
877
|
const clientCreatedTs: Date | undefined = (tokenTransaction as any)
|
|
608
878
|
.clientCreatedTimestamp;
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
879
|
+
if (!clientCreatedTs) {
|
|
880
|
+
throw new ValidationError(
|
|
881
|
+
"client created timestamp cannot be null for V1 token transactions",
|
|
882
|
+
{
|
|
883
|
+
field: "clientCreatedTimestamp",
|
|
884
|
+
},
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
const clientUnixTime = clientCreatedTs.getTime();
|
|
612
888
|
const clientTimestampBytes = new Uint8Array(8);
|
|
613
889
|
new DataView(clientTimestampBytes.buffer).setBigUint64(
|
|
614
890
|
0,
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { bech32m } from "@scure/base";
|
|
2
|
+
|
|
3
|
+
import { NetworkType } from "../utils/network.js";
|
|
4
|
+
import { ValidationError } from "../errors/index.js";
|
|
5
|
+
|
|
6
|
+
const HumanReadableTokenIdentifierNetworkPrefix: Record<NetworkType, string> = {
|
|
7
|
+
MAINNET: "btk",
|
|
8
|
+
REGTEST: "btkrt",
|
|
9
|
+
TESTNET: "btkt",
|
|
10
|
+
SIGNET: "btks",
|
|
11
|
+
LOCAL: "btkl",
|
|
12
|
+
} as const;
|
|
13
|
+
|
|
14
|
+
export type HumanReadableTokenIdentifier =
|
|
15
|
+
| `btk1${string}`
|
|
16
|
+
| `btkrt1${string}`
|
|
17
|
+
| `btkt1${string}`
|
|
18
|
+
| `btks1${string}`
|
|
19
|
+
| `btkl1${string}`;
|
|
20
|
+
|
|
21
|
+
export interface HumanReadableTokenIdentifierData {
|
|
22
|
+
tokenIdentifier: Uint8Array;
|
|
23
|
+
network: NetworkType;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function encodeHumanReadableTokenIdentifier(
|
|
27
|
+
payload: HumanReadableTokenIdentifierData,
|
|
28
|
+
): HumanReadableTokenIdentifier {
|
|
29
|
+
try {
|
|
30
|
+
const words = bech32m.toWords(payload.tokenIdentifier);
|
|
31
|
+
return bech32m.encode(
|
|
32
|
+
HumanReadableTokenIdentifierNetworkPrefix[payload.network],
|
|
33
|
+
words,
|
|
34
|
+
500,
|
|
35
|
+
) as HumanReadableTokenIdentifier;
|
|
36
|
+
} catch (error) {
|
|
37
|
+
throw new ValidationError(
|
|
38
|
+
"Failed to encode human readable token identifier",
|
|
39
|
+
{
|
|
40
|
+
field: "tokenIdentifier",
|
|
41
|
+
value: payload.tokenIdentifier,
|
|
42
|
+
},
|
|
43
|
+
error as Error,
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function decodeHumanReadableTokenIdentifier(
|
|
49
|
+
humanReadableTokenIdentifier: HumanReadableTokenIdentifier,
|
|
50
|
+
network: NetworkType,
|
|
51
|
+
): HumanReadableTokenIdentifierData {
|
|
52
|
+
try {
|
|
53
|
+
const decoded = bech32m.decode(
|
|
54
|
+
humanReadableTokenIdentifier as HumanReadableTokenIdentifier,
|
|
55
|
+
500,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
if (decoded.prefix !== HumanReadableTokenIdentifierNetworkPrefix[network]) {
|
|
59
|
+
throw new ValidationError(
|
|
60
|
+
"Invalid human readable token identifier prefix",
|
|
61
|
+
{
|
|
62
|
+
field: "humanReadableTokenIdentifier",
|
|
63
|
+
value: humanReadableTokenIdentifier,
|
|
64
|
+
expected: `prefix='${HumanReadableTokenIdentifierNetworkPrefix[network]}'`,
|
|
65
|
+
},
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const tokenIdentifier = bech32m.fromWords(decoded.words);
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
tokenIdentifier,
|
|
73
|
+
network,
|
|
74
|
+
};
|
|
75
|
+
} catch (error) {
|
|
76
|
+
if (error instanceof ValidationError) {
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
throw new ValidationError(
|
|
80
|
+
"Failed to decode human readable token identifier",
|
|
81
|
+
{
|
|
82
|
+
field: "humanReadableTokenIdentifier",
|
|
83
|
+
value: humanReadableTokenIdentifier,
|
|
84
|
+
},
|
|
85
|
+
error as Error,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|