@wishknish/knishio-client-ts 0.7.0
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/LICENSE +674 -0
- package/README.md +425 -0
- package/dist/index.cjs +9961 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.iife.js +32241 -0
- package/dist/index.iife.js.map +1 -0
- package/dist/index.js +9853 -0
- package/dist/index.js.map +1 -0
- package/package.json +113 -0
- package/src/AuthToken.ts +214 -0
- package/src/KnishIOClient.ts +2020 -0
- package/src/constants.ts +398 -0
- package/src/core/Atom.ts +646 -0
- package/src/core/AtomMeta.ts +278 -0
- package/src/core/Meta.ts +428 -0
- package/src/core/Molecule.ts +825 -0
- package/src/core/PolicyMeta.ts +130 -0
- package/src/core/TokenUnit.ts +148 -0
- package/src/core/Wallet.ts +467 -0
- package/src/exception/AtomIndexException.ts +97 -0
- package/src/exception/AtomsMissingException.ts +109 -0
- package/src/exception/AuthorizationRejectedException.ts +63 -0
- package/src/exception/BalanceInsufficientException.ts +58 -0
- package/src/exception/BaseException.ts +275 -0
- package/src/exception/BatchIdException.ts +58 -0
- package/src/exception/CodeException.ts +119 -0
- package/src/exception/DecryptionKeyException.ts +65 -0
- package/src/exception/InvalidResponseException.ts +112 -0
- package/src/exception/MetaMissingException.ts +58 -0
- package/src/exception/MolecularHashMismatchException.ts +115 -0
- package/src/exception/MolecularHashMissingException.ts +58 -0
- package/src/exception/NegativeAmountException.ts +58 -0
- package/src/exception/PolicyInvalidException.ts +58 -0
- package/src/exception/SignatureMalformedException.ts +58 -0
- package/src/exception/SignatureMismatchException.ts +98 -0
- package/src/exception/StackableUnitAmountException.ts +65 -0
- package/src/exception/StackableUnitDecimalsException.ts +65 -0
- package/src/exception/TransferBalanceException.ts +98 -0
- package/src/exception/TransferMalformedException.ts +58 -0
- package/src/exception/TransferMismatchedException.ts +58 -0
- package/src/exception/TransferRemainderException.ts +58 -0
- package/src/exception/TransferToSelfException.ts +58 -0
- package/src/exception/TransferUnbalancedException.ts +58 -0
- package/src/exception/UnauthenticatedException.ts +147 -0
- package/src/exception/WalletCredentialException.ts +108 -0
- package/src/exception/WalletShadowException.ts +65 -0
- package/src/exception/WrongTokenTypeException.ts +58 -0
- package/src/exception/index.ts +272 -0
- package/src/index.ts +512 -0
- package/src/instance/rules/Callback.ts +257 -0
- package/src/instance/rules/Condition.ts +96 -0
- package/src/instance/rules/Meta.ts +163 -0
- package/src/instance/rules/Rule.ts +29 -0
- package/src/instance/rules/exception/RuleArgumentException.ts +65 -0
- package/src/libraries/CheckMolecule.ts +581 -0
- package/src/libraries/Decimal.ts +94 -0
- package/src/libraries/Dot.ts +202 -0
- package/src/libraries/GraphQLClient.ts +276 -0
- package/src/libraries/Hex.ts +155 -0
- package/src/libraries/UrqlClientWrapper.ts +336 -0
- package/src/libraries/array.ts +91 -0
- package/src/libraries/crypto.ts +816 -0
- package/src/libraries/strings.ts +458 -0
- package/src/mutation/Mutation.ts +103 -0
- package/src/mutation/MutationActiveSession.ts +108 -0
- package/src/mutation/MutationClaimShadowWallet.ts +91 -0
- package/src/mutation/MutationCreateIdentifier.ts +86 -0
- package/src/mutation/MutationCreateMeta.ts +92 -0
- package/src/mutation/MutationCreateRule.ts +90 -0
- package/src/mutation/MutationCreateToken.ts +92 -0
- package/src/mutation/MutationCreateWallet.ts +78 -0
- package/src/mutation/MutationDepositBufferToken.ts +72 -0
- package/src/mutation/MutationLinkIdentifier.ts +87 -0
- package/src/mutation/MutationProposeMolecule.ts +147 -0
- package/src/mutation/MutationRequestAuthorization.ts +77 -0
- package/src/mutation/MutationRequestAuthorizationGuest.ts +85 -0
- package/src/mutation/MutationRequestTokens.ts +98 -0
- package/src/mutation/MutationTransferTokens.ts +87 -0
- package/src/mutation/MutationWithdrawBufferToken.ts +73 -0
- package/src/query/Query.ts +187 -0
- package/src/query/QueryActiveSession.ts +89 -0
- package/src/query/QueryAtom.ts +275 -0
- package/src/query/QueryBalance.ts +102 -0
- package/src/query/QueryBatch.ts +145 -0
- package/src/query/QueryBatchHistory.ts +86 -0
- package/src/query/QueryContinuId.ts +91 -0
- package/src/query/QueryMetaType.ts +177 -0
- package/src/query/QueryMetaTypeViaAtom.ts +199 -0
- package/src/query/QueryPolicy.ts +91 -0
- package/src/query/QueryToken.ts +90 -0
- package/src/query/QueryUserActivity.ts +153 -0
- package/src/query/QueryWalletBundle.ts +92 -0
- package/src/query/QueryWalletList.ts +106 -0
- package/src/response/EnhancedResponse.ts +345 -0
- package/src/response/Response.ts +254 -0
- package/src/response/ResponseActiveSession.ts +72 -0
- package/src/response/ResponseAtom.ts +132 -0
- package/src/response/ResponseAuthorizationGuest.ts +119 -0
- package/src/response/ResponseBalance.ts +153 -0
- package/src/response/ResponseClaimShadowWallet.ts +56 -0
- package/src/response/ResponseContinuId.ts +101 -0
- package/src/response/ResponseCreateIdentifier.ts +56 -0
- package/src/response/ResponseCreateMeta.ts +58 -0
- package/src/response/ResponseCreateRule.ts +56 -0
- package/src/response/ResponseCreateToken.ts +58 -0
- package/src/response/ResponseCreateWallet.ts +58 -0
- package/src/response/ResponseLinkIdentifier.ts +87 -0
- package/src/response/ResponseMetaBatch.ts +72 -0
- package/src/response/ResponseMetaType.ts +108 -0
- package/src/response/ResponseMetaTypeViaAtom.ts +108 -0
- package/src/response/ResponsePolicy.ts +90 -0
- package/src/response/ResponseProposeMolecule.ts +155 -0
- package/src/response/ResponseQueryActiveSession.ts +105 -0
- package/src/response/ResponseQueryUserActivity.ts +89 -0
- package/src/response/ResponseRequestAuthorization.ts +100 -0
- package/src/response/ResponseRequestAuthorizationGuest.ts +133 -0
- package/src/response/ResponseRequestTokens.ts +58 -0
- package/src/response/ResponseTransferTokens.ts +72 -0
- package/src/response/ResponseWalletBundle.ts +95 -0
- package/src/response/ResponseWalletList.ts +165 -0
- package/src/schemas/index.ts +457 -0
- package/src/subscribe/ActiveSessionSubscribe.ts +72 -0
- package/src/subscribe/ActiveWalletSubscribe.ts +99 -0
- package/src/subscribe/CreateMoleculeSubscribe.ts +106 -0
- package/src/subscribe/Subscribe.ts +182 -0
- package/src/subscribe/WalletStatusSubscribe.ts +70 -0
- package/src/subscribe/index.ts +59 -0
- package/src/types/assertions.ts +722 -0
- package/src/types/client.ts +567 -0
- package/src/types/crypto.ts +541 -0
- package/src/types/graphql.ts +630 -0
- package/src/types/guards.ts +659 -0
- package/src/types/index.ts +614 -0
- package/src/types/response.ts +133 -0
- package/src/types/template-literals.ts +382 -0
- package/src/validation/UNIVERSAL_CONFIGURATION_INTERFACES.ts +580 -0
- package/src/validation/ValidationService.ts +607 -0
- package/src/validation/schemas.ts +447 -0
- package/src/versions/HashAtom.ts +170 -0
- package/src/versions/Version4.ts +120 -0
- package/src/versions/index.ts +80 -0
|
@@ -0,0 +1,825 @@
|
|
|
1
|
+
/*
|
|
2
|
+
(
|
|
3
|
+
(/(
|
|
4
|
+
(//(
|
|
5
|
+
(///(
|
|
6
|
+
(/////(
|
|
7
|
+
(//////( )
|
|
8
|
+
(////////( (/)
|
|
9
|
+
(////////( (///)
|
|
10
|
+
(//////////( (////)
|
|
11
|
+
(//////////( (//////)
|
|
12
|
+
(////////////( (///////)
|
|
13
|
+
(/////////////( (/////////)
|
|
14
|
+
(//////////////( (///////////)
|
|
15
|
+
(///////////////( (/////////////)
|
|
16
|
+
(////////////////( (//////////////)
|
|
17
|
+
((((((((((((((((((( (((((((((((((((
|
|
18
|
+
((((((((((((((((((( ((((((((((((((
|
|
19
|
+
((((((((((((((((((( ((((((((((((((
|
|
20
|
+
(((((((((((((((((((( (((((((((((((
|
|
21
|
+
(((((((((((((((((((( ((((((((((((
|
|
22
|
+
((((((((((((((((((( ((((((((((((
|
|
23
|
+
((((((((((((((((((( ((((((((((
|
|
24
|
+
((((((((((((((((((/ (((((((((
|
|
25
|
+
(((((((((((((((((( ((((((((
|
|
26
|
+
((((((((((((((((( (((((((
|
|
27
|
+
(((((((((((((((((( (((((
|
|
28
|
+
################# ##
|
|
29
|
+
################ #
|
|
30
|
+
################# ##
|
|
31
|
+
%################ ###
|
|
32
|
+
###############( ####
|
|
33
|
+
############### ####
|
|
34
|
+
############### ######
|
|
35
|
+
%#############( (#######
|
|
36
|
+
%############# #########
|
|
37
|
+
############( ##########
|
|
38
|
+
########### #############
|
|
39
|
+
######### ##############
|
|
40
|
+
%######
|
|
41
|
+
|
|
42
|
+
Powered by Knish.IO: Connecting a Decentralized World
|
|
43
|
+
|
|
44
|
+
Please visit https://github.com/WishKnish/KnishIO-Client-TS for information.
|
|
45
|
+
|
|
46
|
+
License: https://github.com/WishKnish/KnishIO-Client-TS/blob/master/LICENSE
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
import Atom from './Atom'
|
|
50
|
+
import AtomMeta from './AtomMeta'
|
|
51
|
+
import Wallet from './Wallet'
|
|
52
|
+
import CheckMolecule from '@/libraries/CheckMolecule'
|
|
53
|
+
import { chunkSubstr, hexToBase64 } from '@/libraries/strings'
|
|
54
|
+
import { generateBundleHash, generateOTSSignature } from '@/libraries/crypto'
|
|
55
|
+
import { deepCloning } from '@/libraries/array'
|
|
56
|
+
import {
|
|
57
|
+
AtomsMissingException,
|
|
58
|
+
BalanceInsufficientException,
|
|
59
|
+
NegativeAmountException,
|
|
60
|
+
SignatureMalformedException
|
|
61
|
+
} from '@/exception'
|
|
62
|
+
import type { AtomIsotope } from '@/types'
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Molecule class - Transaction container for KnishIO DLT
|
|
66
|
+
* Essential implementation for basic transactions
|
|
67
|
+
*/
|
|
68
|
+
export default class Molecule {
|
|
69
|
+
public status: string | null
|
|
70
|
+
public molecularHash: string | null
|
|
71
|
+
public createdAt: string
|
|
72
|
+
public cellSlugOrigin: string | null
|
|
73
|
+
public cellSlug: string | null
|
|
74
|
+
public secret: string | null
|
|
75
|
+
public bundle: string | null
|
|
76
|
+
public sourceWallet: Wallet | null
|
|
77
|
+
public remainderWallet: Wallet | null
|
|
78
|
+
public atoms: Atom[]
|
|
79
|
+
public version: string | null
|
|
80
|
+
public local?: number
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Create new Molecule instance
|
|
84
|
+
* Matches JavaScript SDK constructor signature
|
|
85
|
+
*/
|
|
86
|
+
constructor({
|
|
87
|
+
secret = null,
|
|
88
|
+
bundle = null,
|
|
89
|
+
sourceWallet = null,
|
|
90
|
+
remainderWallet = null,
|
|
91
|
+
cellSlug = null,
|
|
92
|
+
version = null
|
|
93
|
+
}: {
|
|
94
|
+
secret?: string | null
|
|
95
|
+
bundle?: string | null
|
|
96
|
+
sourceWallet?: Wallet | null
|
|
97
|
+
remainderWallet?: Wallet | null
|
|
98
|
+
cellSlug?: string | null
|
|
99
|
+
version?: string | number | null
|
|
100
|
+
} = {}) {
|
|
101
|
+
this.status = null
|
|
102
|
+
this.molecularHash = null
|
|
103
|
+
this.createdAt = String(+new Date())
|
|
104
|
+
this.cellSlugOrigin = this.cellSlug = cellSlug
|
|
105
|
+
this.secret = secret
|
|
106
|
+
this.bundle = bundle
|
|
107
|
+
this.sourceWallet = sourceWallet
|
|
108
|
+
this.atoms = []
|
|
109
|
+
|
|
110
|
+
if (version !== null) {
|
|
111
|
+
this.version = String(version)
|
|
112
|
+
} else {
|
|
113
|
+
this.version = null
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Set the remainder wallet for this transaction
|
|
117
|
+
if (remainderWallet || sourceWallet) {
|
|
118
|
+
this.remainderWallet = remainderWallet || Wallet.create({
|
|
119
|
+
secret: secret!,
|
|
120
|
+
bundle,
|
|
121
|
+
token: sourceWallet!.token,
|
|
122
|
+
batchId: sourceWallet!.batchId,
|
|
123
|
+
characters: sourceWallet!.characters
|
|
124
|
+
})
|
|
125
|
+
} else {
|
|
126
|
+
this.remainderWallet = null
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Returns the cell slug delimiter
|
|
132
|
+
*/
|
|
133
|
+
get cellSlugDelimiter(): string {
|
|
134
|
+
return '.'
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Filters atoms by isotope(s)
|
|
139
|
+
* Matches JavaScript SDK getIsotopes method
|
|
140
|
+
*/
|
|
141
|
+
getIsotopes(isotopes: AtomIsotope | AtomIsotope[]): Atom[] {
|
|
142
|
+
return Molecule.isotopeFilter(isotopes, this.atoms)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Filters the atoms array by the supplied isotope list
|
|
147
|
+
* Static version matching JavaScript SDK
|
|
148
|
+
*/
|
|
149
|
+
static isotopeFilter(isotopes: AtomIsotope | AtomIsotope[], atoms: Atom[]): Atom[] {
|
|
150
|
+
if (!Array.isArray(isotopes)) {
|
|
151
|
+
isotopes = [isotopes]
|
|
152
|
+
}
|
|
153
|
+
return atoms.filter(atom => isotopes.includes(atom.isotope))
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Generates the next atomic index
|
|
158
|
+
*/
|
|
159
|
+
static generateNextAtomIndex(atoms: Atom[]): number {
|
|
160
|
+
return atoms.length
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Generates the next atomic index for this molecule
|
|
165
|
+
*/
|
|
166
|
+
generateIndex(): number {
|
|
167
|
+
return Molecule.generateNextAtomIndex(this.atoms)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Add an atom to this molecule
|
|
172
|
+
* Matches JavaScript SDK addAtom method
|
|
173
|
+
*/
|
|
174
|
+
addAtom(atom: Atom): Molecule {
|
|
175
|
+
// Reset the molecular hash
|
|
176
|
+
this.molecularHash = null
|
|
177
|
+
|
|
178
|
+
// Set atom's index
|
|
179
|
+
atom.index = this.generateIndex()
|
|
180
|
+
atom.version = this.version ? Number(this.version) : 4
|
|
181
|
+
|
|
182
|
+
// Add atom
|
|
183
|
+
this.atoms.push(atom)
|
|
184
|
+
|
|
185
|
+
// Sort atoms
|
|
186
|
+
this.atoms = Atom.sortAtoms(this.atoms)
|
|
187
|
+
|
|
188
|
+
return this
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Add user remainder atom for ContinuID
|
|
193
|
+
*/
|
|
194
|
+
addContinuIdAtom(): Molecule {
|
|
195
|
+
if (!this.remainderWallet) {
|
|
196
|
+
throw new Error('Remainder wallet required for ContinuID atom')
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
this.addAtom(new Atom({
|
|
200
|
+
isotope: 'I',
|
|
201
|
+
position: this.remainderWallet.position!,
|
|
202
|
+
walletAddress: this.remainderWallet.address! as any,
|
|
203
|
+
token: 'USER',
|
|
204
|
+
metaType: 'walletBundle',
|
|
205
|
+
metaId: this.remainderWallet.bundle!
|
|
206
|
+
}))
|
|
207
|
+
return this
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Initialize a V-type molecule to transfer value
|
|
212
|
+
* Essential method for basic transfers
|
|
213
|
+
*/
|
|
214
|
+
initValue({
|
|
215
|
+
recipientWallet,
|
|
216
|
+
amount
|
|
217
|
+
}: {
|
|
218
|
+
recipientWallet: Wallet
|
|
219
|
+
amount: number
|
|
220
|
+
}): Molecule {
|
|
221
|
+
if (!this.sourceWallet) {
|
|
222
|
+
throw new Error('Source wallet required for value transfer')
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (this.sourceWallet.balance - amount < 0) {
|
|
226
|
+
throw new BalanceInsufficientException()
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Initializing a new Atom to remove entire balance from source (UTXO model)
|
|
230
|
+
this.addAtom(new Atom({
|
|
231
|
+
isotope: 'V',
|
|
232
|
+
position: this.sourceWallet.position!,
|
|
233
|
+
walletAddress: this.sourceWallet.address! as any,
|
|
234
|
+
token: this.sourceWallet.token,
|
|
235
|
+
value: -this.sourceWallet.balance
|
|
236
|
+
}))
|
|
237
|
+
|
|
238
|
+
// Initializing a new Atom to add tokens to recipient
|
|
239
|
+
this.addAtom(new Atom({
|
|
240
|
+
isotope: 'V',
|
|
241
|
+
position: recipientWallet.position!,
|
|
242
|
+
walletAddress: recipientWallet.address! as any,
|
|
243
|
+
token: recipientWallet.token,
|
|
244
|
+
value: amount,
|
|
245
|
+
metaType: 'walletBundle',
|
|
246
|
+
metaId: recipientWallet.bundle!
|
|
247
|
+
}))
|
|
248
|
+
|
|
249
|
+
// Initializing a remainder atom (matching JavaScript SDK - uses existing remainderWallet)
|
|
250
|
+
if (!this.remainderWallet) {
|
|
251
|
+
throw new Error('Remainder wallet required for value transfer')
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
this.addAtom(new Atom({
|
|
255
|
+
isotope: 'V',
|
|
256
|
+
position: this.remainderWallet.position!,
|
|
257
|
+
walletAddress: this.remainderWallet.address! as any,
|
|
258
|
+
token: this.remainderWallet.token,
|
|
259
|
+
value: this.sourceWallet.balance - amount,
|
|
260
|
+
metaType: 'walletBundle',
|
|
261
|
+
metaId: this.remainderWallet.bundle!
|
|
262
|
+
}))
|
|
263
|
+
|
|
264
|
+
return this
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Sign the molecule with one-time signature
|
|
269
|
+
* Matches JavaScript SDK sign method
|
|
270
|
+
*/
|
|
271
|
+
sign({
|
|
272
|
+
bundle = null,
|
|
273
|
+
anonymous = false,
|
|
274
|
+
compressed = true
|
|
275
|
+
}: {
|
|
276
|
+
bundle?: string | null
|
|
277
|
+
anonymous?: boolean
|
|
278
|
+
compressed?: boolean
|
|
279
|
+
} = {}): string | null {
|
|
280
|
+
// Do we have atoms?
|
|
281
|
+
if (this.atoms.length === 0 || this.atoms.filter(atom => !(atom instanceof Atom)).length !== 0) {
|
|
282
|
+
throw new AtomsMissingException()
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Derive the user's bundle
|
|
286
|
+
if (!anonymous && !this.bundle) {
|
|
287
|
+
this.bundle = bundle || generateBundleHash(this.secret!, 'Molecule::sign')
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Hash atoms to get molecular hash
|
|
291
|
+
this.molecularHash = Atom.hashAtoms({
|
|
292
|
+
atoms: this.atoms
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
// Signing atom
|
|
296
|
+
const signingAtom = this.atoms[0]
|
|
297
|
+
if (!signingAtom) {
|
|
298
|
+
throw new SignatureMalformedException('No atoms available for signing!')
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Set signing position from the first atom
|
|
302
|
+
let signingPosition = signingAtom.position
|
|
303
|
+
|
|
304
|
+
// Signing position is required
|
|
305
|
+
if (!signingPosition) {
|
|
306
|
+
throw new SignatureMalformedException('Signing wallet must have a position!')
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Generate the private signing key for this molecule
|
|
310
|
+
const key = Wallet.generateKey({
|
|
311
|
+
secret: this.secret!,
|
|
312
|
+
token: signingAtom.token as string,
|
|
313
|
+
position: signingPosition
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
// Use standardized WOTS+ signature generation from crypto library
|
|
317
|
+
// This ensures 100% compatibility with Implementation Guide requirements
|
|
318
|
+
let signatureFragments = generateOTSSignature(key, this.molecularHash)
|
|
319
|
+
|
|
320
|
+
// Compressing the OTS
|
|
321
|
+
if (compressed) {
|
|
322
|
+
signatureFragments = hexToBase64(signatureFragments)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Chunking the signature across multiple atoms
|
|
326
|
+
const chunkedSignature = chunkSubstr(signatureFragments, Math.ceil(signatureFragments.length / this.atoms.length))
|
|
327
|
+
|
|
328
|
+
let lastPosition: string | null = null
|
|
329
|
+
|
|
330
|
+
for (let chunkCount = 0, condition = chunkedSignature.length; chunkCount < condition; chunkCount++) {
|
|
331
|
+
const atom = this.atoms[chunkCount]
|
|
332
|
+
const signatureChunk = chunkedSignature[chunkCount]
|
|
333
|
+
if (atom && signatureChunk) {
|
|
334
|
+
atom.otsFragment = signatureChunk
|
|
335
|
+
lastPosition = atom.position
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return lastPosition
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Validate the molecular structure
|
|
344
|
+
* Uses CheckMolecule for validation
|
|
345
|
+
* Matches JavaScript SDK check method exactly
|
|
346
|
+
*/
|
|
347
|
+
check(senderWallet: Wallet | null = null): boolean {
|
|
348
|
+
return (new CheckMolecule(this as any)).verify(senderWallet)
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Convert Hm to numeric notation via EnumerateMolecule(Hm)
|
|
353
|
+
* Required by CheckMolecule for OTS validation
|
|
354
|
+
*/
|
|
355
|
+
normalizedHash(): number[] {
|
|
356
|
+
if (!this.molecularHash) {
|
|
357
|
+
throw new Error('Molecular hash not set')
|
|
358
|
+
}
|
|
359
|
+
return Molecule.normalize(Molecule.enumerate(this.molecularHash))
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Enumerate a hash string to numeric values
|
|
364
|
+
* Matches JavaScript SDK Molecule.enumerate exactly
|
|
365
|
+
*/
|
|
366
|
+
static enumerate(hash: string): number[] {
|
|
367
|
+
const mapped: Record<string, number> = {
|
|
368
|
+
'0': -8,
|
|
369
|
+
'1': -7,
|
|
370
|
+
'2': -6,
|
|
371
|
+
'3': -5,
|
|
372
|
+
'4': -4,
|
|
373
|
+
'5': -3,
|
|
374
|
+
'6': -2,
|
|
375
|
+
'7': -1,
|
|
376
|
+
'8': 0,
|
|
377
|
+
'9': 1,
|
|
378
|
+
'a': 2,
|
|
379
|
+
'b': 3,
|
|
380
|
+
'c': 4,
|
|
381
|
+
'd': 5,
|
|
382
|
+
'e': 6,
|
|
383
|
+
'f': 7,
|
|
384
|
+
'g': 8
|
|
385
|
+
}
|
|
386
|
+
const target: number[] = []
|
|
387
|
+
const hashList = hash.toLowerCase().split('')
|
|
388
|
+
|
|
389
|
+
for (let index = 0, len = hashList.length; index < len; ++index) {
|
|
390
|
+
const symbol = hashList[index]
|
|
391
|
+
|
|
392
|
+
if (symbol && typeof mapped[symbol] !== 'undefined') {
|
|
393
|
+
target[index] = mapped[symbol]!
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return target
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Normalize enumerated string to ensure sum equals zero
|
|
402
|
+
* Matches JavaScript SDK Molecule.normalize exactly
|
|
403
|
+
*/
|
|
404
|
+
static normalize(mappedHashArray: number[]): number[] {
|
|
405
|
+
let total = mappedHashArray.reduce((total, num) => total + num)
|
|
406
|
+
|
|
407
|
+
const totalCondition = total < 0
|
|
408
|
+
|
|
409
|
+
while (total < 0 || total > 0) {
|
|
410
|
+
for (const index of Object.keys(mappedHashArray)) {
|
|
411
|
+
const idx = Number(index)
|
|
412
|
+
const condition = totalCondition ? (mappedHashArray[idx] ?? 0) < 8 : (mappedHashArray[idx] ?? 0) > -8
|
|
413
|
+
|
|
414
|
+
if (condition) {
|
|
415
|
+
if (totalCondition) {
|
|
416
|
+
mappedHashArray[idx] = (mappedHashArray[idx] ?? 0) + 1
|
|
417
|
+
total++
|
|
418
|
+
} else {
|
|
419
|
+
mappedHashArray[idx] = (mappedHashArray[idx] ?? 0) - 1
|
|
420
|
+
total--
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (total === 0) {
|
|
424
|
+
break
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return mappedHashArray
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Returns JSON-ready object for cross-SDK compatibility (2025 TS best practices)
|
|
435
|
+
*
|
|
436
|
+
* Includes all necessary fields for cross-SDK validation while excluding sensitive data.
|
|
437
|
+
* Follows 2025 TypeScript best practices with proper error handling and type safety.
|
|
438
|
+
*
|
|
439
|
+
* @param options - Serialization options
|
|
440
|
+
* @param options.includeValidationContext - Include sourceWallet/remainderWallet for validation (default: true)
|
|
441
|
+
* @param options.includeOtsFragments - Include OTS signature fragments (default: true)
|
|
442
|
+
* @param options.secureMode - Extra security checks (default: false)
|
|
443
|
+
* @return JSON-serializable object
|
|
444
|
+
* @throws Error if molecule is in invalid state for serialization
|
|
445
|
+
*/
|
|
446
|
+
toJSON(options: {
|
|
447
|
+
includeValidationContext?: boolean
|
|
448
|
+
includeOtsFragments?: boolean
|
|
449
|
+
secureMode?: boolean
|
|
450
|
+
} = {}): any {
|
|
451
|
+
const {
|
|
452
|
+
includeValidationContext = true,
|
|
453
|
+
includeOtsFragments = true,
|
|
454
|
+
secureMode = false
|
|
455
|
+
} = options
|
|
456
|
+
|
|
457
|
+
try {
|
|
458
|
+
// Security check in secure mode
|
|
459
|
+
if (secureMode && this.secret) {
|
|
460
|
+
throw new Error('Cannot serialize molecule with secret in secure mode')
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Core molecule properties (always included)
|
|
464
|
+
const serialized: any = {
|
|
465
|
+
status: this.status,
|
|
466
|
+
molecularHash: this.molecularHash,
|
|
467
|
+
createdAt: this.createdAt,
|
|
468
|
+
cellSlug: this.cellSlug,
|
|
469
|
+
cellSlugOrigin: this.cellSlugOrigin,
|
|
470
|
+
version: this.version,
|
|
471
|
+
bundle: this.bundle,
|
|
472
|
+
|
|
473
|
+
// Serialized atoms array with optional OTS fragments
|
|
474
|
+
atoms: this.atoms.map(atom => atom.toJSON({
|
|
475
|
+
includeOtsFragments
|
|
476
|
+
}))
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Validation context (essential for cross-SDK validation)
|
|
480
|
+
if (includeValidationContext) {
|
|
481
|
+
if (this.sourceWallet) {
|
|
482
|
+
serialized.sourceWallet = {
|
|
483
|
+
address: this.sourceWallet.address,
|
|
484
|
+
position: this.sourceWallet.position,
|
|
485
|
+
token: this.sourceWallet.token,
|
|
486
|
+
balance: this.sourceWallet.balance || 0,
|
|
487
|
+
bundle: this.sourceWallet.bundle,
|
|
488
|
+
batchId: this.sourceWallet.batchId || null,
|
|
489
|
+
characters: this.sourceWallet.characters || 'BASE64',
|
|
490
|
+
// Exclude sensitive fields like secret, key, privkey
|
|
491
|
+
pubkey: this.sourceWallet.pubkey || null,
|
|
492
|
+
tokenUnits: this.sourceWallet.tokenUnits || [],
|
|
493
|
+
tradeRates: this.sourceWallet.tradeRates || {},
|
|
494
|
+
molecules: this.sourceWallet.molecules || {}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
if (this.remainderWallet) {
|
|
499
|
+
serialized.remainderWallet = {
|
|
500
|
+
address: this.remainderWallet.address,
|
|
501
|
+
position: this.remainderWallet.position,
|
|
502
|
+
token: this.remainderWallet.token,
|
|
503
|
+
balance: this.remainderWallet.balance || 0,
|
|
504
|
+
bundle: this.remainderWallet.bundle,
|
|
505
|
+
batchId: this.remainderWallet.batchId || null,
|
|
506
|
+
characters: this.remainderWallet.characters || 'BASE64',
|
|
507
|
+
// Exclude sensitive fields
|
|
508
|
+
pubkey: this.remainderWallet.pubkey || null,
|
|
509
|
+
tokenUnits: this.remainderWallet.tokenUnits || [],
|
|
510
|
+
tradeRates: this.remainderWallet.tradeRates || {},
|
|
511
|
+
molecules: this.remainderWallet.molecules || {}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
return serialized
|
|
517
|
+
|
|
518
|
+
} catch (error) {
|
|
519
|
+
throw new Error(`Molecule serialization failed: ${(error as Error).message}`)
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Returns the base cell slug portion
|
|
525
|
+
*/
|
|
526
|
+
cellSlugBase(): string {
|
|
527
|
+
return (this.cellSlug || '').split(this.cellSlugDelimiter)[0] || ''
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// =============================================================================
|
|
531
|
+
// STUB METHODS - Implement as needed (YAGNI)
|
|
532
|
+
// =============================================================================
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Initialize token creation
|
|
536
|
+
*/
|
|
537
|
+
initTokenCreation({
|
|
538
|
+
recipientWallet,
|
|
539
|
+
amount,
|
|
540
|
+
meta
|
|
541
|
+
}: {
|
|
542
|
+
recipientWallet: Wallet
|
|
543
|
+
amount: number
|
|
544
|
+
meta: any
|
|
545
|
+
}): Molecule {
|
|
546
|
+
// Create a new token by adding atoms
|
|
547
|
+
this.addAtom(new Atom({
|
|
548
|
+
isotope: 'T',
|
|
549
|
+
position: recipientWallet.position!,
|
|
550
|
+
walletAddress: recipientWallet.address! as any,
|
|
551
|
+
token: recipientWallet.token,
|
|
552
|
+
value: amount,
|
|
553
|
+
metaType: 'token',
|
|
554
|
+
metaId: recipientWallet.token,
|
|
555
|
+
meta: meta ? [{ key: 'tokenMeta', value: JSON.stringify(meta) }] : []
|
|
556
|
+
}))
|
|
557
|
+
|
|
558
|
+
// Add wallet bundle metadata
|
|
559
|
+
this.addAtom(new Atom({
|
|
560
|
+
isotope: 'M',
|
|
561
|
+
position: recipientWallet.position!,
|
|
562
|
+
walletAddress: recipientWallet.address! as any,
|
|
563
|
+
token: recipientWallet.token,
|
|
564
|
+
metaType: 'walletBundle',
|
|
565
|
+
metaId: recipientWallet.bundle!,
|
|
566
|
+
meta: meta ? [{ key: 'tokenMeta', value: JSON.stringify(meta) }] : []
|
|
567
|
+
}))
|
|
568
|
+
|
|
569
|
+
// Add ContinuID atom (matching JavaScript SDK)
|
|
570
|
+
this.addContinuIdAtom()
|
|
571
|
+
|
|
572
|
+
return this
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Burn tokens
|
|
577
|
+
*/
|
|
578
|
+
burnToken({
|
|
579
|
+
amount,
|
|
580
|
+
walletBundle = null
|
|
581
|
+
}: {
|
|
582
|
+
amount: number
|
|
583
|
+
walletBundle?: string | null
|
|
584
|
+
}): Molecule {
|
|
585
|
+
if (!this.sourceWallet) {
|
|
586
|
+
throw new Error('Source wallet required for token burning')
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
if (amount <= 0) {
|
|
590
|
+
throw new NegativeAmountException()
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
if (this.sourceWallet.balance - amount < 0) {
|
|
594
|
+
throw new BalanceInsufficientException()
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// Burn tokens by removing from source
|
|
598
|
+
this.addAtom(new Atom({
|
|
599
|
+
isotope: 'V',
|
|
600
|
+
position: this.sourceWallet.position!,
|
|
601
|
+
walletAddress: this.sourceWallet.address! as any,
|
|
602
|
+
token: this.sourceWallet.token,
|
|
603
|
+
value: -amount,
|
|
604
|
+
metaType: walletBundle ? 'walletBundle' : null,
|
|
605
|
+
metaId: walletBundle || null
|
|
606
|
+
}))
|
|
607
|
+
|
|
608
|
+
return this
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* Initialize meta
|
|
613
|
+
*/
|
|
614
|
+
initMeta({
|
|
615
|
+
meta,
|
|
616
|
+
metaType,
|
|
617
|
+
metaId,
|
|
618
|
+
policy
|
|
619
|
+
}: {
|
|
620
|
+
meta: any
|
|
621
|
+
metaType: string
|
|
622
|
+
metaId: string
|
|
623
|
+
policy?: any
|
|
624
|
+
}): Molecule {
|
|
625
|
+
if (!this.sourceWallet) {
|
|
626
|
+
throw new Error('Source wallet required for meta creation')
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
const metaArray: Array<{ key: string; value: any }> = []
|
|
630
|
+
|
|
631
|
+
// Convert meta to array format
|
|
632
|
+
if (meta) {
|
|
633
|
+
if (Array.isArray(meta)) {
|
|
634
|
+
metaArray.push(...meta)
|
|
635
|
+
} else if (typeof meta === 'object') {
|
|
636
|
+
for (const [key, value] of Object.entries(meta)) {
|
|
637
|
+
metaArray.push({ key, value })
|
|
638
|
+
}
|
|
639
|
+
} else {
|
|
640
|
+
metaArray.push({ key: 'value', value: meta })
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// Add policy if provided
|
|
645
|
+
if (policy) {
|
|
646
|
+
metaArray.push({ key: 'policy', value: JSON.stringify(policy) })
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Create meta atom
|
|
650
|
+
this.addAtom(new Atom({
|
|
651
|
+
isotope: 'M',
|
|
652
|
+
position: this.sourceWallet.position!,
|
|
653
|
+
walletAddress: this.sourceWallet.address! as any,
|
|
654
|
+
token: this.sourceWallet.token,
|
|
655
|
+
metaType,
|
|
656
|
+
metaId,
|
|
657
|
+
meta: metaArray
|
|
658
|
+
}))
|
|
659
|
+
|
|
660
|
+
// Add ContinuID atom (matching JavaScript SDK)
|
|
661
|
+
this.addContinuIdAtom()
|
|
662
|
+
|
|
663
|
+
return this
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* Initialize wallet creation
|
|
668
|
+
*/
|
|
669
|
+
initWalletCreation(wallet: Wallet, atomMeta: AtomMeta | null = null): Molecule {
|
|
670
|
+
// Create wallet creation atom
|
|
671
|
+
const atom = new Atom({
|
|
672
|
+
isotope: 'C',
|
|
673
|
+
position: wallet.position!,
|
|
674
|
+
walletAddress: wallet.address! as any,
|
|
675
|
+
token: wallet.token,
|
|
676
|
+
metaType: 'wallet',
|
|
677
|
+
metaId: wallet.address!,
|
|
678
|
+
meta: atomMeta ? atomMeta.get() as any : []
|
|
679
|
+
})
|
|
680
|
+
|
|
681
|
+
// Add wallet metadata
|
|
682
|
+
if (!atomMeta) {
|
|
683
|
+
atom.meta.push(
|
|
684
|
+
{ key: 'position', value: wallet.position! },
|
|
685
|
+
{ key: 'bundle', value: wallet.bundle! },
|
|
686
|
+
{ key: 'token', value: wallet.token },
|
|
687
|
+
{ key: 'batchId', value: wallet.batchId || '' },
|
|
688
|
+
{ key: 'characters', value: wallet.characters || '' }
|
|
689
|
+
)
|
|
690
|
+
|
|
691
|
+
if (wallet.pubkey) {
|
|
692
|
+
atom.meta.push({ key: 'pubkey', value: wallet.pubkey })
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
this.addAtom(atom)
|
|
697
|
+
|
|
698
|
+
// Add ContinuID atom (matching JavaScript SDK)
|
|
699
|
+
this.addContinuIdAtom()
|
|
700
|
+
|
|
701
|
+
return this
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* Creates a Molecule instance from JSON data (2025 TS best practices)
|
|
706
|
+
*
|
|
707
|
+
* Handles cross-SDK deserialization with robust error handling and validation.
|
|
708
|
+
* Essential for cross-platform molecule validation and compatibility testing.
|
|
709
|
+
*
|
|
710
|
+
* @param json - JSON string or object to deserialize
|
|
711
|
+
* @param options - Deserialization options
|
|
712
|
+
* @param options.includeValidationContext - Reconstruct sourceWallet/remainderWallet (default: true)
|
|
713
|
+
* @param options.validateStructure - Validate required fields (default: true)
|
|
714
|
+
* @param options.strictMode - Strict validation mode (default: false)
|
|
715
|
+
* @return Reconstructed molecule instance
|
|
716
|
+
* @throws Error if JSON is invalid or required fields are missing
|
|
717
|
+
*/
|
|
718
|
+
static fromJSON(json: string | any, options: {
|
|
719
|
+
includeValidationContext?: boolean
|
|
720
|
+
validateStructure?: boolean
|
|
721
|
+
strictMode?: boolean
|
|
722
|
+
} = {}): Molecule {
|
|
723
|
+
const {
|
|
724
|
+
includeValidationContext = true,
|
|
725
|
+
validateStructure = true,
|
|
726
|
+
strictMode = false
|
|
727
|
+
} = options
|
|
728
|
+
|
|
729
|
+
try {
|
|
730
|
+
// Parse JSON safely
|
|
731
|
+
const data = typeof json === 'string' ? JSON.parse(json) : json
|
|
732
|
+
|
|
733
|
+
// Validate required fields in strict mode
|
|
734
|
+
if (strictMode || validateStructure) {
|
|
735
|
+
if (!data.molecularHash || !Array.isArray(data.atoms)) {
|
|
736
|
+
throw new Error('Invalid molecule data: missing molecularHash or atoms array')
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// Create minimal molecule instance (never include secret from JSON)
|
|
741
|
+
const molecule = new Molecule({
|
|
742
|
+
secret: null,
|
|
743
|
+
bundle: data.bundle || null,
|
|
744
|
+
cellSlug: data.cellSlug || null,
|
|
745
|
+
version: data.version || null
|
|
746
|
+
})
|
|
747
|
+
|
|
748
|
+
// Populate core properties
|
|
749
|
+
molecule.status = data.status
|
|
750
|
+
molecule.molecularHash = data.molecularHash
|
|
751
|
+
molecule.createdAt = data.createdAt || String(+new Date())
|
|
752
|
+
molecule.cellSlugOrigin = data.cellSlugOrigin
|
|
753
|
+
|
|
754
|
+
// Reconstruct atoms array with proper Atom instances
|
|
755
|
+
if (Array.isArray(data.atoms)) {
|
|
756
|
+
molecule.atoms = data.atoms.map((atomData: any, index: number) => {
|
|
757
|
+
try {
|
|
758
|
+
return Atom.fromJSON(atomData)
|
|
759
|
+
} catch (error) {
|
|
760
|
+
throw new Error(`Failed to reconstruct atom ${index}: ${(error as Error).message}`)
|
|
761
|
+
}
|
|
762
|
+
})
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// Reconstruct validation context if available and requested
|
|
766
|
+
if (includeValidationContext) {
|
|
767
|
+
if (data.sourceWallet) {
|
|
768
|
+
// Create source wallet for validation (without secret for security)
|
|
769
|
+
molecule.sourceWallet = new Wallet({
|
|
770
|
+
secret: null,
|
|
771
|
+
token: data.sourceWallet.token,
|
|
772
|
+
position: data.sourceWallet.position,
|
|
773
|
+
bundle: data.sourceWallet.bundle,
|
|
774
|
+
batchId: data.sourceWallet.batchId,
|
|
775
|
+
characters: data.sourceWallet.characters
|
|
776
|
+
})
|
|
777
|
+
|
|
778
|
+
// Set additional properties for validation context
|
|
779
|
+
molecule.sourceWallet.balance = data.sourceWallet.balance || 0
|
|
780
|
+
molecule.sourceWallet.address = data.sourceWallet.address as any
|
|
781
|
+
if (data.sourceWallet.pubkey) {
|
|
782
|
+
molecule.sourceWallet.pubkey = data.sourceWallet.pubkey
|
|
783
|
+
}
|
|
784
|
+
molecule.sourceWallet.tokenUnits = data.sourceWallet.tokenUnits || []
|
|
785
|
+
molecule.sourceWallet.tradeRates = data.sourceWallet.tradeRates || {}
|
|
786
|
+
molecule.sourceWallet.molecules = data.sourceWallet.molecules || {}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
if (data.remainderWallet) {
|
|
790
|
+
// Create remainder wallet for validation (without secret for security)
|
|
791
|
+
molecule.remainderWallet = new Wallet({
|
|
792
|
+
secret: null,
|
|
793
|
+
token: data.remainderWallet.token,
|
|
794
|
+
position: data.remainderWallet.position,
|
|
795
|
+
bundle: data.remainderWallet.bundle,
|
|
796
|
+
batchId: data.remainderWallet.batchId,
|
|
797
|
+
characters: data.remainderWallet.characters
|
|
798
|
+
})
|
|
799
|
+
|
|
800
|
+
// Set additional properties for validation context
|
|
801
|
+
molecule.remainderWallet.balance = data.remainderWallet.balance || 0
|
|
802
|
+
molecule.remainderWallet.address = data.remainderWallet.address as any
|
|
803
|
+
if (data.remainderWallet.pubkey) {
|
|
804
|
+
molecule.remainderWallet.pubkey = data.remainderWallet.pubkey
|
|
805
|
+
}
|
|
806
|
+
molecule.remainderWallet.tokenUnits = data.remainderWallet.tokenUnits || []
|
|
807
|
+
molecule.remainderWallet.tradeRates = data.remainderWallet.tradeRates || {}
|
|
808
|
+
molecule.remainderWallet.molecules = data.remainderWallet.molecules || {}
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
return molecule
|
|
813
|
+
|
|
814
|
+
} catch (error) {
|
|
815
|
+
throw new Error(`Molecule deserialization failed: ${(error as Error).message}`)
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
/**
|
|
820
|
+
* Convert JSON to Molecule instance - legacy method for compatibility
|
|
821
|
+
*/
|
|
822
|
+
static jsonToObject(json: string): Molecule {
|
|
823
|
+
return Molecule.fromJSON(json)
|
|
824
|
+
}
|
|
825
|
+
}
|