@wishknish/knishio-client-ts 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.
Files changed (43) hide show
  1. package/dist/index.cjs +2670 -1039
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +336 -105
  4. package/dist/index.d.ts +336 -105
  5. package/dist/index.iife.js +22097 -20047
  6. package/dist/index.iife.js.map +1 -1
  7. package/dist/index.js +2667 -1040
  8. package/dist/index.js.map +1 -1
  9. package/package.json +10 -4
  10. package/src/AuthToken.ts +8 -2
  11. package/src/KnishIOClient.ts +306 -50
  12. package/src/constants.ts +5 -3
  13. package/src/core/Atom.ts +167 -100
  14. package/src/core/AtomMeta.ts +11 -28
  15. package/src/core/Meta.ts +18 -15
  16. package/src/core/Molecule.ts +582 -140
  17. package/src/core/Wallet.ts +19 -24
  18. package/src/index.ts +8 -0
  19. package/src/instance/rules/Rule.ts +128 -19
  20. package/src/libraries/CheckMolecule.ts +88 -1
  21. package/src/libraries/crypto.ts +14 -30
  22. package/src/libraries/strings.ts +26 -25
  23. package/src/mutation/MutationAppendRequest.ts +52 -0
  24. package/src/mutation/MutationCreateRule.ts +2 -2
  25. package/src/mutation/MutationPeering.ts +37 -0
  26. package/src/mutation/MutationRequestAuthorization.ts +3 -3
  27. package/src/query/QueryActiveSession.ts +0 -1
  28. package/src/query/QueryContinuId.ts +8 -0
  29. package/src/query/QueryMetaTypeViaMolecule.ts +221 -0
  30. package/src/query/QueryWalletList.ts +2 -2
  31. package/src/response/ResponseAppendRequest.ts +17 -0
  32. package/src/response/ResponseBalance.ts +8 -3
  33. package/src/response/ResponseContinuId.ts +1 -1
  34. package/src/response/ResponseMetaType.ts +32 -19
  35. package/src/response/ResponseMetaTypeViaMolecule.ts +224 -0
  36. package/src/response/ResponsePeering.ts +17 -0
  37. package/src/response/ResponsePolicy.ts +2 -3
  38. package/src/response/ResponseProposeMolecule.ts +0 -1
  39. package/src/response/ResponseRequestAuthorization.ts +32 -2
  40. package/src/response/ResponseRequestAuthorizationGuest.ts +25 -1
  41. package/src/response/ResponseWalletList.ts +1 -1
  42. package/src/types/guards.ts +18 -5
  43. package/src/types/index.ts +3 -3
@@ -49,9 +49,11 @@ License: https://github.com/WishKnish/KnishIO-Client-TS/blob/master/LICENSE
49
49
  import Atom from './Atom'
50
50
  import AtomMeta from './AtomMeta'
51
51
  import Wallet from './Wallet'
52
+ import Rule from '@/instance/rules/Rule'
52
53
  import CheckMolecule from '@/libraries/CheckMolecule'
53
54
  import { chunkSubstr, hexToBase64 } from '@/libraries/strings'
54
- import { generateBundleHash, generateOTSSignature } from '@/libraries/crypto'
55
+ import { generateBundleHash, generateBatchId, generateOTSSignature } from '@/libraries/crypto'
56
+ import versions from '@/versions'
55
57
  import {
56
58
  AtomsMissingException,
57
59
  BalanceInsufficientException,
@@ -75,7 +77,8 @@ export default class Molecule {
75
77
  public sourceWallet: Wallet | null
76
78
  public remainderWallet: Wallet | null
77
79
  public atoms: Atom[]
78
- public version: string | null
80
+ public version: string | null | undefined
81
+ public parentHashes: string[]
79
82
  public local?: number
80
83
 
81
84
  /**
@@ -105,12 +108,15 @@ export default class Molecule {
105
108
  this.bundle = bundle
106
109
  this.sourceWallet = sourceWallet
107
110
  this.atoms = []
108
-
109
- if (version !== null) {
111
+ this.parentHashes = []
112
+
113
+ const versionRegistry = versions as Record<string | number, any>
114
+ if (version !== null && Object.prototype.hasOwnProperty.call(versionRegistry, version)) {
110
115
  this.version = String(version)
111
- } else {
112
- this.version = null
113
116
  }
117
+ // Only set version if it's a recognized version in the registry (matching JS SDK).
118
+ // When version is not recognized, this.version stays undefined.
119
+ // JSON.stringify omits undefined properties, so the server won't see a version field.
114
120
 
115
121
  // Set the remainder wallet for this transaction
116
122
  if (remainderWallet || sourceWallet) {
@@ -149,7 +155,7 @@ export default class Molecule {
149
155
  if (!Array.isArray(isotopes)) {
150
156
  isotopes = [isotopes]
151
157
  }
152
- return atoms.filter(atom => isotopes.includes(atom.isotope))
158
+ return atoms.filter(atom => atom.isotope !== null && isotopes.includes(atom.isotope))
153
159
  }
154
160
 
155
161
  /**
@@ -176,7 +182,7 @@ export default class Molecule {
176
182
 
177
183
  // Set atom's index
178
184
  atom.index = this.generateIndex()
179
- atom.version = this.version ? Number(this.version) : 4
185
+ atom.version = this.version ?? undefined
180
186
 
181
187
  // Add atom
182
188
  this.atoms.push(atom)
@@ -189,19 +195,74 @@ export default class Molecule {
189
195
 
190
196
  /**
191
197
  * Add user remainder atom for ContinuID
198
+ * Matches JavaScript SDK addContinuIdAtom exactly
192
199
  */
193
200
  addContinuIdAtom(): Molecule {
194
- if (!this.remainderWallet) {
195
- throw new Error('Remainder wallet required for ContinuID atom')
201
+ // If remainder wallet is not USER token, create a new USER remainder wallet
202
+ if (!this.remainderWallet || this.remainderWallet.token !== 'USER') {
203
+ this.remainderWallet = Wallet.create({
204
+ secret: this.secret!,
205
+ bundle: this.bundle!
206
+ })
207
+ }
208
+
209
+ // ContinuID metadata for chain integrity validation
210
+ const continuIdMeta: Record<string, any> = {}
211
+
212
+ // previousPosition: the source wallet's position being consumed
213
+ if (this.sourceWallet && this.sourceWallet.position) {
214
+ continuIdMeta.previousPosition = this.sourceWallet.position
215
+ }
216
+
217
+ // pubkey: ML-KEM public key for encrypted communication
218
+ if (this.remainderWallet.pubkey) {
219
+ continuIdMeta.pubkey = this.remainderWallet.pubkey
196
220
  }
197
221
 
198
- this.addAtom(new Atom({
222
+ // characters: encoding format for the wallet
223
+ if (this.remainderWallet.characters) {
224
+ continuIdMeta.characters = this.remainderWallet.characters
225
+ }
226
+
227
+ this.addAtom(Atom.create({
199
228
  isotope: 'I',
200
- position: this.remainderWallet.position!,
201
- walletAddress: this.remainderWallet.address! as any,
202
- token: 'USER',
229
+ wallet: this.remainderWallet!,
203
230
  metaType: 'walletBundle',
204
- metaId: this.remainderWallet.bundle!
231
+ metaId: this.remainderWallet!.bundle!,
232
+ meta: new AtomMeta(continuIdMeta)
233
+ }))
234
+ return this
235
+ }
236
+
237
+ /**
238
+ * Add a policy R-isotope atom (matching JS SDK)
239
+ */
240
+ addPolicyAtom({
241
+ metaType,
242
+ metaId,
243
+ meta = {},
244
+ policy = {}
245
+ }: {
246
+ metaType: string
247
+ metaId: string
248
+ meta?: any
249
+ policy?: Record<string, any>
250
+ }): Molecule {
251
+ const atomMeta = new AtomMeta(meta)
252
+ atomMeta.addPolicy(policy)
253
+
254
+ const wallet = Wallet.create({
255
+ secret: this.secret!,
256
+ bundle: this.sourceWallet!.bundle!,
257
+ token: 'USER'
258
+ })
259
+
260
+ this.addAtom(Atom.create({
261
+ wallet,
262
+ isotope: 'R',
263
+ metaType,
264
+ metaId,
265
+ meta: atomMeta
205
266
  }))
206
267
  return this
207
268
  }
@@ -221,43 +282,31 @@ export default class Molecule {
221
282
  throw new Error('Source wallet required for value transfer')
222
283
  }
223
284
 
224
- if (this.sourceWallet.balance - amount < 0) {
285
+ if (Number(this.sourceWallet.balance) - amount < 0) {
225
286
  throw new BalanceInsufficientException()
226
287
  }
227
288
 
228
- // Initializing a new Atom to remove entire balance from source (UTXO model)
229
- this.addAtom(new Atom({
289
+ // Initializing a new Atom to remove tokens from source (debit full balance)
290
+ this.addAtom(Atom.create({
230
291
  isotope: 'V',
231
- position: this.sourceWallet.position!,
232
- walletAddress: this.sourceWallet.address! as any,
233
- token: this.sourceWallet.token,
234
- value: -this.sourceWallet.balance
292
+ wallet: this.sourceWallet,
293
+ value: -Number(this.sourceWallet.balance)
235
294
  }))
236
-
237
295
  // Initializing a new Atom to add tokens to recipient
238
- this.addAtom(new Atom({
296
+ this.addAtom(Atom.create({
239
297
  isotope: 'V',
240
- position: recipientWallet.position!,
241
- walletAddress: recipientWallet.address! as any,
242
- token: recipientWallet.token,
298
+ wallet: recipientWallet,
243
299
  value: amount,
244
300
  metaType: 'walletBundle',
245
301
  metaId: recipientWallet.bundle!
246
302
  }))
247
-
248
- // Initializing a remainder atom (matching JavaScript SDK - uses existing remainderWallet)
249
- if (!this.remainderWallet) {
250
- throw new Error('Remainder wallet required for value transfer')
251
- }
252
-
253
- this.addAtom(new Atom({
303
+ // Initializing a remainder atom
304
+ this.addAtom(Atom.create({
254
305
  isotope: 'V',
255
- position: this.remainderWallet.position!,
256
- walletAddress: this.remainderWallet.address! as any,
257
- token: this.remainderWallet.token,
258
- value: this.sourceWallet.balance - amount,
306
+ wallet: this.remainderWallet!,
307
+ value: Number(this.sourceWallet.balance) - amount,
259
308
  metaType: 'walletBundle',
260
- metaId: this.remainderWallet.bundle!
309
+ metaId: this.remainderWallet!.bundle!
261
310
  }))
262
311
 
263
312
  return this
@@ -300,6 +349,21 @@ export default class Molecule {
300
349
  // Set signing position from the first atom
301
350
  let signingPosition = signingAtom.position
302
351
 
352
+ // Get signing wallet from first atom's metas (matching JS SDK exactly)
353
+ // This handles buffer withdraw reconciliation where a different wallet signs
354
+ const signingWalletMeta = signingAtom.aggregatedMeta ? signingAtom.aggregatedMeta() : null
355
+ const signingWalletJson = signingWalletMeta?.signingWallet as string | undefined
356
+ if (signingWalletJson) {
357
+ try {
358
+ const parsedSigningWallet = JSON.parse(signingWalletJson)
359
+ if (parsedSigningWallet.position) {
360
+ signingPosition = parsedSigningWallet.position
361
+ }
362
+ } catch {
363
+ // Invalid JSON in signingWallet meta, use default position
364
+ }
365
+ }
366
+
303
367
  // Signing position is required
304
368
  if (!signingPosition) {
305
369
  throw new SignatureMalformedException('Signing wallet must have a position!')
@@ -448,7 +512,7 @@ export default class Molecule {
448
512
  secureMode?: boolean
449
513
  } = {}): any {
450
514
  const {
451
- includeValidationContext = true,
515
+ includeValidationContext = false,
452
516
  includeOtsFragments = true,
453
517
  secureMode = false
454
518
  } = options
@@ -459,30 +523,37 @@ export default class Molecule {
459
523
  throw new Error('Cannot serialize molecule with secret in secure mode')
460
524
  }
461
525
 
462
- // Core molecule properties (always included)
526
+ // Core molecule properties (server-compatible fields only)
463
527
  const serialized: any = {
464
528
  status: this.status,
465
529
  molecularHash: this.molecularHash,
466
530
  createdAt: this.createdAt,
467
531
  cellSlug: this.cellSlug,
468
- cellSlugOrigin: this.cellSlugOrigin,
469
- version: this.version,
470
532
  bundle: this.bundle,
471
-
533
+
472
534
  // Serialized atoms array with optional OTS fragments
473
- atoms: this.atoms.map(atom => atom.toJSON({
474
- includeOtsFragments
535
+ atoms: this.atoms.map(atom => atom.toJSON({
536
+ includeOtsFragments
475
537
  }))
476
538
  }
477
539
 
478
- // Validation context (essential for cross-SDK validation)
540
+ // Parent molecular hashes for DAG linkage (only include when non-empty
541
+ // to maintain backward compatibility with servers that don't support it)
542
+ if (this.parentHashes && this.parentHashes.length > 0) {
543
+ serialized.parentHashes = this.parentHashes
544
+ }
545
+
546
+ // Extended context for Rust validator and local validation
479
547
  if (includeValidationContext) {
548
+ serialized.cellSlugOrigin = this.cellSlugOrigin
549
+ serialized.version = this.version
550
+
480
551
  if (this.sourceWallet) {
481
552
  serialized.sourceWallet = {
482
553
  address: this.sourceWallet.address,
483
554
  position: this.sourceWallet.position,
484
555
  token: this.sourceWallet.token,
485
- balance: this.sourceWallet.balance || 0,
556
+ balance: this.sourceWallet.balance || '0',
486
557
  bundle: this.sourceWallet.bundle,
487
558
  batchId: this.sourceWallet.batchId || null,
488
559
  characters: this.sourceWallet.characters || 'BASE64',
@@ -499,7 +570,7 @@ export default class Molecule {
499
570
  address: this.remainderWallet.address,
500
571
  position: this.remainderWallet.position,
501
572
  token: this.remainderWallet.token,
502
- balance: this.remainderWallet.balance || 0,
573
+ balance: this.remainderWallet.balance || '0',
503
574
  bundle: this.remainderWallet.bundle,
504
575
  batchId: this.remainderWallet.batchId || null,
505
576
  characters: this.remainderWallet.characters || 'BASE64',
@@ -532,6 +603,7 @@ export default class Molecule {
532
603
 
533
604
  /**
534
605
  * Initialize token creation
606
+ * Matches JavaScript SDK initTokenCreation implementation
535
607
  */
536
608
  initTokenCreation({
537
609
  recipientWallet,
@@ -542,32 +614,28 @@ export default class Molecule {
542
614
  amount: number
543
615
  meta: any
544
616
  }): Molecule {
545
- // Create a new token by adding atoms
546
- this.addAtom(new Atom({
547
- isotope: 'T',
548
- position: recipientWallet.position!,
549
- walletAddress: recipientWallet.address! as any,
550
- token: recipientWallet.token,
617
+ if (!this.sourceWallet) {
618
+ throw new Error('Source wallet required for token creation')
619
+ }
620
+
621
+ // Build meta using AtomMeta class (matching JavaScript SDK exactly)
622
+ const atomMeta = new AtomMeta(meta)
623
+ atomMeta.setMetaWallet(recipientWallet)
624
+
625
+ // The primary atom tells the ledger that a certain amount of the new token is being issued
626
+ this.addAtom(Atom.create({
627
+ isotope: 'C',
628
+ wallet: this.sourceWallet,
551
629
  value: amount,
552
630
  metaType: 'token',
553
631
  metaId: recipientWallet.token,
554
- meta: meta ? [{ key: 'tokenMeta', value: JSON.stringify(meta) }] : []
632
+ meta: atomMeta,
633
+ batchId: recipientWallet.batchId
555
634
  }))
556
-
557
- // Add wallet bundle metadata
558
- this.addAtom(new Atom({
559
- isotope: 'M',
560
- position: recipientWallet.position!,
561
- walletAddress: recipientWallet.address! as any,
562
- token: recipientWallet.token,
563
- metaType: 'walletBundle',
564
- metaId: recipientWallet.bundle!,
565
- meta: meta ? [{ key: 'tokenMeta', value: JSON.stringify(meta) }] : []
566
- }))
567
-
635
+
568
636
  // Add ContinuID atom (matching JavaScript SDK)
569
637
  this.addContinuIdAtom()
570
-
638
+
571
639
  return this
572
640
  }
573
641
 
@@ -576,7 +644,7 @@ export default class Molecule {
576
644
  */
577
645
  burnToken({
578
646
  amount,
579
- walletBundle = null
647
+ walletBundle: _walletBundle = null
580
648
  }: {
581
649
  amount: number
582
650
  walletBundle?: string | null
@@ -585,25 +653,52 @@ export default class Molecule {
585
653
  throw new Error('Source wallet required for token burning')
586
654
  }
587
655
 
588
- if (amount <= 0) {
589
- throw new NegativeAmountException()
656
+ if (amount < 0) {
657
+ throw new NegativeAmountException('Molecule::burnToken() - Amount to burn must be positive!')
590
658
  }
591
-
592
- if (this.sourceWallet.balance - amount < 0) {
659
+
660
+ if (Number(this.sourceWallet.balance) - amount < 0) {
593
661
  throw new BalanceInsufficientException()
594
662
  }
595
-
596
- // Burn tokens by removing from source
597
- this.addAtom(new Atom({
663
+
664
+ // Burn tokens by removing from source (matching JS SDK)
665
+ this.addAtom(Atom.create({
598
666
  isotope: 'V',
599
- position: this.sourceWallet.position!,
600
- walletAddress: this.sourceWallet.address! as any,
601
- token: this.sourceWallet.token,
602
- value: -amount,
603
- metaType: walletBundle ? 'walletBundle' : null,
604
- metaId: walletBundle || null
667
+ wallet: this.sourceWallet,
668
+ value: -amount
605
669
  }))
606
-
670
+ // Remainder atom (matching JS SDK)
671
+ this.addAtom(Atom.create({
672
+ isotope: 'V',
673
+ wallet: this.remainderWallet!,
674
+ value: Number(this.sourceWallet.balance) - amount,
675
+ metaType: 'walletBundle',
676
+ metaId: this.remainderWallet!.bundle!
677
+ }))
678
+
679
+ return this
680
+ }
681
+
682
+ /**
683
+ * Initialize authorization request
684
+ * Creates U-isotope (authorization) atom for requesting auth token
685
+ * Matches JavaScript SDK Molecule.initAuthorization
686
+ */
687
+ initAuthorization({ meta }: { meta: Record<string, any> }): Molecule {
688
+ if (!this.sourceWallet) {
689
+ throw new Error('Source wallet required for authorization')
690
+ }
691
+
692
+ // Create U-isotope atom for authorization (matching JS SDK)
693
+ this.addAtom(Atom.create({
694
+ isotope: 'U',
695
+ wallet: this.sourceWallet,
696
+ meta: new AtomMeta(meta)
697
+ }))
698
+
699
+ // Add ContinuID atom (matching JavaScript SDK)
700
+ this.addContinuIdAtom()
701
+
607
702
  return this
608
703
  }
609
704
 
@@ -625,78 +720,171 @@ export default class Molecule {
625
720
  throw new Error('Source wallet required for meta creation')
626
721
  }
627
722
 
628
- const metaArray: Array<{ key: string; value: any }> = []
629
-
630
- // Convert meta to array format
631
- if (meta) {
632
- if (Array.isArray(meta)) {
633
- metaArray.push(...meta)
634
- } else if (typeof meta === 'object') {
635
- for (const [key, value] of Object.entries(meta)) {
636
- metaArray.push({ key, value })
637
- }
638
- } else {
639
- metaArray.push({ key: 'value', value: meta })
640
- }
641
- }
642
-
643
- // Add policy if provided
644
- if (policy) {
645
- metaArray.push({ key: 'policy', value: JSON.stringify(policy) })
646
- }
647
-
648
- // Create meta atom
649
- this.addAtom(new Atom({
723
+ // Initializing a new Atom to hold our metadata (matching JS SDK)
724
+ this.addAtom(Atom.create({
650
725
  isotope: 'M',
651
- position: this.sourceWallet.position!,
652
- walletAddress: this.sourceWallet.address! as any,
653
- token: this.sourceWallet.token,
726
+ wallet: this.sourceWallet,
654
727
  metaType,
655
728
  metaId,
656
- meta: metaArray
729
+ meta: new AtomMeta(meta)
657
730
  }))
658
-
659
- // Add ContinuID atom (matching JavaScript SDK)
731
+
732
+ // Only add policy atom if policy is provided and not empty
733
+ if (policy && Object.keys(policy).length > 0) {
734
+ this.addPolicyAtom({
735
+ metaType,
736
+ metaId,
737
+ meta,
738
+ policy
739
+ })
740
+ }
741
+
742
+ // User remainder atom
660
743
  this.addContinuIdAtom()
661
-
744
+
662
745
  return this
663
746
  }
664
747
 
665
748
  /**
666
749
  * Initialize wallet creation
750
+ * Uses source wallet for atom properties (matching JavaScript SDK)
751
+ * The wallet being created is stored in metadata
667
752
  */
668
753
  initWalletCreation(wallet: Wallet, atomMeta: AtomMeta | null = null): Molecule {
669
- // Create wallet creation atom
670
- const atom = new Atom({
754
+ if (!this.sourceWallet) {
755
+ throw new Error('Source wallet required for wallet creation')
756
+ }
757
+
758
+ // Matching JS SDK: use AtomMeta.setMetaWallet() for wallet metadata
759
+ if (!atomMeta) {
760
+ atomMeta = new AtomMeta()
761
+ }
762
+ atomMeta.setMetaWallet(wallet)
763
+
764
+ const creationAtom = Atom.create({
671
765
  isotope: 'C',
672
- position: wallet.position!,
673
- walletAddress: wallet.address! as any,
674
- token: wallet.token,
766
+ wallet: this.sourceWallet,
675
767
  metaType: 'wallet',
676
768
  metaId: wallet.address!,
677
- meta: atomMeta ? atomMeta.get() as any : []
769
+ meta: atomMeta,
770
+ batchId: wallet.batchId
678
771
  })
679
-
680
- // Add wallet metadata
681
- if (!atomMeta) {
682
- atom.meta.push(
683
- { key: 'position', value: wallet.position! },
684
- { key: 'bundle', value: wallet.bundle! },
685
- { key: 'token', value: wallet.token },
686
- { key: 'batchId', value: wallet.batchId || '' },
687
- { key: 'characters', value: wallet.characters || '' }
688
- )
689
-
690
- if (wallet.pubkey) {
691
- atom.meta.push({ key: 'pubkey', value: wallet.pubkey })
692
- }
772
+ this.addAtom(creationAtom)
773
+ this.addContinuIdAtom()
774
+
775
+ return this
776
+ }
777
+
778
+ /**
779
+ * Creates atoms for rule definition
780
+ * Matches JavaScript SDK Molecule.createRule method
781
+ *
782
+ * @param metaType - The meta type for the rule
783
+ * @param metaId - The meta ID for the rule
784
+ * @param rule - Array of Rule objects or rule data
785
+ * @param policy - Optional policy object
786
+ * @return This molecule instance for chaining
787
+ */
788
+ createRule({
789
+ metaType,
790
+ metaId,
791
+ rule,
792
+ policy = {}
793
+ }: {
794
+ metaType: string
795
+ metaId: string
796
+ rule: Rule[] | any[] | any // Accept single object or array
797
+ policy?: Record<string, any>
798
+ }): Molecule {
799
+ if (!this.sourceWallet) {
800
+ throw new Error('Source wallet required for createRule')
693
801
  }
694
-
695
- this.addAtom(atom)
696
-
697
- // Add ContinuID atom (matching JavaScript SDK)
802
+
803
+ // Normalize rule to array (accept single object or array)
804
+ const ruleArray = Array.isArray(rule) ? rule : [rule]
805
+
806
+ // Convert rules to objects (matching JavaScript SDK)
807
+ const rules: Record<string, any>[] = []
808
+ for (const r of ruleArray) {
809
+ rules.push(r instanceof Rule ? r : Rule.toObject(r))
810
+ }
811
+
812
+ // Create atom meta with rules
813
+ const atomMeta = new AtomMeta({
814
+ rule: JSON.stringify(rules)
815
+ })
816
+
817
+ // Add policies to meta object only if policy exists and has keys (matching JS SDK)
818
+ if (policy && Object.keys(policy).length > 0) {
819
+ atomMeta.addPolicy(policy)
820
+ }
821
+
822
+ // Create R-isotope atom for rule (matching JS SDK)
823
+ this.addAtom(Atom.create({
824
+ isotope: 'R',
825
+ wallet: this.sourceWallet,
826
+ metaType,
827
+ metaId,
828
+ meta: atomMeta
829
+ }))
830
+
831
+ // Add ContinuID atom
698
832
  this.addContinuIdAtom()
699
-
833
+
834
+ return this
835
+ }
836
+
837
+ /**
838
+ * Initializes a token request molecule
839
+ * Matches JavaScript SDK Molecule.initTokenRequest method
840
+ *
841
+ * @param token - Token slug to request
842
+ * @param amount - Amount of tokens to request
843
+ * @param metaType - Meta type (typically 'walletBundle')
844
+ * @param metaId - Meta ID (typically recipient bundle hash)
845
+ * @param meta - Optional additional metadata
846
+ * @param batchId - Optional batch ID
847
+ * @return This molecule instance for chaining
848
+ */
849
+ initTokenRequest({
850
+ token,
851
+ amount,
852
+ metaType,
853
+ metaId,
854
+ meta = {},
855
+ batchId = null
856
+ }: {
857
+ token: string
858
+ amount: number
859
+ metaType: string
860
+ metaId: string
861
+ meta?: Record<string, any>
862
+ batchId?: string | null
863
+ }): Molecule {
864
+ if (!this.sourceWallet) {
865
+ throw new Error('Source wallet required for initTokenRequest')
866
+ }
867
+
868
+ // Add token and amount to meta (matching JavaScript SDK exactly)
869
+ const metaWithToken = { ...meta, token, amount: String(amount) }
870
+
871
+ // Mark as local molecule
872
+ this.local = 1
873
+
874
+ // Create T-isotope atom for token request (matching JS SDK)
875
+ this.addAtom(Atom.create({
876
+ isotope: 'T',
877
+ wallet: this.sourceWallet,
878
+ value: amount,
879
+ metaType,
880
+ metaId,
881
+ meta: new AtomMeta(metaWithToken),
882
+ batchId
883
+ }))
884
+
885
+ // Add ContinuID atom
886
+ this.addContinuIdAtom()
887
+
700
888
  return this
701
889
  }
702
890
 
@@ -761,6 +949,9 @@ export default class Molecule {
761
949
  })
762
950
  }
763
951
 
952
+ // Reconstruct parent hashes for DAG linkage
953
+ molecule.parentHashes = Array.isArray(data.parentHashes) ? [...data.parentHashes] : []
954
+
764
955
  // Reconstruct validation context if available and requested
765
956
  if (includeValidationContext) {
766
957
  if (data.sourceWallet) {
@@ -775,7 +966,7 @@ export default class Molecule {
775
966
  })
776
967
 
777
968
  // Set additional properties for validation context
778
- molecule.sourceWallet.balance = data.sourceWallet.balance || 0
969
+ molecule.sourceWallet.balance = String(data.sourceWallet.balance != null ? data.sourceWallet.balance : 0)
779
970
  molecule.sourceWallet.address = data.sourceWallet.address as any
780
971
  if (data.sourceWallet.pubkey) {
781
972
  molecule.sourceWallet.pubkey = data.sourceWallet.pubkey
@@ -797,7 +988,7 @@ export default class Molecule {
797
988
  })
798
989
 
799
990
  // Set additional properties for validation context
800
- molecule.remainderWallet.balance = data.remainderWallet.balance || 0
991
+ molecule.remainderWallet.balance = String(data.remainderWallet.balance != null ? data.remainderWallet.balance : 0)
801
992
  molecule.remainderWallet.address = data.remainderWallet.address as any
802
993
  if (data.remainderWallet.pubkey) {
803
994
  molecule.remainderWallet.pubkey = data.remainderWallet.pubkey
@@ -821,4 +1012,255 @@ export default class Molecule {
821
1012
  static jsonToObject(json: string): Molecule {
822
1013
  return Molecule.fromJSON(json)
823
1014
  }
1015
+
1016
+ // =============================================================================
1017
+ // ISOTOPE INIT METHODS - MATCHING JAVASCRIPT SDK PARITY
1018
+ // =============================================================================
1019
+
1020
+ /**
1021
+ * Initialize a P-type molecule for peer registration
1022
+ * Matches JavaScript SDK Molecule.initPeering implementation exactly
1023
+ *
1024
+ * @param host - The peer host URL to register
1025
+ * @return This molecule instance for chaining
1026
+ */
1027
+ initPeering({ host }: { host: string }): Molecule {
1028
+ if (!this.sourceWallet) {
1029
+ throw new Error('Source wallet required for peer registration')
1030
+ }
1031
+
1032
+ this.addAtom(Atom.create({
1033
+ isotope: 'P',
1034
+ wallet: this.sourceWallet,
1035
+ metaType: 'walletBundle',
1036
+ metaId: this.bundle!,
1037
+ meta: new AtomMeta({ peerHost: host })
1038
+ }))
1039
+
1040
+ this.addContinuIdAtom()
1041
+
1042
+ return this
1043
+ }
1044
+
1045
+ /**
1046
+ * Initialize an A-type molecule for an append request
1047
+ * Matches JavaScript SDK Molecule.initAppendRequest implementation exactly
1048
+ *
1049
+ * @param metaType - The target MetaType to append to
1050
+ * @param metaId - The target MetaId to append to
1051
+ * @param action - The action to perform
1052
+ * @param meta - Additional metadata
1053
+ * @return This molecule instance for chaining
1054
+ */
1055
+ initAppendRequest({
1056
+ metaType,
1057
+ metaId,
1058
+ action,
1059
+ meta = {}
1060
+ }: {
1061
+ metaType: string
1062
+ metaId: string
1063
+ action: string
1064
+ meta?: Record<string, any>
1065
+ }): Molecule {
1066
+ if (!this.sourceWallet) {
1067
+ throw new Error('Source wallet required for append request')
1068
+ }
1069
+
1070
+ this.addAtom(Atom.create({
1071
+ isotope: 'A',
1072
+ wallet: this.sourceWallet,
1073
+ metaType,
1074
+ metaId,
1075
+ meta: new AtomMeta({ action, ...meta })
1076
+ }))
1077
+
1078
+ this.addContinuIdAtom()
1079
+
1080
+ return this
1081
+ }
1082
+
1083
+ /**
1084
+ * Initialize a deposit buffer operation (B-isotope)
1085
+ * Matches JavaScript SDK Molecule.initDepositBuffer implementation exactly
1086
+ *
1087
+ * @param amount - Amount to deposit into buffer
1088
+ * @param tradeRates - Trade rates for the buffer wallet
1089
+ * @return This molecule instance for chaining
1090
+ */
1091
+ initDepositBuffer({
1092
+ amount,
1093
+ tradeRates
1094
+ }: {
1095
+ amount: number
1096
+ tradeRates?: Record<string, any>
1097
+ }): Molecule {
1098
+ if (!this.sourceWallet) {
1099
+ throw new Error('Source wallet required for buffer deposit')
1100
+ }
1101
+
1102
+ if (Number(this.sourceWallet.balance) - amount < 0) {
1103
+ throw new BalanceInsufficientException()
1104
+ }
1105
+
1106
+ // Create a buffer wallet
1107
+ const bufferWallet = Wallet.create({
1108
+ secret: this.secret!,
1109
+ bundle: this.bundle,
1110
+ token: this.sourceWallet.token,
1111
+ batchId: this.sourceWallet.batchId
1112
+ })
1113
+ if (tradeRates) {
1114
+ bufferWallet.tradeRates = tradeRates
1115
+ }
1116
+
1117
+ // Debit full balance from source (V-isotope)
1118
+ this.addAtom(Atom.create({
1119
+ isotope: 'V',
1120
+ wallet: this.sourceWallet,
1121
+ value: -Number(this.sourceWallet.balance)
1122
+ }))
1123
+
1124
+ // Deposit amount to buffer (B-isotope)
1125
+ this.addAtom(Atom.create({
1126
+ isotope: 'B',
1127
+ wallet: bufferWallet,
1128
+ value: amount,
1129
+ metaType: 'walletBundle',
1130
+ metaId: this.sourceWallet.bundle!
1131
+ }))
1132
+
1133
+ // Remainder back to source
1134
+ this.addAtom(Atom.create({
1135
+ isotope: 'V',
1136
+ wallet: this.remainderWallet!,
1137
+ value: Number(this.sourceWallet.balance) - amount,
1138
+ metaType: 'walletBundle',
1139
+ metaId: this.sourceWallet.bundle!
1140
+ }))
1141
+
1142
+ return this
1143
+ }
1144
+
1145
+ /**
1146
+ * Initialize a withdraw buffer operation (B-isotope)
1147
+ * Matches JavaScript SDK Molecule.initWithdrawBuffer implementation exactly
1148
+ *
1149
+ * @param recipients - Map of recipientBundle → amount
1150
+ * @param signingWallet - Optional signing wallet for reconciliation
1151
+ * @return This molecule instance for chaining
1152
+ */
1153
+ initWithdrawBuffer({
1154
+ recipients,
1155
+ signingWallet = null
1156
+ }: {
1157
+ recipients: Record<string, number>
1158
+ signingWallet?: any | null
1159
+ }): Molecule {
1160
+ if (!this.sourceWallet) {
1161
+ throw new Error('Source wallet required for buffer withdrawal')
1162
+ }
1163
+
1164
+ // Calculate total from all recipients
1165
+ let amount = 0
1166
+ for (const recipientAmount of Object.values(recipients || {})) {
1167
+ amount += recipientAmount
1168
+ }
1169
+
1170
+ if (Number(this.sourceWallet.balance) - amount < 0) {
1171
+ throw new BalanceInsufficientException()
1172
+ }
1173
+
1174
+ // Set a meta signing position for molecule correct reconciliation
1175
+ const firstAtomMeta = new AtomMeta()
1176
+ if (signingWallet) {
1177
+ firstAtomMeta.setSigningWallet(signingWallet)
1178
+ }
1179
+
1180
+ // Debit full balance from source (B-isotope)
1181
+ this.addAtom(Atom.create({
1182
+ isotope: 'B',
1183
+ wallet: this.sourceWallet,
1184
+ value: -Number(this.sourceWallet.balance),
1185
+ meta: firstAtomMeta,
1186
+ metaType: 'walletBundle',
1187
+ metaId: this.sourceWallet.bundle!
1188
+ }))
1189
+
1190
+ // Add recipient V-atoms (matching JS SDK: uses `new Atom()` with explicit token)
1191
+ for (const [recipientBundle, recipientAmount] of Object.entries(recipients || {})) {
1192
+ this.addAtom(new Atom({
1193
+ isotope: 'V',
1194
+ token: this.sourceWallet.token,
1195
+ value: recipientAmount as number,
1196
+ batchId: this.sourceWallet.batchId ? generateBatchId({}) : null,
1197
+ metaType: 'walletBundle',
1198
+ metaId: recipientBundle
1199
+ }))
1200
+ }
1201
+
1202
+ // Remainder back to buffer
1203
+ this.addAtom(Atom.create({
1204
+ isotope: 'B',
1205
+ wallet: this.remainderWallet!,
1206
+ value: Number(this.sourceWallet.balance) - amount,
1207
+ metaType: 'walletBundle',
1208
+ metaId: this.remainderWallet!.bundle!
1209
+ }))
1210
+
1211
+ return this
1212
+ }
1213
+
1214
+ /**
1215
+ * Initialize shadow wallet claim
1216
+ * Matches JavaScript SDK Molecule.initShadowWalletClaim implementation exactly
1217
+ *
1218
+ * @param wallet - The shadow wallet to claim
1219
+ * @return This molecule instance for chaining
1220
+ */
1221
+ initShadowWalletClaim(wallet: Wallet): Molecule {
1222
+ const atomMeta = new AtomMeta()
1223
+ atomMeta.setShadowWalletClaim(true)
1224
+ return this.initWalletCreation(wallet, atomMeta)
1225
+ }
1226
+
1227
+ /**
1228
+ * Initialize identifier creation
1229
+ * Matches JavaScript SDK Molecule.initIdentifierCreation implementation exactly
1230
+ *
1231
+ * @param type - Identifier type (phone or email)
1232
+ * @param contact - Phone number or email string
1233
+ * @param code - Verification code
1234
+ * @return This molecule instance for chaining
1235
+ */
1236
+ initIdentifierCreation({
1237
+ type,
1238
+ contact,
1239
+ code
1240
+ }: {
1241
+ type: string
1242
+ contact: string
1243
+ code: string
1244
+ }): Molecule {
1245
+ if (!this.sourceWallet) {
1246
+ throw new Error('Source wallet required for identifier creation')
1247
+ }
1248
+
1249
+ const meta = {
1250
+ code,
1251
+ hash: generateBundleHash(contact.trim(), 'Molecule::initIdentifierCreation')
1252
+ }
1253
+
1254
+ this.addAtom(Atom.create({
1255
+ isotope: 'C',
1256
+ wallet: this.sourceWallet,
1257
+ metaType: 'identifier',
1258
+ metaId: type,
1259
+ meta: new AtomMeta(meta)
1260
+ }))
1261
+
1262
+ this.addContinuIdAtom()
1263
+
1264
+ return this
1265
+ }
824
1266
  }