@wishknish/knishio-client-ts 0.7.4 → 0.7.7

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 +2702 -1033
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +339 -106
  4. package/dist/index.d.ts +339 -106
  5. package/dist/index.iife.js +22052 -19964
  6. package/dist/index.iife.js.map +1 -1
  7. package/dist/index.js +2699 -1034
  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 +346 -51
  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 +607 -141
  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,9 @@ 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 continuIdPosition: string | null
82
+ public parentHashes: string[]
79
83
  public local?: number
80
84
 
81
85
  /**
@@ -88,7 +92,8 @@ export default class Molecule {
88
92
  sourceWallet = null,
89
93
  remainderWallet = null,
90
94
  cellSlug = null,
91
- version = null
95
+ version = null,
96
+ continuIdPosition = null
92
97
  }: {
93
98
  secret?: string | null
94
99
  bundle?: string | null
@@ -96,6 +101,7 @@ export default class Molecule {
96
101
  remainderWallet?: Wallet | null
97
102
  cellSlug?: string | null
98
103
  version?: string | number | null
104
+ continuIdPosition?: string | null
99
105
  } = {}) {
100
106
  this.status = null
101
107
  this.molecularHash = null
@@ -104,13 +110,17 @@ export default class Molecule {
104
110
  this.secret = secret
105
111
  this.bundle = bundle
106
112
  this.sourceWallet = sourceWallet
113
+ this.continuIdPosition = continuIdPosition
107
114
  this.atoms = []
108
-
109
- if (version !== null) {
115
+ this.parentHashes = []
116
+
117
+ const versionRegistry = versions as Record<string | number, any>
118
+ if (version !== null && Object.prototype.hasOwnProperty.call(versionRegistry, version)) {
110
119
  this.version = String(version)
111
- } else {
112
- this.version = null
113
120
  }
121
+ // Only set version if it's a recognized version in the registry (matching JS SDK).
122
+ // When version is not recognized, this.version stays undefined.
123
+ // JSON.stringify omits undefined properties, so the server won't see a version field.
114
124
 
115
125
  // Set the remainder wallet for this transaction
116
126
  if (remainderWallet || sourceWallet) {
@@ -149,7 +159,7 @@ export default class Molecule {
149
159
  if (!Array.isArray(isotopes)) {
150
160
  isotopes = [isotopes]
151
161
  }
152
- return atoms.filter(atom => isotopes.includes(atom.isotope))
162
+ return atoms.filter(atom => atom.isotope !== null && isotopes.includes(atom.isotope))
153
163
  }
154
164
 
155
165
  /**
@@ -176,7 +186,7 @@ export default class Molecule {
176
186
 
177
187
  // Set atom's index
178
188
  atom.index = this.generateIndex()
179
- atom.version = this.version ? Number(this.version) : 4
189
+ atom.version = this.version ?? undefined
180
190
 
181
191
  // Add atom
182
192
  this.atoms.push(atom)
@@ -189,19 +199,78 @@ export default class Molecule {
189
199
 
190
200
  /**
191
201
  * Add user remainder atom for ContinuID
202
+ * Matches JavaScript SDK addContinuIdAtom exactly
192
203
  */
193
204
  addContinuIdAtom(): Molecule {
194
- if (!this.remainderWallet) {
195
- throw new Error('Remainder wallet required for ContinuID atom')
205
+ // If remainder wallet is not USER token, create a new USER remainder wallet
206
+ if (!this.remainderWallet || this.remainderWallet.token !== 'USER') {
207
+ this.remainderWallet = Wallet.create({
208
+ secret: this.secret!,
209
+ bundle: this.bundle!
210
+ })
211
+ }
212
+
213
+ // ContinuID metadata for chain integrity validation
214
+ const continuIdMeta: Record<string, any> = {}
215
+
216
+ // previousPosition: the current USER ContinuID position being consumed.
217
+ // For non-USER source wallets (V/B isotopes), use the explicitly passed
218
+ // continuIdPosition from createMolecule() instead of the TOKEN wallet position.
219
+ if (this.continuIdPosition) {
220
+ continuIdMeta.previousPosition = this.continuIdPosition
221
+ } else if (this.sourceWallet && this.sourceWallet.position) {
222
+ continuIdMeta.previousPosition = this.sourceWallet.position
196
223
  }
197
224
 
198
- this.addAtom(new Atom({
225
+ // pubkey: ML-KEM public key for encrypted communication
226
+ if (this.remainderWallet.pubkey) {
227
+ continuIdMeta.pubkey = this.remainderWallet.pubkey
228
+ }
229
+
230
+ // characters: encoding format for the wallet
231
+ if (this.remainderWallet.characters) {
232
+ continuIdMeta.characters = this.remainderWallet.characters
233
+ }
234
+
235
+ this.addAtom(Atom.create({
199
236
  isotope: 'I',
200
- position: this.remainderWallet.position!,
201
- walletAddress: this.remainderWallet.address! as any,
202
- token: 'USER',
237
+ wallet: this.remainderWallet!,
203
238
  metaType: 'walletBundle',
204
- metaId: this.remainderWallet.bundle!
239
+ metaId: this.remainderWallet!.bundle!,
240
+ meta: new AtomMeta(continuIdMeta)
241
+ }))
242
+ return this
243
+ }
244
+
245
+ /**
246
+ * Add a policy R-isotope atom (matching JS SDK)
247
+ */
248
+ addPolicyAtom({
249
+ metaType,
250
+ metaId,
251
+ meta = {},
252
+ policy = {}
253
+ }: {
254
+ metaType: string
255
+ metaId: string
256
+ meta?: any
257
+ policy?: Record<string, any>
258
+ }): Molecule {
259
+ const atomMeta = new AtomMeta(meta)
260
+ atomMeta.addPolicy(policy)
261
+
262
+ const wallet = Wallet.create({
263
+ secret: this.secret!,
264
+ bundle: this.sourceWallet!.bundle!,
265
+ token: 'USER'
266
+ })
267
+
268
+ this.addAtom(Atom.create({
269
+ wallet,
270
+ isotope: 'R',
271
+ metaType,
272
+ metaId,
273
+ meta: atomMeta
205
274
  }))
206
275
  return this
207
276
  }
@@ -221,43 +290,31 @@ export default class Molecule {
221
290
  throw new Error('Source wallet required for value transfer')
222
291
  }
223
292
 
224
- if (this.sourceWallet.balance - amount < 0) {
293
+ if (Number(this.sourceWallet.balance) - amount < 0) {
225
294
  throw new BalanceInsufficientException()
226
295
  }
227
296
 
228
- // Initializing a new Atom to remove entire balance from source (UTXO model)
229
- this.addAtom(new Atom({
297
+ // Initializing a new Atom to remove tokens from source (debit full balance)
298
+ this.addAtom(Atom.create({
230
299
  isotope: 'V',
231
- position: this.sourceWallet.position!,
232
- walletAddress: this.sourceWallet.address! as any,
233
- token: this.sourceWallet.token,
234
- value: -this.sourceWallet.balance
300
+ wallet: this.sourceWallet,
301
+ value: -Number(this.sourceWallet.balance)
235
302
  }))
236
-
237
303
  // Initializing a new Atom to add tokens to recipient
238
- this.addAtom(new Atom({
304
+ this.addAtom(Atom.create({
239
305
  isotope: 'V',
240
- position: recipientWallet.position!,
241
- walletAddress: recipientWallet.address! as any,
242
- token: recipientWallet.token,
306
+ wallet: recipientWallet,
243
307
  value: amount,
244
308
  metaType: 'walletBundle',
245
309
  metaId: recipientWallet.bundle!
246
310
  }))
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({
311
+ // Initializing a remainder atom
312
+ this.addAtom(Atom.create({
254
313
  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,
314
+ wallet: this.remainderWallet!,
315
+ value: Number(this.sourceWallet.balance) - amount,
259
316
  metaType: 'walletBundle',
260
- metaId: this.remainderWallet.bundle!
317
+ metaId: this.remainderWallet!.bundle!
261
318
  }))
262
319
 
263
320
  return this
@@ -300,6 +357,21 @@ export default class Molecule {
300
357
  // Set signing position from the first atom
301
358
  let signingPosition = signingAtom.position
302
359
 
360
+ // Get signing wallet from first atom's metas (matching JS SDK exactly)
361
+ // This handles buffer withdraw reconciliation where a different wallet signs
362
+ const signingWalletMeta = signingAtom.aggregatedMeta ? signingAtom.aggregatedMeta() : null
363
+ const signingWalletJson = signingWalletMeta?.signingWallet as string | undefined
364
+ if (signingWalletJson) {
365
+ try {
366
+ const parsedSigningWallet = JSON.parse(signingWalletJson)
367
+ if (parsedSigningWallet.position) {
368
+ signingPosition = parsedSigningWallet.position
369
+ }
370
+ } catch {
371
+ // Invalid JSON in signingWallet meta, use default position
372
+ }
373
+ }
374
+
303
375
  // Signing position is required
304
376
  if (!signingPosition) {
305
377
  throw new SignatureMalformedException('Signing wallet must have a position!')
@@ -448,7 +520,7 @@ export default class Molecule {
448
520
  secureMode?: boolean
449
521
  } = {}): any {
450
522
  const {
451
- includeValidationContext = true,
523
+ includeValidationContext = false,
452
524
  includeOtsFragments = true,
453
525
  secureMode = false
454
526
  } = options
@@ -459,30 +531,37 @@ export default class Molecule {
459
531
  throw new Error('Cannot serialize molecule with secret in secure mode')
460
532
  }
461
533
 
462
- // Core molecule properties (always included)
534
+ // Core molecule properties (server-compatible fields only)
463
535
  const serialized: any = {
464
536
  status: this.status,
465
537
  molecularHash: this.molecularHash,
466
538
  createdAt: this.createdAt,
467
539
  cellSlug: this.cellSlug,
468
- cellSlugOrigin: this.cellSlugOrigin,
469
- version: this.version,
470
540
  bundle: this.bundle,
471
-
541
+
472
542
  // Serialized atoms array with optional OTS fragments
473
- atoms: this.atoms.map(atom => atom.toJSON({
474
- includeOtsFragments
543
+ atoms: this.atoms.map(atom => atom.toJSON({
544
+ includeOtsFragments
475
545
  }))
476
546
  }
477
547
 
478
- // Validation context (essential for cross-SDK validation)
548
+ // Parent molecular hashes for DAG linkage (only include when non-empty
549
+ // to maintain backward compatibility with servers that don't support it)
550
+ if (this.parentHashes && this.parentHashes.length > 0) {
551
+ serialized.parentHashes = this.parentHashes
552
+ }
553
+
554
+ // Extended context for Rust validator and local validation
479
555
  if (includeValidationContext) {
556
+ serialized.cellSlugOrigin = this.cellSlugOrigin
557
+ serialized.version = this.version
558
+
480
559
  if (this.sourceWallet) {
481
560
  serialized.sourceWallet = {
482
561
  address: this.sourceWallet.address,
483
562
  position: this.sourceWallet.position,
484
563
  token: this.sourceWallet.token,
485
- balance: this.sourceWallet.balance || 0,
564
+ balance: this.sourceWallet.balance || '0',
486
565
  bundle: this.sourceWallet.bundle,
487
566
  batchId: this.sourceWallet.batchId || null,
488
567
  characters: this.sourceWallet.characters || 'BASE64',
@@ -499,7 +578,7 @@ export default class Molecule {
499
578
  address: this.remainderWallet.address,
500
579
  position: this.remainderWallet.position,
501
580
  token: this.remainderWallet.token,
502
- balance: this.remainderWallet.balance || 0,
581
+ balance: this.remainderWallet.balance || '0',
503
582
  bundle: this.remainderWallet.bundle,
504
583
  batchId: this.remainderWallet.batchId || null,
505
584
  characters: this.remainderWallet.characters || 'BASE64',
@@ -532,6 +611,7 @@ export default class Molecule {
532
611
 
533
612
  /**
534
613
  * Initialize token creation
614
+ * Matches JavaScript SDK initTokenCreation implementation
535
615
  */
536
616
  initTokenCreation({
537
617
  recipientWallet,
@@ -542,32 +622,28 @@ export default class Molecule {
542
622
  amount: number
543
623
  meta: any
544
624
  }): 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,
625
+ if (!this.sourceWallet) {
626
+ throw new Error('Source wallet required for token creation')
627
+ }
628
+
629
+ // Build meta using AtomMeta class (matching JavaScript SDK exactly)
630
+ const atomMeta = new AtomMeta(meta)
631
+ atomMeta.setMetaWallet(recipientWallet)
632
+
633
+ // The primary atom tells the ledger that a certain amount of the new token is being issued
634
+ this.addAtom(Atom.create({
635
+ isotope: 'C',
636
+ wallet: this.sourceWallet,
551
637
  value: amount,
552
638
  metaType: 'token',
553
639
  metaId: recipientWallet.token,
554
- meta: meta ? [{ key: 'tokenMeta', value: JSON.stringify(meta) }] : []
640
+ meta: atomMeta,
641
+ batchId: recipientWallet.batchId
555
642
  }))
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
-
643
+
568
644
  // Add ContinuID atom (matching JavaScript SDK)
569
645
  this.addContinuIdAtom()
570
-
646
+
571
647
  return this
572
648
  }
573
649
 
@@ -576,7 +652,7 @@ export default class Molecule {
576
652
  */
577
653
  burnToken({
578
654
  amount,
579
- walletBundle = null
655
+ walletBundle: _walletBundle = null
580
656
  }: {
581
657
  amount: number
582
658
  walletBundle?: string | null
@@ -585,25 +661,68 @@ export default class Molecule {
585
661
  throw new Error('Source wallet required for token burning')
586
662
  }
587
663
 
588
- if (amount <= 0) {
589
- throw new NegativeAmountException()
664
+ if (amount < 0) {
665
+ throw new NegativeAmountException('Molecule::burnToken() - Amount to burn must be positive!')
590
666
  }
591
-
592
- if (this.sourceWallet.balance - amount < 0) {
667
+
668
+ if (Number(this.sourceWallet.balance) - amount < 0) {
593
669
  throw new BalanceInsufficientException()
594
670
  }
595
-
596
- // Burn tokens by removing from source
597
- this.addAtom(new Atom({
671
+
672
+ // Create burn address wallet (null bundle = token destruction)
673
+ const burnWallet = new Wallet({
674
+ bundle: '0000000000000000000000000000000000000000000000000000000000000000',
675
+ token: this.sourceWallet.token
676
+ })
677
+
678
+ // V-atom 1: Debit full balance from source
679
+ this.addAtom(Atom.create({
598
680
  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
681
+ wallet: this.sourceWallet,
682
+ value: -Number(this.sourceWallet.balance)
605
683
  }))
606
-
684
+
685
+ // V-atom 2: Credit burn amount to burn address
686
+ this.addAtom(Atom.create({
687
+ isotope: 'V',
688
+ wallet: burnWallet,
689
+ value: amount,
690
+ metaType: 'walletBundle',
691
+ metaId: burnWallet.bundle!
692
+ }))
693
+
694
+ // V-atom 3: Remainder back to source identity
695
+ this.addAtom(Atom.create({
696
+ isotope: 'V',
697
+ wallet: this.remainderWallet!,
698
+ value: Number(this.sourceWallet.balance) - amount,
699
+ metaType: 'walletBundle',
700
+ metaId: this.remainderWallet!.bundle!
701
+ }))
702
+
703
+ return this
704
+ }
705
+
706
+ /**
707
+ * Initialize authorization request
708
+ * Creates U-isotope (authorization) atom for requesting auth token
709
+ * Matches JavaScript SDK Molecule.initAuthorization
710
+ */
711
+ initAuthorization({ meta }: { meta: Record<string, any> }): Molecule {
712
+ if (!this.sourceWallet) {
713
+ throw new Error('Source wallet required for authorization')
714
+ }
715
+
716
+ // Create U-isotope atom for authorization (matching JS SDK)
717
+ this.addAtom(Atom.create({
718
+ isotope: 'U',
719
+ wallet: this.sourceWallet,
720
+ meta: new AtomMeta(meta)
721
+ }))
722
+
723
+ // Add ContinuID atom (matching JavaScript SDK)
724
+ this.addContinuIdAtom()
725
+
607
726
  return this
608
727
  }
609
728
 
@@ -625,78 +744,171 @@ export default class Molecule {
625
744
  throw new Error('Source wallet required for meta creation')
626
745
  }
627
746
 
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({
747
+ // Initializing a new Atom to hold our metadata (matching JS SDK)
748
+ this.addAtom(Atom.create({
650
749
  isotope: 'M',
651
- position: this.sourceWallet.position!,
652
- walletAddress: this.sourceWallet.address! as any,
653
- token: this.sourceWallet.token,
750
+ wallet: this.sourceWallet,
654
751
  metaType,
655
752
  metaId,
656
- meta: metaArray
753
+ meta: new AtomMeta(meta)
657
754
  }))
658
-
659
- // Add ContinuID atom (matching JavaScript SDK)
755
+
756
+ // Only add policy atom if policy is provided and not empty
757
+ if (policy && Object.keys(policy).length > 0) {
758
+ this.addPolicyAtom({
759
+ metaType,
760
+ metaId,
761
+ meta,
762
+ policy
763
+ })
764
+ }
765
+
766
+ // User remainder atom
660
767
  this.addContinuIdAtom()
661
-
768
+
662
769
  return this
663
770
  }
664
771
 
665
772
  /**
666
773
  * Initialize wallet creation
774
+ * Uses source wallet for atom properties (matching JavaScript SDK)
775
+ * The wallet being created is stored in metadata
667
776
  */
668
777
  initWalletCreation(wallet: Wallet, atomMeta: AtomMeta | null = null): Molecule {
669
- // Create wallet creation atom
670
- const atom = new Atom({
778
+ if (!this.sourceWallet) {
779
+ throw new Error('Source wallet required for wallet creation')
780
+ }
781
+
782
+ // Matching JS SDK: use AtomMeta.setMetaWallet() for wallet metadata
783
+ if (!atomMeta) {
784
+ atomMeta = new AtomMeta()
785
+ }
786
+ atomMeta.setMetaWallet(wallet)
787
+
788
+ const creationAtom = Atom.create({
671
789
  isotope: 'C',
672
- position: wallet.position!,
673
- walletAddress: wallet.address! as any,
674
- token: wallet.token,
790
+ wallet: this.sourceWallet,
675
791
  metaType: 'wallet',
676
792
  metaId: wallet.address!,
677
- meta: atomMeta ? atomMeta.get() as any : []
793
+ meta: atomMeta,
794
+ batchId: wallet.batchId
678
795
  })
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
- }
796
+ this.addAtom(creationAtom)
797
+ this.addContinuIdAtom()
798
+
799
+ return this
800
+ }
801
+
802
+ /**
803
+ * Creates atoms for rule definition
804
+ * Matches JavaScript SDK Molecule.createRule method
805
+ *
806
+ * @param metaType - The meta type for the rule
807
+ * @param metaId - The meta ID for the rule
808
+ * @param rule - Array of Rule objects or rule data
809
+ * @param policy - Optional policy object
810
+ * @return This molecule instance for chaining
811
+ */
812
+ createRule({
813
+ metaType,
814
+ metaId,
815
+ rule,
816
+ policy = {}
817
+ }: {
818
+ metaType: string
819
+ metaId: string
820
+ rule: Rule[] | any[] | any // Accept single object or array
821
+ policy?: Record<string, any>
822
+ }): Molecule {
823
+ if (!this.sourceWallet) {
824
+ throw new Error('Source wallet required for createRule')
693
825
  }
694
-
695
- this.addAtom(atom)
696
-
697
- // Add ContinuID atom (matching JavaScript SDK)
826
+
827
+ // Normalize rule to array (accept single object or array)
828
+ const ruleArray = Array.isArray(rule) ? rule : [rule]
829
+
830
+ // Convert rules to objects (matching JavaScript SDK)
831
+ const rules: Record<string, any>[] = []
832
+ for (const r of ruleArray) {
833
+ rules.push(r instanceof Rule ? r : Rule.toObject(r))
834
+ }
835
+
836
+ // Create atom meta with rules
837
+ const atomMeta = new AtomMeta({
838
+ rule: JSON.stringify(rules)
839
+ })
840
+
841
+ // Add policies to meta object only if policy exists and has keys (matching JS SDK)
842
+ if (policy && Object.keys(policy).length > 0) {
843
+ atomMeta.addPolicy(policy)
844
+ }
845
+
846
+ // Create R-isotope atom for rule (matching JS SDK)
847
+ this.addAtom(Atom.create({
848
+ isotope: 'R',
849
+ wallet: this.sourceWallet,
850
+ metaType,
851
+ metaId,
852
+ meta: atomMeta
853
+ }))
854
+
855
+ // Add ContinuID atom
698
856
  this.addContinuIdAtom()
699
-
857
+
858
+ return this
859
+ }
860
+
861
+ /**
862
+ * Initializes a token request molecule
863
+ * Matches JavaScript SDK Molecule.initTokenRequest method
864
+ *
865
+ * @param token - Token slug to request
866
+ * @param amount - Amount of tokens to request
867
+ * @param metaType - Meta type (typically 'walletBundle')
868
+ * @param metaId - Meta ID (typically recipient bundle hash)
869
+ * @param meta - Optional additional metadata
870
+ * @param batchId - Optional batch ID
871
+ * @return This molecule instance for chaining
872
+ */
873
+ initTokenRequest({
874
+ token,
875
+ amount,
876
+ metaType,
877
+ metaId,
878
+ meta = {},
879
+ batchId = null
880
+ }: {
881
+ token: string
882
+ amount: number
883
+ metaType: string
884
+ metaId: string
885
+ meta?: Record<string, any>
886
+ batchId?: string | null
887
+ }): Molecule {
888
+ if (!this.sourceWallet) {
889
+ throw new Error('Source wallet required for initTokenRequest')
890
+ }
891
+
892
+ // Add token and amount to meta (matching JavaScript SDK exactly)
893
+ const metaWithToken = { ...meta, token, amount: String(amount) }
894
+
895
+ // Mark as local molecule
896
+ this.local = 1
897
+
898
+ // Create T-isotope atom for token request (matching JS SDK)
899
+ this.addAtom(Atom.create({
900
+ isotope: 'T',
901
+ wallet: this.sourceWallet,
902
+ value: amount,
903
+ metaType,
904
+ metaId,
905
+ meta: new AtomMeta(metaWithToken),
906
+ batchId
907
+ }))
908
+
909
+ // Add ContinuID atom
910
+ this.addContinuIdAtom()
911
+
700
912
  return this
701
913
  }
702
914
 
@@ -761,6 +973,9 @@ export default class Molecule {
761
973
  })
762
974
  }
763
975
 
976
+ // Reconstruct parent hashes for DAG linkage
977
+ molecule.parentHashes = Array.isArray(data.parentHashes) ? [...data.parentHashes] : []
978
+
764
979
  // Reconstruct validation context if available and requested
765
980
  if (includeValidationContext) {
766
981
  if (data.sourceWallet) {
@@ -775,7 +990,7 @@ export default class Molecule {
775
990
  })
776
991
 
777
992
  // Set additional properties for validation context
778
- molecule.sourceWallet.balance = data.sourceWallet.balance || 0
993
+ molecule.sourceWallet.balance = String(data.sourceWallet.balance != null ? data.sourceWallet.balance : 0)
779
994
  molecule.sourceWallet.address = data.sourceWallet.address as any
780
995
  if (data.sourceWallet.pubkey) {
781
996
  molecule.sourceWallet.pubkey = data.sourceWallet.pubkey
@@ -797,7 +1012,7 @@ export default class Molecule {
797
1012
  })
798
1013
 
799
1014
  // Set additional properties for validation context
800
- molecule.remainderWallet.balance = data.remainderWallet.balance || 0
1015
+ molecule.remainderWallet.balance = String(data.remainderWallet.balance != null ? data.remainderWallet.balance : 0)
801
1016
  molecule.remainderWallet.address = data.remainderWallet.address as any
802
1017
  if (data.remainderWallet.pubkey) {
803
1018
  molecule.remainderWallet.pubkey = data.remainderWallet.pubkey
@@ -821,4 +1036,255 @@ export default class Molecule {
821
1036
  static jsonToObject(json: string): Molecule {
822
1037
  return Molecule.fromJSON(json)
823
1038
  }
1039
+
1040
+ // =============================================================================
1041
+ // ISOTOPE INIT METHODS - MATCHING JAVASCRIPT SDK PARITY
1042
+ // =============================================================================
1043
+
1044
+ /**
1045
+ * Initialize a P-type molecule for peer registration
1046
+ * Matches JavaScript SDK Molecule.initPeering implementation exactly
1047
+ *
1048
+ * @param host - The peer host URL to register
1049
+ * @return This molecule instance for chaining
1050
+ */
1051
+ initPeering({ host }: { host: string }): Molecule {
1052
+ if (!this.sourceWallet) {
1053
+ throw new Error('Source wallet required for peer registration')
1054
+ }
1055
+
1056
+ this.addAtom(Atom.create({
1057
+ isotope: 'P',
1058
+ wallet: this.sourceWallet,
1059
+ metaType: 'walletBundle',
1060
+ metaId: this.bundle!,
1061
+ meta: new AtomMeta({ peerHost: host })
1062
+ }))
1063
+
1064
+ this.addContinuIdAtom()
1065
+
1066
+ return this
1067
+ }
1068
+
1069
+ /**
1070
+ * Initialize an A-type molecule for an append request
1071
+ * Matches JavaScript SDK Molecule.initAppendRequest implementation exactly
1072
+ *
1073
+ * @param metaType - The target MetaType to append to
1074
+ * @param metaId - The target MetaId to append to
1075
+ * @param action - The action to perform
1076
+ * @param meta - Additional metadata
1077
+ * @return This molecule instance for chaining
1078
+ */
1079
+ initAppendRequest({
1080
+ metaType,
1081
+ metaId,
1082
+ action,
1083
+ meta = {}
1084
+ }: {
1085
+ metaType: string
1086
+ metaId: string
1087
+ action: string
1088
+ meta?: Record<string, any>
1089
+ }): Molecule {
1090
+ if (!this.sourceWallet) {
1091
+ throw new Error('Source wallet required for append request')
1092
+ }
1093
+
1094
+ this.addAtom(Atom.create({
1095
+ isotope: 'A',
1096
+ wallet: this.sourceWallet,
1097
+ metaType,
1098
+ metaId,
1099
+ meta: new AtomMeta({ action, ...meta })
1100
+ }))
1101
+
1102
+ this.addContinuIdAtom()
1103
+
1104
+ return this
1105
+ }
1106
+
1107
+ /**
1108
+ * Initialize a deposit buffer operation (B-isotope)
1109
+ * Matches JavaScript SDK Molecule.initDepositBuffer implementation exactly
1110
+ *
1111
+ * @param amount - Amount to deposit into buffer
1112
+ * @param tradeRates - Trade rates for the buffer wallet
1113
+ * @return This molecule instance for chaining
1114
+ */
1115
+ initDepositBuffer({
1116
+ amount,
1117
+ tradeRates
1118
+ }: {
1119
+ amount: number
1120
+ tradeRates?: Record<string, any>
1121
+ }): Molecule {
1122
+ if (!this.sourceWallet) {
1123
+ throw new Error('Source wallet required for buffer deposit')
1124
+ }
1125
+
1126
+ if (Number(this.sourceWallet.balance) - amount < 0) {
1127
+ throw new BalanceInsufficientException()
1128
+ }
1129
+
1130
+ // Create a buffer wallet
1131
+ const bufferWallet = Wallet.create({
1132
+ secret: this.secret!,
1133
+ bundle: this.bundle,
1134
+ token: this.sourceWallet.token,
1135
+ batchId: this.sourceWallet.batchId
1136
+ })
1137
+ if (tradeRates) {
1138
+ bufferWallet.tradeRates = tradeRates
1139
+ }
1140
+
1141
+ // Debit full balance from source (V-isotope)
1142
+ this.addAtom(Atom.create({
1143
+ isotope: 'V',
1144
+ wallet: this.sourceWallet,
1145
+ value: -Number(this.sourceWallet.balance)
1146
+ }))
1147
+
1148
+ // Deposit amount to buffer (B-isotope)
1149
+ this.addAtom(Atom.create({
1150
+ isotope: 'B',
1151
+ wallet: bufferWallet,
1152
+ value: amount,
1153
+ metaType: 'walletBundle',
1154
+ metaId: this.sourceWallet.bundle!
1155
+ }))
1156
+
1157
+ // Remainder back to source
1158
+ this.addAtom(Atom.create({
1159
+ isotope: 'V',
1160
+ wallet: this.remainderWallet!,
1161
+ value: Number(this.sourceWallet.balance) - amount,
1162
+ metaType: 'walletBundle',
1163
+ metaId: this.sourceWallet.bundle!
1164
+ }))
1165
+
1166
+ return this
1167
+ }
1168
+
1169
+ /**
1170
+ * Initialize a withdraw buffer operation (B-isotope)
1171
+ * Matches JavaScript SDK Molecule.initWithdrawBuffer implementation exactly
1172
+ *
1173
+ * @param recipients - Map of recipientBundle → amount
1174
+ * @param signingWallet - Optional signing wallet for reconciliation
1175
+ * @return This molecule instance for chaining
1176
+ */
1177
+ initWithdrawBuffer({
1178
+ recipients,
1179
+ signingWallet = null
1180
+ }: {
1181
+ recipients: Record<string, number>
1182
+ signingWallet?: any | null
1183
+ }): Molecule {
1184
+ if (!this.sourceWallet) {
1185
+ throw new Error('Source wallet required for buffer withdrawal')
1186
+ }
1187
+
1188
+ // Calculate total from all recipients
1189
+ let amount = 0
1190
+ for (const recipientAmount of Object.values(recipients || {})) {
1191
+ amount += recipientAmount
1192
+ }
1193
+
1194
+ if (Number(this.sourceWallet.balance) - amount < 0) {
1195
+ throw new BalanceInsufficientException()
1196
+ }
1197
+
1198
+ // Set a meta signing position for molecule correct reconciliation
1199
+ const firstAtomMeta = new AtomMeta()
1200
+ if (signingWallet) {
1201
+ firstAtomMeta.setSigningWallet(signingWallet)
1202
+ }
1203
+
1204
+ // Debit full balance from source (B-isotope)
1205
+ this.addAtom(Atom.create({
1206
+ isotope: 'B',
1207
+ wallet: this.sourceWallet,
1208
+ value: -Number(this.sourceWallet.balance),
1209
+ meta: firstAtomMeta,
1210
+ metaType: 'walletBundle',
1211
+ metaId: this.sourceWallet.bundle!
1212
+ }))
1213
+
1214
+ // Add recipient V-atoms (matching JS SDK: uses `new Atom()` with explicit token)
1215
+ for (const [recipientBundle, recipientAmount] of Object.entries(recipients || {})) {
1216
+ this.addAtom(new Atom({
1217
+ isotope: 'V',
1218
+ token: this.sourceWallet.token,
1219
+ value: recipientAmount as number,
1220
+ batchId: this.sourceWallet.batchId ? generateBatchId({}) : null,
1221
+ metaType: 'walletBundle',
1222
+ metaId: recipientBundle
1223
+ }))
1224
+ }
1225
+
1226
+ // Remainder back to buffer
1227
+ this.addAtom(Atom.create({
1228
+ isotope: 'B',
1229
+ wallet: this.remainderWallet!,
1230
+ value: Number(this.sourceWallet.balance) - amount,
1231
+ metaType: 'walletBundle',
1232
+ metaId: this.remainderWallet!.bundle!
1233
+ }))
1234
+
1235
+ return this
1236
+ }
1237
+
1238
+ /**
1239
+ * Initialize shadow wallet claim
1240
+ * Matches JavaScript SDK Molecule.initShadowWalletClaim implementation exactly
1241
+ *
1242
+ * @param wallet - The shadow wallet to claim
1243
+ * @return This molecule instance for chaining
1244
+ */
1245
+ initShadowWalletClaim(wallet: Wallet): Molecule {
1246
+ const atomMeta = new AtomMeta()
1247
+ atomMeta.setShadowWalletClaim(true)
1248
+ return this.initWalletCreation(wallet, atomMeta)
1249
+ }
1250
+
1251
+ /**
1252
+ * Initialize identifier creation
1253
+ * Matches JavaScript SDK Molecule.initIdentifierCreation implementation exactly
1254
+ *
1255
+ * @param type - Identifier type (phone or email)
1256
+ * @param contact - Phone number or email string
1257
+ * @param code - Verification code
1258
+ * @return This molecule instance for chaining
1259
+ */
1260
+ initIdentifierCreation({
1261
+ type,
1262
+ contact,
1263
+ code
1264
+ }: {
1265
+ type: string
1266
+ contact: string
1267
+ code: string
1268
+ }): Molecule {
1269
+ if (!this.sourceWallet) {
1270
+ throw new Error('Source wallet required for identifier creation')
1271
+ }
1272
+
1273
+ const meta = {
1274
+ code,
1275
+ hash: generateBundleHash(contact.trim(), 'Molecule::initIdentifierCreation')
1276
+ }
1277
+
1278
+ this.addAtom(Atom.create({
1279
+ isotope: 'C',
1280
+ wallet: this.sourceWallet,
1281
+ metaType: 'identifier',
1282
+ metaId: type,
1283
+ meta: new AtomMeta(meta)
1284
+ }))
1285
+
1286
+ this.addContinuIdAtom()
1287
+
1288
+ return this
1289
+ }
824
1290
  }