@wishknish/knishio-client-js 0.7.4 → 0.7.6

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/src/index.js CHANGED
@@ -46,15 +46,112 @@ Please visit https://github.com/WishKnish/KnishIO-Client-JS for information.
46
46
  License: https://github.com/WishKnish/KnishIO-Client-JS/blob/master/LICENSE
47
47
  */
48
48
 
49
+ // =============================================================================
50
+ // CORE CLASSES
51
+ // =============================================================================
52
+
49
53
  import Atom from './Atom.js'
54
+ import AtomMeta from './AtomMeta.js'
50
55
  import Molecule from './Molecule.js'
51
56
  import Wallet from './Wallet.js'
52
57
  import Meta from './Meta.js'
58
+ import AuthToken from './AuthToken.js'
59
+ import TokenUnit from './TokenUnit.js'
60
+ import PolicyMeta from './PolicyMeta.js'
53
61
  import KnishIOClient from './KnishIOClient.js'
54
- import MutationPeering from './mutation/MutationPeering.js'
62
+
63
+ // Validation
64
+ import CheckMolecule from './libraries/CheckMolecule.js'
65
+
66
+ // Utilities
67
+ import Dot from './libraries/Dot.js'
68
+ import Decimal from './libraries/Decimal.js'
69
+
70
+ // =============================================================================
71
+ // BASE CLASSES
72
+ // =============================================================================
73
+
74
+ import Query from './query/Query.js'
75
+ import Mutation from './mutation/Mutation.js'
76
+ import Response from './response/Response.js'
77
+
78
+ // =============================================================================
79
+ // QUERY CLASSES
80
+ // =============================================================================
81
+
82
+ import QueryActiveSession from './query/QueryActiveSession.js'
83
+ import QueryAtom from './query/QueryAtom.js'
84
+ import QueryBalance from './query/QueryBalance.js'
85
+ import QueryBatch from './query/QueryBatch.js'
86
+ import QueryBatchHistory from './query/QueryBatchHistory.js'
87
+ import QueryContinuId from './query/QueryContinuId.js'
88
+ import QueryMetaType from './query/QueryMetaType.js'
89
+ import QueryMetaTypeViaAtom from './query/QueryMetaTypeViaAtom.js'
90
+ import QueryMetaTypeViaMolecule from './query/QueryMetaTypeViaMolecule.js'
91
+ import QueryPolicy from './query/QueryPolicy.js'
92
+ import QueryToken from './query/QueryToken.js'
93
+ import QueryUserActivity from './query/QueryUserActivity.js'
94
+ import QueryWalletBundle from './query/QueryWalletBundle.js'
95
+ import QueryWalletList from './query/QueryWalletList.js'
96
+
97
+ // =============================================================================
98
+ // MUTATION CLASSES
99
+ // =============================================================================
100
+
101
+ import MutationActiveSession from './mutation/MutationActiveSession.js'
55
102
  import MutationAppendRequest from './mutation/MutationAppendRequest.js'
56
- import ResponsePeering from './response/ResponsePeering.js'
103
+ import MutationClaimShadowWallet from './mutation/MutationClaimShadowWallet.js'
104
+ import MutationCreateIdentifier from './mutation/MutationCreateIdentifier.js'
105
+ import MutationCreateMeta from './mutation/MutationCreateMeta.js'
106
+ import MutationCreateRule from './mutation/MutationCreateRule.js'
107
+ import MutationCreateToken from './mutation/MutationCreateToken.js'
108
+ import MutationCreateWallet from './mutation/MutationCreateWallet.js'
109
+ import MutationDepositBufferToken from './mutation/MutationDepositBufferToken.js'
110
+ import MutationLinkIdentifier from './mutation/MutationLinkIdentifier.js'
111
+ import MutationPeering from './mutation/MutationPeering.js'
112
+ import MutationProposeMolecule from './mutation/MutationProposeMolecule.js'
113
+ import MutationRequestAuthorization from './mutation/MutationRequestAuthorization.js'
114
+ import MutationRequestAuthorizationGuest from './mutation/MutationRequestAuthorizationGuest.js'
115
+ import MutationRequestTokens from './mutation/MutationRequestTokens.js'
116
+ import MutationTransferTokens from './mutation/MutationTransferTokens.js'
117
+ import MutationWithdrawBufferToken from './mutation/MutationWithdrawBufferToken.js'
118
+
119
+ // =============================================================================
120
+ // RESPONSE CLASSES
121
+ // =============================================================================
122
+
123
+ import ResponseActiveSession from './response/ResponseActiveSession.js'
57
124
  import ResponseAppendRequest from './response/ResponseAppendRequest.js'
125
+ import ResponseAtom from './response/ResponseAtom.js'
126
+ import ResponseAuthorizationGuest from './response/ResponseAuthorizationGuest.js'
127
+ import ResponseBalance from './response/ResponseBalance.js'
128
+ import ResponseClaimShadowWallet from './response/ResponseClaimShadowWallet.js'
129
+ import ResponseContinuId from './response/ResponseContinuId.js'
130
+ import ResponseCreateIdentifier from './response/ResponseCreateIdentifier.js'
131
+ import ResponseCreateMeta from './response/ResponseCreateMeta.js'
132
+ import ResponseCreateRule from './response/ResponseCreateRule.js'
133
+ import ResponseCreateToken from './response/ResponseCreateToken.js'
134
+ import ResponseCreateWallet from './response/ResponseCreateWallet.js'
135
+ import ResponseLinkIdentifier from './response/ResponseLinkIdentifier.js'
136
+ import ResponseMetaType from './response/ResponseMetaType.js'
137
+ import ResponseMetaTypeViaAtom from './response/ResponseMetaTypeViaAtom.js'
138
+ import ResponseMetaTypeViaMolecule from './response/ResponseMetaTypeViaMolecule.js'
139
+ import ResponsePeering from './response/ResponsePeering.js'
140
+ import ResponsePolicy from './response/ResponsePolicy.js'
141
+ import ResponseProposeMolecule from './response/ResponseProposeMolecule.js'
142
+ import ResponseQueryActiveSession from './response/ResponseQueryActiveSession.js'
143
+ import ResponseQueryUserActivity from './response/ResponseQueryUserActivity.js'
144
+ import ResponseRequestAuthorization from './response/ResponseRequestAuthorization.js'
145
+ import ResponseRequestAuthorizationGuest from './response/ResponseRequestAuthorizationGuest.js'
146
+ import ResponseRequestTokens from './response/ResponseRequestTokens.js'
147
+ import ResponseTransferTokens from './response/ResponseTransferTokens.js'
148
+ import ResponseWalletBundle from './response/ResponseWalletBundle.js'
149
+ import ResponseWalletList from './response/ResponseWalletList.js'
150
+
151
+ // =============================================================================
152
+ // LIBRARY FUNCTIONS
153
+ // =============================================================================
154
+
58
155
  import {
59
156
  base64ToHex,
60
157
  bufferToHexString,
@@ -63,30 +160,149 @@ import {
63
160
  hexStringToBuffer,
64
161
  hexToBase64,
65
162
  isHex,
163
+ isNumeric,
66
164
  randomString
67
165
  } from './libraries/strings.js'
166
+
68
167
  import {
168
+ generateBatchId,
69
169
  generateBundleHash,
70
170
  generateSecret,
71
171
  shake256
72
172
  } from './libraries/crypto.js'
73
173
 
174
+ import {
175
+ chunkArray,
176
+ deepCloning,
177
+ diff,
178
+ intersect
179
+ } from './libraries/array.js'
180
+
181
+ // =============================================================================
182
+ // EXCEPTION SYSTEM
183
+ // =============================================================================
184
+
74
185
  export {
186
+ AtomIndexException,
187
+ AtomsMissingException,
188
+ AuthorizationRejectedException,
189
+ BalanceInsufficientException,
190
+ BatchIdException,
191
+ CodeException,
192
+ InvalidResponseException,
193
+ MetaMissingException,
194
+ MolecularHashMismatchException,
195
+ MolecularHashMissingException,
196
+ NegativeAmountException,
197
+ PolicyInvalidException,
198
+ SignatureMalformedException,
199
+ SignatureMismatchException,
200
+ StackableUnitAmountException,
201
+ StackableUnitDecimalsException,
202
+ TransferBalanceException,
203
+ TransferMalformedException,
204
+ TransferMismatchedException,
205
+ TransferRemainderException,
206
+ TransferToSelfException,
207
+ TransferUnbalancedException,
208
+ UnauthenticatedException,
209
+ WalletShadowException,
210
+ WrongTokenTypeException
211
+ } from './exception/index.js'
212
+
213
+ // =============================================================================
214
+ // NAMED EXPORTS
215
+ // =============================================================================
216
+
217
+ export {
218
+ // Core classes
75
219
  Atom,
220
+ AtomMeta,
76
221
  Molecule,
77
222
  Wallet,
78
223
  Meta,
224
+ AuthToken,
225
+ TokenUnit,
226
+ PolicyMeta,
79
227
  KnishIOClient,
80
228
 
81
- // mutations
82
- MutationPeering,
229
+ // Validation
230
+ CheckMolecule,
231
+
232
+ // Utilities
233
+ Dot,
234
+ Decimal,
235
+
236
+ // Base classes
237
+ Query,
238
+ Mutation,
239
+ Response,
240
+
241
+ // Queries
242
+ QueryActiveSession,
243
+ QueryAtom,
244
+ QueryBalance,
245
+ QueryBatch,
246
+ QueryBatchHistory,
247
+ QueryContinuId,
248
+ QueryMetaType,
249
+ QueryMetaTypeViaAtom,
250
+ QueryMetaTypeViaMolecule,
251
+ QueryPolicy,
252
+ QueryToken,
253
+ QueryUserActivity,
254
+ QueryWalletBundle,
255
+ QueryWalletList,
256
+
257
+ // Mutations
258
+ MutationActiveSession,
83
259
  MutationAppendRequest,
260
+ MutationClaimShadowWallet,
261
+ MutationCreateIdentifier,
262
+ MutationCreateMeta,
263
+ MutationCreateRule,
264
+ MutationCreateToken,
265
+ MutationCreateWallet,
266
+ MutationDepositBufferToken,
267
+ MutationLinkIdentifier,
268
+ MutationPeering,
269
+ MutationProposeMolecule,
270
+ MutationRequestAuthorization,
271
+ MutationRequestAuthorizationGuest,
272
+ MutationRequestTokens,
273
+ MutationTransferTokens,
274
+ MutationWithdrawBufferToken,
84
275
 
85
- // responses
86
- ResponsePeering,
276
+ // Responses
277
+ ResponseActiveSession,
87
278
  ResponseAppendRequest,
279
+ ResponseAtom,
280
+ ResponseAuthorizationGuest,
281
+ ResponseBalance,
282
+ ResponseClaimShadowWallet,
283
+ ResponseContinuId,
284
+ ResponseCreateIdentifier,
285
+ ResponseCreateMeta,
286
+ ResponseCreateRule,
287
+ ResponseCreateToken,
288
+ ResponseCreateWallet,
289
+ ResponseLinkIdentifier,
290
+ ResponseMetaType,
291
+ ResponseMetaTypeViaAtom,
292
+ ResponseMetaTypeViaMolecule,
293
+ ResponsePeering,
294
+ ResponsePolicy,
295
+ ResponseProposeMolecule,
296
+ ResponseQueryActiveSession,
297
+ ResponseQueryUserActivity,
298
+ ResponseRequestAuthorization,
299
+ ResponseRequestAuthorizationGuest,
300
+ ResponseRequestTokens,
301
+ ResponseTransferTokens,
302
+ ResponseWalletBundle,
303
+ ResponseWalletList,
88
304
 
89
- // strings
305
+ // String utilities
90
306
  chunkSubstr,
91
307
  base64ToHex,
92
308
  bufferToHexString,
@@ -94,10 +310,18 @@ export {
94
310
  hexStringToBuffer,
95
311
  hexToBase64,
96
312
  isHex,
313
+ isNumeric,
97
314
  randomString,
98
315
 
99
- // crypto
316
+ // Crypto utilities
100
317
  generateSecret,
101
318
  generateBundleHash,
102
- shake256
319
+ generateBatchId,
320
+ shake256,
321
+
322
+ // Array utilities
323
+ chunkArray,
324
+ deepCloning,
325
+ diff,
326
+ intersect
103
327
  }
@@ -63,6 +63,7 @@ import WrongTokenTypeException from './../exception/WrongTokenTypeException.js'
63
63
  import BatchIdException from './../exception/BatchIdException.js'
64
64
  import Atom from './../Atom.js'
65
65
  import Meta from './../Meta.js'
66
+ import Molecule from './../Molecule.js'
66
67
  import Wallet from './../Wallet.js'
67
68
  import Rule from '../instance/Rules/Rule.js'
68
69
  import {
@@ -119,6 +120,8 @@ export default class CheckMolecule {
119
120
  this.isotopeR() &&
120
121
  this.isotopeP() &&
121
122
  this.isotopeA() &&
123
+ this.isotopeB() &&
124
+ this.isotopeF() &&
122
125
  this.isotopeV(senderWallet)
123
126
  }
124
127
 
@@ -384,6 +387,104 @@ export default class CheckMolecule {
384
387
  return true
385
388
  }
386
389
 
390
+ /**
391
+ * Validates B-isotope (Buffer/Exchange) atoms
392
+ *
393
+ * @returns {boolean}
394
+ */
395
+ isotopeB () {
396
+ const isotopeB = this.molecule.getIsotopes('B')
397
+
398
+ if (isotopeB.length === 0) {
399
+ return true
400
+ }
401
+
402
+ for (const atom of isotopeB) {
403
+ // B atoms must reference a wallet bundle
404
+ if (!atom.metaType || atom.metaType !== 'walletBundle') {
405
+ throw new MetaMissingException('Check::isotopeB() - B-isotope atoms must have metaType "walletBundle"!')
406
+ }
407
+
408
+ if (!atom.metaId) {
409
+ throw new MetaMissingException('Check::isotopeB() - B-isotope atoms must have a metaId!')
410
+ }
411
+
412
+ // Value must be parseable as a number
413
+ const value = Number(atom.value)
414
+ if (Number.isNaN(value)) {
415
+ throw new TransferMalformedException('Check::isotopeB() - B-isotope atom value is not a valid number!')
416
+ }
417
+ }
418
+
419
+ // V+B balance conservation: sum of all V and B atom values must equal zero
420
+ const vAtoms = this.molecule.getIsotopes('V')
421
+ if (vAtoms.length > 0) {
422
+ let sum = 0
423
+ for (const atom of [...vAtoms, ...isotopeB]) {
424
+ const value = Number(atom.value)
425
+ if (!Number.isNaN(value)) {
426
+ sum += value
427
+ }
428
+ }
429
+ if (sum !== 0) {
430
+ throw new TransferUnbalancedException('Check::isotopeB() - V+B atom values do not balance to zero!')
431
+ }
432
+ }
433
+
434
+ return true
435
+ }
436
+
437
+ /**
438
+ * Validates F-isotope (Fusion/NFT) atoms
439
+ *
440
+ * @returns {boolean}
441
+ */
442
+ isotopeF () {
443
+ const isotopeF = this.molecule.getIsotopes('F')
444
+
445
+ if (isotopeF.length === 0) {
446
+ return true
447
+ }
448
+
449
+ for (const atom of isotopeF) {
450
+ // F atoms must reference a wallet bundle
451
+ if (!atom.metaType || atom.metaType !== 'walletBundle') {
452
+ throw new MetaMissingException('Check::isotopeF() - F-isotope atoms must have metaType "walletBundle"!')
453
+ }
454
+
455
+ if (!atom.metaId) {
456
+ throw new MetaMissingException('Check::isotopeF() - F-isotope atoms must have a metaId!')
457
+ }
458
+
459
+ // Value must be parseable
460
+ const value = Number(atom.value)
461
+ if (Number.isNaN(value)) {
462
+ throw new TransferMalformedException('Check::isotopeF() - F-isotope atom value is not a valid number!')
463
+ }
464
+
465
+ if (value < 0) {
466
+ throw new TransferMalformedException('Check::isotopeF() - F-isotope atom value must not be negative!')
467
+ }
468
+ }
469
+
470
+ // V+F balance conservation: sum of all V and F atom values must equal zero
471
+ const vAtoms = this.molecule.getIsotopes('V')
472
+ if (vAtoms.length > 0) {
473
+ let sum = 0
474
+ for (const atom of [...vAtoms, ...isotopeF]) {
475
+ const value = Number(atom.value)
476
+ if (!Number.isNaN(value)) {
477
+ sum += value
478
+ }
479
+ }
480
+ if (sum !== 0) {
481
+ throw new TransferUnbalancedException('Check::isotopeF() - V+F atom values do not balance to zero!')
482
+ }
483
+ }
484
+
485
+ return true
486
+ }
487
+
387
488
  /**
388
489
  *
389
490
  * @param senderWallet
@@ -396,9 +497,14 @@ export default class CheckMolecule {
396
497
  return true
397
498
  }
398
499
 
500
+ // When B or F atoms are present, cross-isotope conservation is validated
501
+ // by isotopeB()/isotopeF() — skip V-only conservation check
502
+ const hasCrossIsotope = this.molecule.getIsotopes('B').length > 0 ||
503
+ this.molecule.getIsotopes('F').length > 0
504
+
399
505
  const firstAtom = this.molecule.atoms[0]
400
506
 
401
- if (firstAtom.isotope === 'V' && isotopeV.length === 2) {
507
+ if (!hasCrossIsotope && firstAtom.isotope === 'V' && isotopeV.length === 2) {
402
508
  const endAtom = isotopeV[isotopeV.length - 1]
403
509
 
404
510
  if (firstAtom.token !== endAtom.token) {
@@ -409,6 +515,11 @@ export default class CheckMolecule {
409
515
  throw new TransferMalformedException()
410
516
  }
411
517
 
518
+ // Conservation check for 2-atom transfers
519
+ if ((Number(firstAtom.value) + Number(endAtom.value)) !== 0) {
520
+ throw new TransferUnbalancedException()
521
+ }
522
+
412
523
  return true
413
524
  }
414
525
 
@@ -454,8 +565,8 @@ export default class CheckMolecule {
454
565
  }
455
566
  }
456
567
 
457
- // All atoms must sum to zero for a balanced transaction
458
- if (sum !== 0) {
568
+ // V-only conservation: all V atoms must sum to zero (skip for B/F cross-isotope)
569
+ if (!hasCrossIsotope && sum !== 0) {
459
570
  throw new TransferUnbalancedException()
460
571
  }
461
572
 
@@ -467,7 +578,7 @@ export default class CheckMolecule {
467
578
  throw new TypeError('Invalid isotope "V" values')
468
579
  }
469
580
 
470
- const remainder = senderWallet.balance + value
581
+ const remainder = Number(senderWallet.balance) + value
471
582
 
472
583
  // Is there enough balance to send?
473
584
  if (remainder < 0) {
@@ -475,7 +586,8 @@ export default class CheckMolecule {
475
586
  }
476
587
 
477
588
  // Does the remainder match what should be there in the source wallet, if provided?
478
- if (remainder !== sum) {
589
+ // Skip for cross-isotope (B/F) conservation is validated by isotopeB()/isotopeF()
590
+ if (!hasCrossIsotope && remainder !== sum) {
479
591
  throw new TransferRemainderException()
480
592
  }
481
593
  } else if (value !== 0) {
@@ -578,4 +690,98 @@ export default class CheckMolecule {
578
690
  // Looks like we passed all the tests!
579
691
  return true
580
692
  }
693
+
694
+ /**
695
+ * Converts server-side molecule data (from GraphQL meta query responses)
696
+ * into a Molecule instance suitable for verification via CheckMolecule.
697
+ *
698
+ * Handles field mapping differences between server and client:
699
+ * - tokenSlug → token
700
+ * - metasJson (JSON string) → meta (array of {key, value})
701
+ * - bundleHash → bundle
702
+ *
703
+ * @param {object} serverData - Molecule data from GraphQL response
704
+ * @param {string} serverData.molecularHash
705
+ * @param {string} serverData.bundleHash
706
+ * @param {string|null} serverData.cellSlug
707
+ * @param {string|null} serverData.status
708
+ * @param {string|null} serverData.createdAt
709
+ * @param {array} serverData.atoms - Array of server-format atom objects
710
+ * @return {Molecule}
711
+ */
712
+ static fromServerData ({
713
+ molecularHash,
714
+ bundleHash,
715
+ cellSlug = null,
716
+ status = null,
717
+ createdAt = null,
718
+ atoms = []
719
+ }) {
720
+ const mappedAtoms = atoms.map(serverAtom => {
721
+ let meta = []
722
+ if (serverAtom.metasJson) {
723
+ try {
724
+ const parsed = JSON.parse(serverAtom.metasJson)
725
+ if (Array.isArray(parsed)) {
726
+ // Already in [{key, value}] format
727
+ meta = parsed
728
+ } else if (parsed && typeof parsed === 'object') {
729
+ // Object format {key1: val1, key2: val2} — convert to [{key, value}] pairs
730
+ meta = Object.entries(parsed).map(([key, value]) => ({ key, value }))
731
+ }
732
+ } catch (e) {
733
+ meta = []
734
+ }
735
+ }
736
+
737
+ return {
738
+ position: serverAtom.position || null,
739
+ walletAddress: serverAtom.walletAddress || null,
740
+ isotope: serverAtom.isotope || null,
741
+ token: serverAtom.tokenSlug || serverAtom.token || null,
742
+ value: serverAtom.value != null ? String(serverAtom.value) : null,
743
+ batchId: serverAtom.batchId || null,
744
+ metaType: serverAtom.metaType || null,
745
+ metaId: serverAtom.metaId || null,
746
+ meta,
747
+ index: serverAtom.index != null ? serverAtom.index : null,
748
+ otsFragment: serverAtom.otsFragment || null,
749
+ createdAt: serverAtom.createdAt || null
750
+ }
751
+ })
752
+
753
+ return Molecule.fromJSON({
754
+ molecularHash,
755
+ bundle: bundleHash,
756
+ cellSlug,
757
+ status,
758
+ createdAt,
759
+ atoms: mappedAtoms
760
+ })
761
+ }
762
+
763
+ /**
764
+ * Verifies a molecule reconstructed from server-side GraphQL data.
765
+ * Returns an object with verification result and any error details.
766
+ *
767
+ * @param {object} moleculeData - Server molecule data (same format as fromServerData)
768
+ * @return {{ molecularHash: string, verified: boolean, error: string|null }}
769
+ */
770
+ static verifyFromServerData (moleculeData) {
771
+ try {
772
+ const molecule = CheckMolecule.fromServerData(moleculeData)
773
+ new CheckMolecule(molecule).verify()
774
+ return {
775
+ molecularHash: moleculeData.molecularHash,
776
+ verified: true,
777
+ error: null
778
+ }
779
+ } catch (error) {
780
+ return {
781
+ molecularHash: moleculeData.molecularHash || null,
782
+ verified: false,
783
+ error: error.message || String(error)
784
+ }
785
+ }
786
+ }
581
787
  }
@@ -75,6 +75,14 @@ export default class QueryContinuId extends Query {
75
75
  }`
76
76
  }
77
77
 
78
+ /**
79
+ * Force network-only for ContinuID queries to prevent URQL cache from
80
+ * returning stale wallet positions after mutations advance the ContinuID chain
81
+ */
82
+ createQueryContext () {
83
+ return { requestPolicy: 'network-only' }
84
+ }
85
+
78
86
  /**
79
87
  * Returns a Response object
80
88
  *