@wishknish/knishio-client-ts 0.9.3 → 0.9.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wishknish/knishio-client-ts",
3
- "version": "0.9.3",
3
+ "version": "0.9.4",
4
4
  "productName": "Knish.IO TypeScript SDK Client",
5
5
  "description": "TypeScript implementation of the Knish.IO SDK to consume Knish.IO GraphQL APIs with enhanced type safety.",
6
6
  "license": "GPL-3.0-or-later",
@@ -139,6 +139,10 @@ export default class CheckMolecule {
139
139
  this.isotopeU() &&
140
140
  this.isotopeI() &&
141
141
  this.isotopeR() &&
142
+ this.isotopeP() &&
143
+ this.isotopeA() &&
144
+ this.isotopeB() &&
145
+ this.isotopeF() &&
142
146
  this.isotopeV(senderWallet || null)
143
147
  }
144
148
 
@@ -369,6 +373,172 @@ export default class CheckMolecule {
369
373
  return true
370
374
  }
371
375
 
376
+ /**
377
+ * Validate isotope P atoms (peering)
378
+ * @return True if valid
379
+ * @throws WrongTokenTypeException if the token slug is not USER
380
+ * @throws MetaMissingException if the required peerHost meta field is absent
381
+ */
382
+ isotopeP(): boolean {
383
+ for (const atom of this.molecule.getIsotopes('P')) {
384
+ if (atom.token !== 'USER') {
385
+ throw new WrongTokenTypeException(`Check::isotopeP() - "${atom.token}" is not a valid Token slug for "${atom.isotope}" isotope Atoms!`)
386
+ }
387
+
388
+ const metas = atom.aggregatedMeta()
389
+
390
+ if (!Object.prototype.hasOwnProperty.call(metas, 'peerHost') || !metas.peerHost) {
391
+ throw new MetaMissingException('Check::isotopeP() - Required meta field "peerHost" is missing!')
392
+ }
393
+ }
394
+
395
+ return true
396
+ }
397
+
398
+ /**
399
+ * Validate isotope A atoms (append request)
400
+ * @return True if valid
401
+ * @throws WrongTokenTypeException if the token slug is not USER
402
+ * @throws MetaMissingException if metaType, metaId or the action meta field is absent
403
+ */
404
+ isotopeA(): boolean {
405
+ for (const atom of this.molecule.getIsotopes('A')) {
406
+ if (atom.token !== 'USER') {
407
+ throw new WrongTokenTypeException(`Check::isotopeA() - "${atom.token}" is not a valid Token slug for "${atom.isotope}" isotope Atoms!`)
408
+ }
409
+
410
+ if (!atom.metaType) {
411
+ throw new MetaMissingException('Check::isotopeA() - Required field "metaType" is missing!')
412
+ }
413
+
414
+ if (!atom.metaId) {
415
+ throw new MetaMissingException('Check::isotopeA() - Required field "metaId" is missing!')
416
+ }
417
+
418
+ const metas = atom.aggregatedMeta()
419
+
420
+ if (!Object.prototype.hasOwnProperty.call(metas, 'action') || !metas.action) {
421
+ throw new MetaMissingException('Check::isotopeA() - Required meta field "action" is missing!')
422
+ }
423
+ }
424
+
425
+ return true
426
+ }
427
+
428
+ /**
429
+ * Validate isotope B atoms (buffer/exchange)
430
+ *
431
+ * Buffer molecules are cross-isotope: their V atoms do not balance on their own
432
+ * because a B atom absorbs the difference. isotopeV() therefore skips its V-only
433
+ * conservation checks whenever B (or F) atoms are present, and conservation is
434
+ * enforced here instead over the combined V+B set.
435
+ *
436
+ * @return True if valid
437
+ * @throws MetaMissingException if metaType is not walletBundle, or metaId is absent
438
+ * @throws TransferMalformedException if a B atom value is not a number
439
+ * @throws TransferUnbalancedException if V+B atom values do not sum to zero
440
+ */
441
+ isotopeB(): boolean {
442
+ const isotopeB = this.molecule.getIsotopes('B')
443
+
444
+ if (isotopeB.length === 0) {
445
+ return true
446
+ }
447
+
448
+ for (const atom of isotopeB) {
449
+ // B atoms must reference a wallet bundle
450
+ if (!atom.metaType || atom.metaType !== 'walletBundle') {
451
+ throw new MetaMissingException('Check::isotopeB() - B-isotope atoms must have metaType "walletBundle"!')
452
+ }
453
+
454
+ if (!atom.metaId) {
455
+ throw new MetaMissingException('Check::isotopeB() - B-isotope atoms must have a metaId!')
456
+ }
457
+
458
+ // Value must be parseable as a number
459
+ const value = atom.value !== null ? Number(atom.value) : Number.NaN
460
+ if (Number.isNaN(value)) {
461
+ throw new TransferMalformedException('Check::isotopeB() - B-isotope atom value is not a valid number!')
462
+ }
463
+ }
464
+
465
+ // V+B balance conservation: sum of all V and B atom values must equal zero
466
+ const vAtoms = this.molecule.getIsotopes('V')
467
+ if (vAtoms.length > 0) {
468
+ let sum = 0
469
+ for (const atom of [...vAtoms, ...isotopeB]) {
470
+ const value = atom.value !== null ? Number(atom.value) : Number.NaN
471
+ if (!Number.isNaN(value)) {
472
+ sum += value
473
+ }
474
+ }
475
+ if (sum !== 0) {
476
+ throw new TransferUnbalancedException('Check::isotopeB() - V+B atom values do not balance to zero!')
477
+ }
478
+ }
479
+
480
+ return true
481
+ }
482
+
483
+ /**
484
+ * Validate isotope F atoms (fusion/NFT)
485
+ *
486
+ * Mirrors isotopeB() and additionally forbids negative values. Must stay paired
487
+ * with the hasCrossIsotope gate in isotopeV(), which is keyed on B *or* F: without
488
+ * this check an F-isotope molecule would skip V-only conservation with nothing
489
+ * validating V+F conservation in its place.
490
+ *
491
+ * @return True if valid
492
+ * @throws MetaMissingException if metaType is not walletBundle, or metaId is absent
493
+ * @throws TransferMalformedException if an F atom value is not a number, or is negative
494
+ * @throws TransferUnbalancedException if V+F atom values do not sum to zero
495
+ */
496
+ isotopeF(): boolean {
497
+ const isotopeF = this.molecule.getIsotopes('F')
498
+
499
+ if (isotopeF.length === 0) {
500
+ return true
501
+ }
502
+
503
+ for (const atom of isotopeF) {
504
+ // F atoms must reference a wallet bundle
505
+ if (!atom.metaType || atom.metaType !== 'walletBundle') {
506
+ throw new MetaMissingException('Check::isotopeF() - F-isotope atoms must have metaType "walletBundle"!')
507
+ }
508
+
509
+ if (!atom.metaId) {
510
+ throw new MetaMissingException('Check::isotopeF() - F-isotope atoms must have a metaId!')
511
+ }
512
+
513
+ // Value must be parseable
514
+ const value = atom.value !== null ? Number(atom.value) : Number.NaN
515
+ if (Number.isNaN(value)) {
516
+ throw new TransferMalformedException('Check::isotopeF() - F-isotope atom value is not a valid number!')
517
+ }
518
+
519
+ if (value < 0) {
520
+ throw new TransferMalformedException('Check::isotopeF() - F-isotope atom value must not be negative!')
521
+ }
522
+ }
523
+
524
+ // V+F balance conservation: sum of all V and F atom values must equal zero
525
+ const vAtoms = this.molecule.getIsotopes('V')
526
+ if (vAtoms.length > 0) {
527
+ let sum = 0
528
+ for (const atom of [...vAtoms, ...isotopeF]) {
529
+ const value = atom.value !== null ? Number(atom.value) : Number.NaN
530
+ if (!Number.isNaN(value)) {
531
+ sum += value
532
+ }
533
+ }
534
+ if (sum !== 0) {
535
+ throw new TransferUnbalancedException('Check::isotopeF() - V+F atom values do not balance to zero!')
536
+ }
537
+ }
538
+
539
+ return true
540
+ }
541
+
372
542
  /**
373
543
  * Validate isotope V atoms (value transfers)
374
544
  * @param senderWallet - Optional wallet for balance validation
@@ -382,13 +552,22 @@ export default class CheckMolecule {
382
552
  return true
383
553
  }
384
554
 
555
+ // When B or F atoms are present, cross-isotope conservation is validated
556
+ // by isotopeB()/isotopeF() — skip V-only conservation check.
557
+ //
558
+ // This is deliberately keyed on isotope PRESENCE, not on atom shape. A buffer
559
+ // deposit is [V,B,V] and a withdraw is [B,V,B]; a shape-based test accepts the
560
+ // first and rejects the second, which is exactly the defect this mirrors away.
561
+ const hasCrossIsotope = this.molecule.getIsotopes('B').length > 0 ||
562
+ this.molecule.getIsotopes('F').length > 0
563
+
385
564
  const firstAtom = this.molecule.atoms[0]
386
565
 
387
566
  if (!firstAtom) {
388
567
  throw new AtomsMissingException('Check::isotopeV() - Missing first atom')
389
568
  }
390
569
 
391
- if (firstAtom.isotope === 'V' && isotopeV.length === 2) {
570
+ if (!hasCrossIsotope && firstAtom.isotope === 'V' && isotopeV.length === 2) {
392
571
  const endAtom = isotopeV[isotopeV.length - 1]
393
572
 
394
573
  if (!endAtom) {
@@ -404,6 +583,14 @@ export default class CheckMolecule {
404
583
  throw new TransferMalformedException()
405
584
  }
406
585
 
586
+ // Conservation check for 2-atom transfers. This branch returns early, so
587
+ // without this the general sum check below is never reached and a two-atom
588
+ // V transfer that creates or destroys value is accepted.
589
+ const firstValueForPair = firstAtom.value !== null ? Number(firstAtom.value) : 0
590
+ if ((firstValueForPair + endValue) !== 0) {
591
+ throw new TransferUnbalancedException()
592
+ }
593
+
407
594
  return true
408
595
  }
409
596
 
@@ -449,8 +636,10 @@ export default class CheckMolecule {
449
636
  }
450
637
  }
451
638
 
452
- // All atoms must sum to zero for a balanced transaction
453
- if (sum !== 0) {
639
+ // V-only conservation: all V atoms must sum to zero (skip for B/F cross-isotope,
640
+ // where the balancing atom is a different isotope and conservation is enforced
641
+ // by isotopeB()/isotopeF() instead)
642
+ if (!hasCrossIsotope && sum !== 0) {
454
643
  throw new TransferUnbalancedException()
455
644
  }
456
645
 
@@ -470,7 +659,8 @@ export default class CheckMolecule {
470
659
  }
471
660
 
472
661
  // Does the remainder match what should be there in the source wallet, if provided?
473
- if (remainder !== sum) {
662
+ // Skip for cross-isotope (B/F) — conservation is validated by isotopeB()/isotopeF()
663
+ if (!hasCrossIsotope && remainder !== sum) {
474
664
  throw new TransferRemainderException()
475
665
  }
476
666
  } else if (sum !== 0) {