@typemove/move 1.0.0-rc.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,684 @@
1
+ import {
2
+ InternalMoveFunction,
3
+ InternalMoveFunctionVisibility,
4
+ InternalMoveModule,
5
+ InternalMoveStruct,
6
+ } from './internal-models.js'
7
+ import path from 'path'
8
+ import fs from 'fs'
9
+ import { AccountModulesImportInfo, AccountRegister } from './account.js'
10
+ import chalk from 'chalk'
11
+ import { format } from 'prettier'
12
+ import {
13
+ isFrameworkAccount,
14
+ moduleQname,
15
+ normalizeToJSName,
16
+ SPLITTER,
17
+ upperFirst,
18
+ VECTOR_STR,
19
+ } from './utils.js'
20
+ import { camel } from 'radash'
21
+ import { TypeDescriptor } from './types.js'
22
+ import { ChainAdapter } from './chain-adapter.js'
23
+
24
+ interface OutputFile {
25
+ fileName: string
26
+ fileContent: string
27
+ }
28
+
29
+ interface Config<NetworkType> {
30
+ fileName: string
31
+ outputDir: string
32
+ network: NetworkType
33
+ }
34
+
35
+ // TODO be able to generate cjs
36
+ export abstract class AbstractCodegen<NetworkType, ModuleTypes, StructType> {
37
+ TEST_NET: NetworkType
38
+ MAIN_NET: NetworkType
39
+ ADDRESS_TYPE: string
40
+ PREFIX: string
41
+ STRUCT_FIELD_NAME: string = 'data'
42
+ GENERATE_CLIENT = false
43
+ GENERATE_ON_ENTRY = true
44
+ PAYLOAD_OPTIONAL = false
45
+ SYSTEM_MODULES = new Set(['0x1', '0x2', '0x3'])
46
+ ESM = true
47
+
48
+ chainAdapter: ChainAdapter<NetworkType, ModuleTypes, StructType>
49
+
50
+ protected constructor(
51
+ chainAdapter: ChainAdapter<NetworkType, ModuleTypes, StructType>
52
+ ) {
53
+ this.chainAdapter = chainAdapter
54
+ }
55
+
56
+ public maybeEsmPrefix() {
57
+ return this.ESM ? '.js' : ''
58
+ }
59
+
60
+ readModulesFile(fullPath: string) {
61
+ return JSON.parse(fs.readFileSync(fullPath, 'utf-8'))
62
+ }
63
+
64
+ async generate(
65
+ srcDir: string,
66
+ outputDir: string,
67
+ builtin = false
68
+ ): Promise<number> {
69
+ const num1 = await this.generateForNetwork(
70
+ srcDir,
71
+ outputDir,
72
+ this.MAIN_NET,
73
+ builtin
74
+ )
75
+ const num2 = await this.generateForNetwork(
76
+ path.join(srcDir, 'testnet'),
77
+ path.join(outputDir, 'testnet'),
78
+ this.TEST_NET,
79
+ builtin
80
+ )
81
+ return num1 + num2
82
+ }
83
+
84
+ async generateForNetwork(
85
+ srcDir: string,
86
+ outputDir: string,
87
+ network: NetworkType,
88
+ builtin = false
89
+ ) {
90
+ if (!fs.existsSync(srcDir)) {
91
+ return 0
92
+ }
93
+
94
+ const files = fs.readdirSync(srcDir)
95
+ outputDir = path.resolve(outputDir)
96
+ const outputs: OutputFile[] = []
97
+
98
+ fs.mkdirSync(outputDir, { recursive: true })
99
+
100
+ const loader = new AccountRegister()
101
+
102
+ // when generating user code, don't need to generate framework account
103
+ for (const sysModule of this.SYSTEM_MODULES) {
104
+ loader.accountImports.set(
105
+ sysModule,
106
+ new AccountModulesImportInfo(sysModule, sysModule)
107
+ )
108
+ }
109
+ // const client = getRpcClient(network)
110
+
111
+ for (const file of files) {
112
+ if (!file.endsWith('.json')) {
113
+ continue
114
+ }
115
+ const fullPath = path.resolve(srcDir, file)
116
+ const abi = this.readModulesFile(fullPath)
117
+ const modules = this.chainAdapter.toInternalModules(abi)
118
+
119
+ for (const module of modules) {
120
+ loader.register(module, path.basename(file, '.json'))
121
+ }
122
+ const codeGen = new AccountCodegen(this, loader, abi, modules, {
123
+ fileName: path.basename(file, '.json'),
124
+ outputDir: outputDir,
125
+ network,
126
+ })
127
+
128
+ outputs.push(...codeGen.generate())
129
+ }
130
+
131
+ while (loader.pendingAccounts.size > 0) {
132
+ for (const account of loader.pendingAccounts) {
133
+ console.log(
134
+ `download dependent module for account ${account} at ${network}`
135
+ )
136
+
137
+ try {
138
+ const rawModules = await this.chainAdapter.fetchModules(
139
+ account,
140
+ network
141
+ )
142
+ const modules = this.chainAdapter.toInternalModules(rawModules)
143
+
144
+ fs.writeFileSync(
145
+ path.resolve(srcDir, account + '.json'),
146
+ JSON.stringify(rawModules, null, '\t')
147
+ )
148
+ for (const module of modules) {
149
+ loader.register(module, account)
150
+ }
151
+ const codeGen = new AccountCodegen(
152
+ this,
153
+ loader,
154
+ rawModules,
155
+ modules,
156
+ {
157
+ fileName: account,
158
+ outputDir: outputDir,
159
+ network,
160
+ }
161
+ )
162
+
163
+ outputs.push(...codeGen.generate())
164
+ } catch (e) {
165
+ console.error(
166
+ chalk.red(
167
+ 'Error downloading account module, check if you choose the right network,or download account modules manually into your director'
168
+ )
169
+ )
170
+ console.error(e)
171
+ process.exit(1)
172
+ }
173
+ }
174
+ }
175
+
176
+ for (const output of outputs) {
177
+ // const content = output.fileContent
178
+ const content = format(output.fileContent, { parser: 'typescript' })
179
+ fs.writeFileSync(path.join(outputDir, output.fileName), content)
180
+ }
181
+
182
+ const rootFile = path.join(outputDir, 'index.ts')
183
+ let rootFileContent = `/* Autogenerated file. Do not edit manually. */
184
+ /* tslint:disable */
185
+ /* eslint-disable */
186
+ `
187
+ for (const output of outputs) {
188
+ const parsed = path.parse(output.fileName)
189
+ rootFileContent += `export * as _${parsed.name.replaceAll(
190
+ '-',
191
+ '_'
192
+ )} from './${parsed.name}${this.maybeEsmPrefix()}\n`
193
+ }
194
+ fs.writeFileSync(rootFile, rootFileContent)
195
+
196
+ return outputs.length + 1
197
+ }
198
+
199
+ generateNetworkOption(network: NetworkType) {
200
+ switch (network) {
201
+ case this.TEST_NET:
202
+ return 'TEST_NET'
203
+ }
204
+ return 'MAIN_NET'
205
+ }
206
+
207
+ generateModule(
208
+ module: InternalMoveModule,
209
+ allEventStructs: Map<string, InternalMoveStruct>,
210
+ network: NetworkType
211
+ ) {
212
+ const qname = moduleQname(module)
213
+ const functions = this.GENERATE_ON_ENTRY
214
+ ? module.exposedFunctions
215
+ .map((f) => this.generateForEntryFunctions(module, f))
216
+ .filter((s) => s !== '')
217
+ : []
218
+ const clientFunctions = this.GENERATE_CLIENT
219
+ ? module.exposedFunctions
220
+ .map((f) => this.generateClientFunctions(module, f))
221
+ .filter((s) => s !== '')
222
+ : []
223
+ const eventStructs = new Map<string, InternalMoveStruct>()
224
+ for (const [type, struct] of allEventStructs.entries()) {
225
+ if (type.startsWith(qname + SPLITTER)) {
226
+ eventStructs.set(type, struct)
227
+ }
228
+ }
229
+
230
+ const eventTypes = new Set(eventStructs.keys())
231
+ const events = Array.from(eventStructs.values())
232
+ .map((e) => this.generateForEvents(module, e))
233
+ .filter((s) => s !== '')
234
+ const structs = module.structs.map((s) =>
235
+ this.generateStructs(module, s, eventTypes)
236
+ )
237
+ const callArgs = module.exposedFunctions.map((f) =>
238
+ this.generateCallArgsStructs(module, f)
239
+ )
240
+
241
+ const moduleName = normalizeToJSName(module.name)
242
+ let processor = ''
243
+ let client = ''
244
+
245
+ if (clientFunctions.length > 0) {
246
+ client = `
247
+ export class ${moduleName}_client extends ModuleClient {
248
+ ${clientFunctions.join('\n')}
249
+ }
250
+ `
251
+ }
252
+
253
+ if (functions.length > 0 || events.length > 0) {
254
+ processor = `export class ${moduleName} extends ${
255
+ this.PREFIX
256
+ }BaseProcessor {
257
+
258
+ constructor(options: ${this.PREFIX}BindOptions) {
259
+ super("${module.name}", options)
260
+ }
261
+ static DEFAULT_OPTIONS: ${this.PREFIX}BindOptions = {
262
+ address: "${module.address}",
263
+ network: ${this.PREFIX}Network.${this.generateNetworkOption(network)}
264
+ }
265
+
266
+ static bind(options: Partial<${
267
+ this.PREFIX
268
+ }BindOptions> = {}): ${moduleName} {
269
+ return new ${moduleName}({ ...${moduleName}.DEFAULT_OPTIONS, ...options })
270
+ }
271
+
272
+ ${functions.join('\n')}
273
+
274
+ ${events.join('\n')}
275
+ }
276
+ `
277
+ }
278
+
279
+ return `
280
+ ${client}
281
+
282
+ ${processor}
283
+
284
+ export namespace ${moduleName} {
285
+ ${structs.join('\n')}
286
+
287
+ ${callArgs.join('\n')}
288
+ }
289
+ `
290
+ }
291
+
292
+ generateStructs(
293
+ module: InternalMoveModule,
294
+ struct: InternalMoveStruct,
295
+ events: Set<string>,
296
+ typeOnly = false
297
+ ) {
298
+ const typeParams = struct.typeParams || []
299
+ const genericString = this.generateStructTypeParameters(struct)
300
+ const genericStringAny = this.generateStructTypeParameters(struct, true)
301
+
302
+ const structName = normalizeToJSName(struct.name)
303
+
304
+ const fields = struct.fields.map((field) => {
305
+ const type = this.generateTypeForDescriptor(field.type, module.address)
306
+ return `${field.name}: ${type}`
307
+ })
308
+
309
+ const typeParamApplyArg = typeParams
310
+ .map((v, idx) => {
311
+ return `arg${idx}: TypeDescriptor<T${idx}> = ANY_TYPE`
312
+ })
313
+ .join(',')
314
+ const typeParamApply = typeParams
315
+ .map((v, idx) => {
316
+ return `arg${idx}`
317
+ })
318
+ .join(',')
319
+
320
+ const typeDescriptor = `
321
+ export namespace ${structName}{
322
+ export const TYPE_QNAME = '${module.address}::${module.name}::${struct.name}'
323
+
324
+ const TYPE = new TypeDescriptor<${structName}${genericStringAny}>(${structName}.TYPE_QNAME)
325
+
326
+ export function type${genericString}(${typeParamApplyArg}): TypeDescriptor<${structName}${genericString}> {
327
+ return TYPE.apply(${typeParamApply})
328
+ }
329
+ }
330
+ `
331
+ if (typeOnly) {
332
+ return typeDescriptor
333
+ }
334
+
335
+ let eventPayload = ''
336
+ if (events.has(moduleQname(module) + SPLITTER + struct.name)) {
337
+ eventPayload = `
338
+ export interface ${structName}Instance extends
339
+ TypedEventInstance<${structName}${genericStringAny}> {
340
+ ${this.STRUCT_FIELD_NAME}_decoded: ${structName}${genericStringAny}
341
+ type_arguments: [${struct.typeParams.map((_) => 'string').join(', ')}]
342
+ }
343
+ `
344
+ }
345
+
346
+ return `
347
+ export interface ${structName}${genericString} {
348
+ ${fields.join('\n')}
349
+ }
350
+
351
+ ${typeDescriptor}
352
+
353
+ ${eventPayload}
354
+ `
355
+ }
356
+
357
+ generateFunctionTypeParameters(func: InternalMoveFunction) {
358
+ let genericString = ''
359
+ if (func.typeParams && func.typeParams.length > 0) {
360
+ const params = func.typeParams
361
+ .map((v, idx) => {
362
+ return `T${idx}=any`
363
+ })
364
+ .join(',')
365
+ genericString = `<${params}>`
366
+ }
367
+ return genericString
368
+ }
369
+
370
+ generateStructTypeParameters(struct: InternalMoveStruct, useAny = false) {
371
+ let genericString = ''
372
+
373
+ if (struct.typeParams && struct.typeParams.length > 0) {
374
+ const params = struct.typeParams
375
+ .map((v, idx) => {
376
+ return useAny ? 'any' : 'T' + idx
377
+ })
378
+ .join(',')
379
+ genericString = `<${params}>`
380
+ }
381
+ return genericString
382
+ }
383
+
384
+ generateCallArgsStructs(
385
+ module: InternalMoveModule,
386
+ func: InternalMoveFunction
387
+ ) {
388
+ if (!func.isEntry) {
389
+ return
390
+ }
391
+
392
+ const fields = this.chainAdapter
393
+ .getMeaningfulFunctionParams(func.params)
394
+ .map((param) => {
395
+ return (
396
+ this.generateTypeForDescriptor(param, module.address) +
397
+ (this.PAYLOAD_OPTIONAL ? ' | undefined' : '')
398
+ )
399
+ })
400
+
401
+ const camelFuncName = upperFirst(camel(func.name))
402
+
403
+ const genericString = this.generateFunctionTypeParameters(func)
404
+ return `
405
+ export interface ${camelFuncName}Payload${genericString}
406
+ extends TypedFunctionPayload<[${fields.join(',')}]> {
407
+ arguments_decoded: [${fields.join(',')}],
408
+ type_arguments: [${func.typeParams.map((_) => 'string').join(', ')}]
409
+ }
410
+ `
411
+ }
412
+
413
+ generateClientFunctions(
414
+ module: InternalMoveModule,
415
+ func: InternalMoveFunction
416
+ ) {
417
+ if (func.visibility === InternalMoveFunctionVisibility.PRIVATE) {
418
+ return ''
419
+ }
420
+ if (func.isEntry) {
421
+ return ''
422
+ }
423
+ // const moduleName = normalizeToJSName(module.name)
424
+ const funcName = camel(func.name)
425
+ const fields = this.chainAdapter
426
+ .getMeaningfulFunctionParams(func.params)
427
+ .map((param) => {
428
+ return this.generateTypeForDescriptor(param, module.address)
429
+ })
430
+ const genericString = this.generateFunctionTypeParameters(func)
431
+
432
+ const returns = func.return.map((param) => {
433
+ return this.generateTypeForDescriptor(param, module.address)
434
+ })
435
+
436
+ const source = `
437
+ ${funcName}${genericString}(type_arguments: [${func.typeParams
438
+ .map((_) => 'string')
439
+ .join(', ')}], args: [${fields.join(
440
+ ','
441
+ )}], version?: bigint): Promise<[${returns.join(',')}]> {
442
+ return this.viewDecoded('${module.address}::${module.name}::${
443
+ func.name
444
+ }', type_arguments, args, version) as any
445
+ }`
446
+ return source
447
+ }
448
+
449
+ generateForEntryFunctions(
450
+ module: InternalMoveModule,
451
+ func: InternalMoveFunction
452
+ ) {
453
+ return ''
454
+ }
455
+
456
+ generateForEvents(
457
+ module: InternalMoveModule,
458
+ struct: InternalMoveStruct
459
+ ): string {
460
+ return ''
461
+ }
462
+
463
+ generateTypeForDescriptor(
464
+ type: TypeDescriptor,
465
+ currentAddress: string
466
+ ): string {
467
+ if (type.reference) {
468
+ return this.ADDRESS_TYPE
469
+ }
470
+
471
+ switch (type.qname) {
472
+ case 'signer': // TODO check this
473
+ case 'address':
474
+ case 'Address':
475
+ return this.ADDRESS_TYPE
476
+ case '0x1::string::String':
477
+ return 'string'
478
+ case 'bool':
479
+ case 'Bool':
480
+ return 'Boolean'
481
+ case 'u8':
482
+ case 'U8':
483
+ case 'u16':
484
+ case 'U16':
485
+ case 'u32':
486
+ case 'U32':
487
+ return 'number'
488
+ case 'u64':
489
+ case 'U64':
490
+ case 'u128':
491
+ case 'U128':
492
+ case 'u256':
493
+ case 'U256':
494
+ return 'bigint'
495
+ }
496
+
497
+ if (type.qname.toLowerCase() === VECTOR_STR) {
498
+ // vector<u8> as hex string
499
+ const elementTypeQname = type.typeArgs[0].qname
500
+ if (elementTypeQname === 'u8') {
501
+ // only for aptos
502
+ return 'string'
503
+ }
504
+ if (
505
+ elementTypeQname.startsWith('T') &&
506
+ !elementTypeQname.includes(SPLITTER)
507
+ ) {
508
+ return `${elementTypeQname}[] | string`
509
+ }
510
+ return (
511
+ this.generateTypeForDescriptor(type.typeArgs[0], currentAddress) + '[]'
512
+ )
513
+ }
514
+
515
+ const simpleName = this.generateSimpleType(type.qname, currentAddress)
516
+ if (simpleName.length === 0) {
517
+ console.error('unexpected error')
518
+ }
519
+ if (
520
+ simpleName.toLowerCase() === VECTOR_STR ||
521
+ simpleName.toLowerCase().startsWith(VECTOR_STR + SPLITTER)
522
+ ) {
523
+ console.error('unexpected vector type error')
524
+ }
525
+ if (type.typeArgs.length > 0) {
526
+ // return simpleName
527
+ return (
528
+ simpleName +
529
+ '<' +
530
+ type.typeArgs
531
+ .map((t) => this.generateTypeForDescriptor(t, currentAddress))
532
+ .join(',') +
533
+ '>'
534
+ )
535
+ }
536
+ return simpleName
537
+ }
538
+
539
+ generateSimpleType(type: string, currentAddress: string): string {
540
+ const parts = type.split(SPLITTER)
541
+
542
+ for (let i = 0; i < parts.length; i++) {
543
+ parts[i] = normalizeToJSName(parts[i])
544
+ }
545
+
546
+ if (parts.length < 2) {
547
+ return parts[0]
548
+ }
549
+ if (parts[0] === currentAddress) {
550
+ return parts.slice(1).join('.')
551
+ }
552
+ return '_' + parts.join('.')
553
+ }
554
+ }
555
+
556
+ export class AccountCodegen<NetworkType, ModuleType, StructType> {
557
+ modules: InternalMoveModule[]
558
+ config: Config<NetworkType>
559
+ abi: ModuleType[]
560
+ loader: AccountRegister
561
+ moduleGen: AbstractCodegen<NetworkType, ModuleType, StructType>
562
+
563
+ constructor(
564
+ moduleGen: AbstractCodegen<NetworkType, ModuleType, StructType>,
565
+ loader: AccountRegister,
566
+ abi: ModuleType[],
567
+ modules: InternalMoveModule[],
568
+ config: Config<NetworkType>
569
+ ) {
570
+ // const json = fs.readFileSync(config.srcFile, 'utf-8')
571
+ this.moduleGen = moduleGen
572
+ this.abi = abi
573
+ this.modules = modules
574
+ this.config = config
575
+ this.loader = loader
576
+ }
577
+
578
+ generate(): OutputFile[] {
579
+ if (!this.modules) {
580
+ return []
581
+ }
582
+ // const baseName = path.basename(this.config.fileName, '.json')
583
+
584
+ let address: string | undefined
585
+ for (const module of this.modules) {
586
+ address = module.address
587
+ }
588
+ if (!address) {
589
+ return []
590
+ }
591
+
592
+ const dependedAccounts: string[] = []
593
+
594
+ const moduleImports: string[] = []
595
+
596
+ const info = this.loader.accountImports.get(address)
597
+
598
+ if (info) {
599
+ for (const [account] of info.imports.entries()) {
600
+ // Remap to user's filename if possible, TODO codepath not well tested
601
+ const tsAccountModule =
602
+ './' +
603
+ (this.loader.accountImports.get(account)?.moduleName || account)
604
+ if (isFrameworkAccount(account) && !isFrameworkAccount(address)) {
605
+ // Decide where to find runtime library
606
+ moduleImports.push(
607
+ `import { _${account} } from "@typemove/${this.moduleGen.PREFIX.toLowerCase()}"`
608
+ )
609
+ } else {
610
+ moduleImports.push(
611
+ `import * as _${account} from "${tsAccountModule}${this.moduleGen.maybeEsmPrefix()}"`
612
+ )
613
+ }
614
+
615
+ dependedAccounts.push(account)
616
+ }
617
+ }
618
+
619
+ let loadAllTypes = `loadAllTypes(defaultMoveCoder(${
620
+ this.moduleGen.PREFIX
621
+ }Network.${this.moduleGen.generateNetworkOption(this.config.network)}))`
622
+
623
+ if (this.moduleGen.SYSTEM_MODULES.has(address)) {
624
+ loadAllTypes = `
625
+ loadAllTypes(defaultMoveCoder(${this.moduleGen.PREFIX}Network.MAIN_NET))
626
+ loadAllTypes(defaultMoveCoder(${this.moduleGen.PREFIX}Network.TEST_NET))
627
+ `
628
+ }
629
+
630
+ const eventsMap: Map<string, InternalMoveStruct> =
631
+ this.moduleGen.chainAdapter.getAllEventStructs(this.modules)
632
+
633
+ const source = `
634
+ /* Autogenerated file. Do not edit manually. */
635
+ /* tslint:disable */
636
+ /* eslint-disable */
637
+
638
+ /* Generated modules for account ${address} */
639
+
640
+ ${this.generateImports()}
641
+
642
+ ${moduleImports.join('\n')}
643
+
644
+ ${this.modules
645
+ .map((m) =>
646
+ this.moduleGen.generateModule(m, eventsMap, this.config.network)
647
+ )
648
+ .join('\n')}
649
+
650
+ const MODULES = JSON.parse('${JSON.stringify(this.abi)}')
651
+
652
+ export function loadAllTypes(coder: MoveCoder) {
653
+ ${dependedAccounts.map((a) => `_${a}.loadAllTypes(coder)`).join('\n')}
654
+ for (const m of Object.values(MODULES)) {
655
+ coder.load(m as any)
656
+ }
657
+ }
658
+
659
+ ${loadAllTypes}
660
+ ` // source
661
+
662
+ return [
663
+ {
664
+ fileName: this.config.fileName + '.ts',
665
+ fileContent: source,
666
+ },
667
+ ]
668
+ }
669
+
670
+ generateImports() {
671
+ const imports = `
672
+ import { TypeDescriptor, ANY_TYPE } from "@typemove/move"
673
+ import {
674
+ MoveCoder, defaultMoveCoder, ${
675
+ this.moduleGen.PREFIX
676
+ }Network } from "@typemove/${this.moduleGen.PREFIX.toLowerCase()}"
677
+ import { ${
678
+ this.moduleGen.ADDRESS_TYPE
679
+ }, ModuleClient } from "@typemove/${this.moduleGen.PREFIX.toLowerCase()}"
680
+ `
681
+
682
+ return imports
683
+ }
684
+ }