@typemove/move 1.0.0-rc.10

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