@wishknish/knishio-client-js 0.7.4 → 0.7.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/dist/client.cjs.js +81 -27
- package/dist/client.cjs.js.map +1 -1
- package/dist/client.es.mjs +1256 -767
- package/dist/client.es.mjs.map +1 -1
- package/dist/client.iife.js +81 -27
- package/dist/client.iife.js.map +1 -1
- package/package.json +1 -1
- package/src/KnishIOClient.js +47 -2
- package/src/Molecule.js +102 -14
- package/src/Wallet.js +48 -1
- package/src/index.js +6 -0
- package/src/libraries/CheckMolecule.js +211 -5
- package/src/query/QueryMetaTypeViaMolecule.js +223 -0
- package/src/response/ResponseContinuId.js +1 -1
- package/src/response/ResponseMetaTypeViaMolecule.js +210 -0
- package/src/response/ResponseWalletList.js +1 -1
package/package.json
CHANGED
package/src/KnishIOClient.js
CHANGED
|
@@ -54,6 +54,7 @@ import {
|
|
|
54
54
|
} from './libraries/crypto.js'
|
|
55
55
|
import Molecule from './Molecule.js'
|
|
56
56
|
import Wallet from './Wallet.js'
|
|
57
|
+
import TokenUnit from './TokenUnit.js'
|
|
57
58
|
import AuthToken from './AuthToken.js'
|
|
58
59
|
import QueryContinuId from './query/QueryContinuId.js'
|
|
59
60
|
import QueryWalletBundle from './query/QueryWalletBundle.js'
|
|
@@ -93,6 +94,7 @@ import AuthorizationRejectedException from './exception/AuthorizationRejectedExc
|
|
|
93
94
|
import QueryAtom from './query/QueryAtom.js'
|
|
94
95
|
import QueryPolicy from './query/QueryPolicy.js'
|
|
95
96
|
import QueryMetaTypeViaAtom from './query/QueryMetaTypeViaAtom.js'
|
|
97
|
+
import QueryMetaTypeViaMolecule from './query/QueryMetaTypeViaMolecule.js'
|
|
96
98
|
import MutationCreateRule from './mutation/MutationCreateRule.js'
|
|
97
99
|
import MutationDepositBufferToken from './mutation/MutationDepositBufferToken.js'
|
|
98
100
|
import MutationWithdrawBufferToken from './mutation/MutationWithdrawBufferToken.js'
|
|
@@ -777,10 +779,11 @@ export default class KnishIOClient {
|
|
|
777
779
|
* @param {string|null} count
|
|
778
780
|
* @param {string|null} countBy
|
|
779
781
|
* @param {boolean} throughAtom
|
|
782
|
+
* @param {boolean} throughMolecule
|
|
780
783
|
* @param {array|null} values
|
|
781
784
|
* @param {array|null} keys
|
|
782
785
|
* @param {array|null} atomValues
|
|
783
|
-
* @return {Promise<ResponseMetaType>}
|
|
786
|
+
* @return {Promise<ResponseMetaType|ResponseMetaTypeViaAtom|ResponseMetaTypeViaMolecule>}
|
|
784
787
|
*/
|
|
785
788
|
queryMeta ({
|
|
786
789
|
metaType,
|
|
@@ -794,6 +797,7 @@ export default class KnishIOClient {
|
|
|
794
797
|
count = null,
|
|
795
798
|
countBy = null,
|
|
796
799
|
throughAtom = true,
|
|
800
|
+
throughMolecule = false,
|
|
797
801
|
values = null,
|
|
798
802
|
keys = null,
|
|
799
803
|
atomValues = null
|
|
@@ -803,7 +807,26 @@ export default class KnishIOClient {
|
|
|
803
807
|
let query
|
|
804
808
|
let variables
|
|
805
809
|
|
|
806
|
-
if (
|
|
810
|
+
if (throughMolecule) {
|
|
811
|
+
/**
|
|
812
|
+
* @type {QueryMetaTypeViaMolecule}
|
|
813
|
+
*/
|
|
814
|
+
query = this.createQuery(QueryMetaTypeViaMolecule)
|
|
815
|
+
variables = QueryMetaTypeViaMolecule.createVariables({
|
|
816
|
+
metaType,
|
|
817
|
+
metaId,
|
|
818
|
+
key,
|
|
819
|
+
value,
|
|
820
|
+
latest,
|
|
821
|
+
filter,
|
|
822
|
+
queryArgs,
|
|
823
|
+
countBy,
|
|
824
|
+
values,
|
|
825
|
+
keys,
|
|
826
|
+
atomValues,
|
|
827
|
+
cellSlug: this.getCellSlug()
|
|
828
|
+
})
|
|
829
|
+
} else if (throughAtom) {
|
|
807
830
|
/**
|
|
808
831
|
* @type {QueryMetaTypeViaAtom}
|
|
809
832
|
*/
|
|
@@ -844,6 +867,23 @@ export default class KnishIOClient {
|
|
|
844
867
|
return this.executeQuery(query, variables)
|
|
845
868
|
}
|
|
846
869
|
|
|
870
|
+
/**
|
|
871
|
+
* Queries meta assets and verifies cryptographic integrity of associated molecules.
|
|
872
|
+
* Returns the same response as queryMeta(), with an additional `integrity` field on the payload
|
|
873
|
+
* containing verification results for each molecule.
|
|
874
|
+
*
|
|
875
|
+
* @param {object} params - Same parameters as queryMeta()
|
|
876
|
+
* @return {Promise<ResponseMetaType|ResponseMetaTypeViaAtom|ResponseMetaTypeViaMolecule>}
|
|
877
|
+
*/
|
|
878
|
+
async queryMetaVerified (params) {
|
|
879
|
+
const response = await this.queryMeta(params)
|
|
880
|
+
const payload = response.payload()
|
|
881
|
+
if (payload) {
|
|
882
|
+
payload.integrity = response.verifyIntegrity()
|
|
883
|
+
}
|
|
884
|
+
return response
|
|
885
|
+
}
|
|
886
|
+
|
|
847
887
|
/**
|
|
848
888
|
* Query batch to get cascading meta instances by batchID
|
|
849
889
|
*
|
|
@@ -2022,6 +2062,11 @@ export default class KnishIOClient {
|
|
|
2022
2062
|
// Split token units (fused)
|
|
2023
2063
|
sourceWallet.splitUnits(fusedTokenUnitIds, remainderWallet)
|
|
2024
2064
|
|
|
2065
|
+
// Coerce string newTokenUnit to TokenUnit object
|
|
2066
|
+
if (typeof newTokenUnit === 'string') {
|
|
2067
|
+
newTokenUnit = new TokenUnit(newTokenUnit, newTokenUnit, {})
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2025
2070
|
// Set recipient new fused token unit
|
|
2026
2071
|
newTokenUnit.metas.fusedTokenUnits = sourceWallet.getTokenUnitsData()
|
|
2027
2072
|
recipientWallet.tokenUnits = [newTokenUnit]
|
package/src/Molecule.js
CHANGED
|
@@ -96,6 +96,7 @@ export default class Molecule {
|
|
|
96
96
|
this.bundle = bundle
|
|
97
97
|
this.sourceWallet = sourceWallet
|
|
98
98
|
this.atoms = []
|
|
99
|
+
this.parentHashes = []
|
|
99
100
|
if (version !== null && Object.prototype.hasOwnProperty.call(versions, version)) {
|
|
100
101
|
this.version = String(version)
|
|
101
102
|
}
|
|
@@ -112,6 +113,17 @@ export default class Molecule {
|
|
|
112
113
|
}
|
|
113
114
|
}
|
|
114
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Sets parent molecular hashes for DAG linkage
|
|
118
|
+
*
|
|
119
|
+
* @param {string[]} hashes - Array of parent molecular hash strings
|
|
120
|
+
* @return {Molecule} this instance for chaining
|
|
121
|
+
*/
|
|
122
|
+
withParentHashes (hashes) {
|
|
123
|
+
this.parentHashes = Array.isArray(hashes) ? [...hashes] : []
|
|
124
|
+
return this
|
|
125
|
+
}
|
|
126
|
+
|
|
115
127
|
/**
|
|
116
128
|
* Returns the cell slug delimiter
|
|
117
129
|
*
|
|
@@ -327,20 +339,21 @@ export default class Molecule {
|
|
|
327
339
|
})
|
|
328
340
|
}
|
|
329
341
|
|
|
330
|
-
//
|
|
342
|
+
// ContinuID metadata for chain integrity validation (matching Rust SDK GAP-07-002).
|
|
343
|
+
// These are atom meta fields (not core fields), so they don't affect molecular hash.
|
|
331
344
|
const continuIdMeta = {}
|
|
332
345
|
|
|
333
|
-
// previousPosition: source wallet position being consumed
|
|
346
|
+
// previousPosition: the source wallet's position being consumed
|
|
334
347
|
if (this.sourceWallet && this.sourceWallet.position) {
|
|
335
348
|
continuIdMeta.previousPosition = this.sourceWallet.position
|
|
336
349
|
}
|
|
337
350
|
|
|
338
|
-
// pubkey:
|
|
351
|
+
// pubkey: ML-KEM public key for encrypted communication
|
|
339
352
|
if (this.remainderWallet.pubkey) {
|
|
340
353
|
continuIdMeta.pubkey = this.remainderWallet.pubkey
|
|
341
354
|
}
|
|
342
355
|
|
|
343
|
-
// characters: encoding format
|
|
356
|
+
// characters: encoding format for the wallet
|
|
344
357
|
if (this.remainderWallet.characters) {
|
|
345
358
|
continuIdMeta.characters = this.remainderWallet.characters
|
|
346
359
|
}
|
|
@@ -493,28 +506,28 @@ export default class Molecule {
|
|
|
493
506
|
for (const unit of units) {
|
|
494
507
|
this.remainderWallet.tokenUnits.push(unit)
|
|
495
508
|
}
|
|
496
|
-
this.remainderWallet.balance = this.remainderWallet.tokenUnits.length
|
|
509
|
+
this.remainderWallet.balance = String(this.remainderWallet.tokenUnits.length)
|
|
497
510
|
|
|
498
511
|
// Override first atom's token units to replenish values
|
|
499
512
|
this.sourceWallet.tokenUnits = units
|
|
500
|
-
this.sourceWallet.balance = this.sourceWallet.tokenUnits.length
|
|
513
|
+
this.sourceWallet.balance = String(this.sourceWallet.tokenUnits.length)
|
|
501
514
|
} else {
|
|
502
515
|
// Update wallet's balances
|
|
503
|
-
this.remainderWallet.balance = this.sourceWallet.balance + amount
|
|
504
|
-
this.sourceWallet.balance = amount
|
|
516
|
+
this.remainderWallet.balance = String(Number(this.sourceWallet.balance) + amount)
|
|
517
|
+
this.sourceWallet.balance = String(amount)
|
|
505
518
|
}
|
|
506
519
|
|
|
507
520
|
// Initializing a new Atom to remove tokens from source
|
|
508
521
|
this.addAtom(Atom.create({
|
|
509
522
|
isotope: 'V',
|
|
510
523
|
wallet: this.sourceWallet,
|
|
511
|
-
value: this.sourceWallet.balance
|
|
524
|
+
value: Number(this.sourceWallet.balance)
|
|
512
525
|
}))
|
|
513
526
|
|
|
514
527
|
this.addAtom(Atom.create({
|
|
515
528
|
isotope: 'V',
|
|
516
529
|
wallet: this.remainderWallet,
|
|
517
|
-
value: this.remainderWallet.balance,
|
|
530
|
+
value: Number(this.remainderWallet.balance),
|
|
518
531
|
metaType: 'walletBundle',
|
|
519
532
|
metaId: this.remainderWallet.bundle
|
|
520
533
|
}))
|
|
@@ -564,6 +577,60 @@ export default class Molecule {
|
|
|
564
577
|
return this
|
|
565
578
|
}
|
|
566
579
|
|
|
580
|
+
/**
|
|
581
|
+
* Creates a stackable V-isotope transfer with 3 atoms:
|
|
582
|
+
* source debit, recipient credit, remainder.
|
|
583
|
+
* Propagates batchId from source wallet.
|
|
584
|
+
*
|
|
585
|
+
* @param {Wallet} recipientWallet - wallet receiving the tokens
|
|
586
|
+
* @param {number} amount - amount to transfer
|
|
587
|
+
* @return {Molecule}
|
|
588
|
+
*/
|
|
589
|
+
addStackableTransfer ({
|
|
590
|
+
recipientWallet,
|
|
591
|
+
amount
|
|
592
|
+
}) {
|
|
593
|
+
if (amount <= 0) {
|
|
594
|
+
throw new NegativeAmountException('Molecule::addStackableTransfer() - Amount must be positive!')
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
if (this.sourceWallet.balance - amount < 0) {
|
|
598
|
+
throw new BalanceInsufficientException()
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const batchId = this.sourceWallet.batchId || generateBatchId({})
|
|
602
|
+
|
|
603
|
+
// Source debit atom
|
|
604
|
+
this.addAtom(Atom.create({
|
|
605
|
+
isotope: 'V',
|
|
606
|
+
wallet: this.sourceWallet,
|
|
607
|
+
value: -amount,
|
|
608
|
+
batchId
|
|
609
|
+
}))
|
|
610
|
+
|
|
611
|
+
// Recipient credit atom
|
|
612
|
+
this.addAtom(Atom.create({
|
|
613
|
+
isotope: 'V',
|
|
614
|
+
wallet: recipientWallet,
|
|
615
|
+
value: amount,
|
|
616
|
+
metaType: 'walletBundle',
|
|
617
|
+
metaId: recipientWallet.bundle,
|
|
618
|
+
batchId: generateBatchId({})
|
|
619
|
+
}))
|
|
620
|
+
|
|
621
|
+
// Remainder atom
|
|
622
|
+
this.addAtom(Atom.create({
|
|
623
|
+
isotope: 'V',
|
|
624
|
+
wallet: this.remainderWallet,
|
|
625
|
+
value: this.sourceWallet.balance - amount,
|
|
626
|
+
metaType: 'walletBundle',
|
|
627
|
+
metaId: this.remainderWallet.bundle,
|
|
628
|
+
batchId
|
|
629
|
+
}))
|
|
630
|
+
|
|
631
|
+
return this
|
|
632
|
+
}
|
|
633
|
+
|
|
567
634
|
/**
|
|
568
635
|
*
|
|
569
636
|
* @param amount
|
|
@@ -1053,6 +1120,20 @@ export default class Molecule {
|
|
|
1053
1120
|
return lastPosition
|
|
1054
1121
|
}
|
|
1055
1122
|
|
|
1123
|
+
/**
|
|
1124
|
+
* Synchronous signing — identical to sign() since all operations are CPU-bound.
|
|
1125
|
+
* Provided for API parity with Rust SDK's sign_sync().
|
|
1126
|
+
*
|
|
1127
|
+
* @param {object} options
|
|
1128
|
+
* @param {string|null} options.bundle
|
|
1129
|
+
* @param {boolean} options.anonymous
|
|
1130
|
+
* @param {boolean} options.compressed
|
|
1131
|
+
* @return {string|null}
|
|
1132
|
+
*/
|
|
1133
|
+
signSync (options = {}) {
|
|
1134
|
+
return this.sign(options)
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1056
1137
|
/**
|
|
1057
1138
|
* Returns the base cell slug portion
|
|
1058
1139
|
*
|
|
@@ -1096,6 +1177,12 @@ export default class Molecule {
|
|
|
1096
1177
|
}))
|
|
1097
1178
|
};
|
|
1098
1179
|
|
|
1180
|
+
// Parent molecular hashes for DAG linkage (only include when non-empty
|
|
1181
|
+
// to maintain backward compatibility with servers that don't support it)
|
|
1182
|
+
if (this.parentHashes && this.parentHashes.length > 0) {
|
|
1183
|
+
serialized.parentHashes = this.parentHashes
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1099
1186
|
// Extended context for Rust validator and local validation
|
|
1100
1187
|
if (includeValidationContext) {
|
|
1101
1188
|
serialized.cellSlugOrigin = this.cellSlugOrigin
|
|
@@ -1106,7 +1193,7 @@ export default class Molecule {
|
|
|
1106
1193
|
address: this.sourceWallet.address,
|
|
1107
1194
|
position: this.sourceWallet.position,
|
|
1108
1195
|
token: this.sourceWallet.token,
|
|
1109
|
-
balance: this.sourceWallet.balance || 0,
|
|
1196
|
+
balance: this.sourceWallet.balance || '0',
|
|
1110
1197
|
bundle: this.sourceWallet.bundle,
|
|
1111
1198
|
batchId: this.sourceWallet.batchId || null,
|
|
1112
1199
|
characters: this.sourceWallet.characters || 'BASE64',
|
|
@@ -1122,7 +1209,7 @@ export default class Molecule {
|
|
|
1122
1209
|
address: this.remainderWallet.address,
|
|
1123
1210
|
position: this.remainderWallet.position,
|
|
1124
1211
|
token: this.remainderWallet.token,
|
|
1125
|
-
balance: this.remainderWallet.balance || 0,
|
|
1212
|
+
balance: this.remainderWallet.balance || '0',
|
|
1126
1213
|
bundle: this.remainderWallet.bundle,
|
|
1127
1214
|
batchId: this.remainderWallet.batchId || null,
|
|
1128
1215
|
characters: this.remainderWallet.characters || 'BASE64',
|
|
@@ -1184,6 +1271,7 @@ export default class Molecule {
|
|
|
1184
1271
|
molecule.molecularHash = data.molecularHash;
|
|
1185
1272
|
molecule.createdAt = data.createdAt || String(+new Date());
|
|
1186
1273
|
molecule.cellSlugOrigin = data.cellSlugOrigin;
|
|
1274
|
+
molecule.parentHashes = Array.isArray(data.parentHashes) ? [...data.parentHashes] : [];
|
|
1187
1275
|
|
|
1188
1276
|
// Reconstruct atoms array with proper Atom instances
|
|
1189
1277
|
if (Array.isArray(data.atoms)) {
|
|
@@ -1210,7 +1298,7 @@ export default class Molecule {
|
|
|
1210
1298
|
});
|
|
1211
1299
|
|
|
1212
1300
|
// Set additional properties for validation context
|
|
1213
|
-
molecule.sourceWallet.balance = data.sourceWallet.balance
|
|
1301
|
+
molecule.sourceWallet.balance = String(data.sourceWallet.balance != null ? data.sourceWallet.balance : 0);
|
|
1214
1302
|
molecule.sourceWallet.address = data.sourceWallet.address;
|
|
1215
1303
|
if (data.sourceWallet.pubkey) {
|
|
1216
1304
|
molecule.sourceWallet.pubkey = data.sourceWallet.pubkey;
|
|
@@ -1232,7 +1320,7 @@ export default class Molecule {
|
|
|
1232
1320
|
});
|
|
1233
1321
|
|
|
1234
1322
|
// Set additional properties for validation context
|
|
1235
|
-
molecule.remainderWallet.balance = data.remainderWallet.balance
|
|
1323
|
+
molecule.remainderWallet.balance = String(data.remainderWallet.balance != null ? data.remainderWallet.balance : 0);
|
|
1236
1324
|
molecule.remainderWallet.address = data.remainderWallet.address;
|
|
1237
1325
|
if (data.remainderWallet.pubkey) {
|
|
1238
1326
|
molecule.remainderWallet.pubkey = data.remainderWallet.pubkey;
|
package/src/Wallet.js
CHANGED
|
@@ -87,7 +87,7 @@ export default class Wallet {
|
|
|
87
87
|
characters = null
|
|
88
88
|
}) {
|
|
89
89
|
this.token = token
|
|
90
|
-
this.balance = 0
|
|
90
|
+
this.balance = '0'
|
|
91
91
|
this.molecules = {}
|
|
92
92
|
|
|
93
93
|
// Empty values
|
|
@@ -208,6 +208,13 @@ export default class Wallet {
|
|
|
208
208
|
token,
|
|
209
209
|
position
|
|
210
210
|
}) {
|
|
211
|
+
if (!secret) {
|
|
212
|
+
throw new WalletCredentialException('Wallet::generateKey() - Secret is required!')
|
|
213
|
+
}
|
|
214
|
+
if (!position) {
|
|
215
|
+
throw new WalletCredentialException('Wallet::generateKey() - Position is required!')
|
|
216
|
+
}
|
|
217
|
+
|
|
211
218
|
// Normalize non-hex secret via SHAKE256 (matching Rust/Kotlin behavior)
|
|
212
219
|
const secretHex = isHex(secret) ? secret : shake256(secret, 1024)
|
|
213
220
|
// Normalize non-hex position via SHAKE256
|
|
@@ -301,6 +308,46 @@ export default class Wallet {
|
|
|
301
308
|
return new Uint8Array(binaryString.length).map((_, i) => binaryString.charCodeAt(i))
|
|
302
309
|
}
|
|
303
310
|
|
|
311
|
+
/**
|
|
312
|
+
* Returns balance as a Number for arithmetic operations.
|
|
313
|
+
* WARNING: Precision loss for values > 2^53.
|
|
314
|
+
*
|
|
315
|
+
* @return {number}
|
|
316
|
+
*/
|
|
317
|
+
balanceAsNumber () {
|
|
318
|
+
return Number(this.balance)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Returns balance as a BigInt for precision-safe integer arithmetic.
|
|
323
|
+
* Truncates any fractional component.
|
|
324
|
+
*
|
|
325
|
+
* @return {bigint}
|
|
326
|
+
*/
|
|
327
|
+
balanceAsBigInt () {
|
|
328
|
+
const str = String(this.balance)
|
|
329
|
+
const intPart = str.includes('.') ? str.split('.')[0] : str
|
|
330
|
+
return BigInt(intPart || '0')
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Sets balance from a BigInt value
|
|
335
|
+
*
|
|
336
|
+
* @param {bigint} value
|
|
337
|
+
*/
|
|
338
|
+
setBalanceBigInt (value) {
|
|
339
|
+
this.balance = value.toString()
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Sets balance from a Number value, storing as String
|
|
344
|
+
*
|
|
345
|
+
* @param {number} value
|
|
346
|
+
*/
|
|
347
|
+
setBalanceNumber (value) {
|
|
348
|
+
this.balance = String(value)
|
|
349
|
+
}
|
|
350
|
+
|
|
304
351
|
/**
|
|
305
352
|
*
|
|
306
353
|
* @returns {*[]}
|
package/src/index.js
CHANGED
|
@@ -53,8 +53,10 @@ import Meta from './Meta.js'
|
|
|
53
53
|
import KnishIOClient from './KnishIOClient.js'
|
|
54
54
|
import MutationPeering from './mutation/MutationPeering.js'
|
|
55
55
|
import MutationAppendRequest from './mutation/MutationAppendRequest.js'
|
|
56
|
+
import QueryMetaTypeViaMolecule from './query/QueryMetaTypeViaMolecule.js'
|
|
56
57
|
import ResponsePeering from './response/ResponsePeering.js'
|
|
57
58
|
import ResponseAppendRequest from './response/ResponseAppendRequest.js'
|
|
59
|
+
import ResponseMetaTypeViaMolecule from './response/ResponseMetaTypeViaMolecule.js'
|
|
58
60
|
import {
|
|
59
61
|
base64ToHex,
|
|
60
62
|
bufferToHexString,
|
|
@@ -78,6 +80,9 @@ export {
|
|
|
78
80
|
Meta,
|
|
79
81
|
KnishIOClient,
|
|
80
82
|
|
|
83
|
+
// queries
|
|
84
|
+
QueryMetaTypeViaMolecule,
|
|
85
|
+
|
|
81
86
|
// mutations
|
|
82
87
|
MutationPeering,
|
|
83
88
|
MutationAppendRequest,
|
|
@@ -85,6 +90,7 @@ export {
|
|
|
85
90
|
// responses
|
|
86
91
|
ResponsePeering,
|
|
87
92
|
ResponseAppendRequest,
|
|
93
|
+
ResponseMetaTypeViaMolecule,
|
|
88
94
|
|
|
89
95
|
// strings
|
|
90
96
|
chunkSubstr,
|