@alephium/web3 0.5.0-rc.1 → 0.5.0-rc.3
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/dist/alephium-web3.min.js +1 -1
- package/dist/alephium-web3.min.js.map +1 -1
- package/dist/src/constants.d.ts +1 -0
- package/dist/src/constants.js +2 -1
- package/dist/src/contract/codegen.d.ts +2 -0
- package/dist/src/contract/codegen.js +359 -0
- package/dist/src/contract/contract.js +2 -0
- package/package.json +1 -1
- package/src/constants.ts +1 -0
- package/src/contract/codegen.ts +362 -0
- package/src/contract/contract.ts +2 -0
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2018 - 2022 The Alephium Authors
|
|
3
|
+
This file is part of the alephium project.
|
|
4
|
+
|
|
5
|
+
The library is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU Lesser General Public License as published by
|
|
7
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
(at your option) any later version.
|
|
9
|
+
|
|
10
|
+
The library is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU Lesser General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU Lesser General Public License
|
|
16
|
+
along with the library. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import * as prettier from 'prettier'
|
|
20
|
+
import path from 'path'
|
|
21
|
+
import fs from 'fs'
|
|
22
|
+
import { Contract, EventSig, FunctionSig, Project, Script } from './contract'
|
|
23
|
+
|
|
24
|
+
const header = `/* Autogenerated file. Do not edit manually. */\n/* tslint:disable */\n/* eslint-disable */\n\n`
|
|
25
|
+
|
|
26
|
+
function array(str: string, size: number): string {
|
|
27
|
+
const result = Array(size).fill(str).join(', ')
|
|
28
|
+
return `[${result}]`
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function parseArrayType(tpe: string): string {
|
|
32
|
+
const ignored = '[;]'
|
|
33
|
+
const tokens: string[] = []
|
|
34
|
+
let acc = ''
|
|
35
|
+
for (let index = 0; index < tpe.length; index++) {
|
|
36
|
+
if (!ignored.includes(tpe.charAt(index))) {
|
|
37
|
+
acc = acc + tpe.charAt(index)
|
|
38
|
+
} else if (acc !== '') {
|
|
39
|
+
tokens.push(acc)
|
|
40
|
+
acc = ''
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const baseTsType = toTsType(tokens[0])
|
|
44
|
+
const sizes = tokens.slice(1).map((str) => parseInt(str))
|
|
45
|
+
return sizes.reduce((acc, size) => array(acc, size), baseTsType)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function toTsType(ralphType: string): string {
|
|
49
|
+
switch (ralphType) {
|
|
50
|
+
case 'U256':
|
|
51
|
+
case 'I256':
|
|
52
|
+
return 'bigint'
|
|
53
|
+
case 'Bool':
|
|
54
|
+
return 'boolean'
|
|
55
|
+
case 'Address':
|
|
56
|
+
case 'ByteVec':
|
|
57
|
+
return 'HexString'
|
|
58
|
+
default: // array type
|
|
59
|
+
return parseArrayType(ralphType)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function formatParameters(fieldsSig: { names: string[]; types: string[] }): string {
|
|
64
|
+
return fieldsSig.names.map((name, idx) => `${name}: ${toTsType(fieldsSig.types[`${idx}`])}`).join(', ')
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function genCallMethod(contractName: string, functionSig: FunctionSig): string {
|
|
68
|
+
if (!functionSig.isPublic || functionSig.returnTypes.length === 0) {
|
|
69
|
+
return ''
|
|
70
|
+
}
|
|
71
|
+
const funcName = functionSig.name.charAt(0).toUpperCase() + functionSig.name.slice(1)
|
|
72
|
+
const funcHasArgs = functionSig.paramNames.length > 0
|
|
73
|
+
const params = funcHasArgs
|
|
74
|
+
? `params: CallContractParams<{${formatParameters({
|
|
75
|
+
names: functionSig.paramNames,
|
|
76
|
+
types: functionSig.paramTypes
|
|
77
|
+
})}}>`
|
|
78
|
+
: `params?: Omit<CallContractParams<{}>, 'args'>`
|
|
79
|
+
const tsReturnTypes = functionSig.returnTypes.map((tpe) => toTsType(tpe))
|
|
80
|
+
const retType =
|
|
81
|
+
tsReturnTypes.length === 0
|
|
82
|
+
? `CallContractResult<null>`
|
|
83
|
+
: tsReturnTypes.length === 1
|
|
84
|
+
? `CallContractResult<${tsReturnTypes[0]}>`
|
|
85
|
+
: `CallContractResult<[${tsReturnTypes.join(', ')}]>`
|
|
86
|
+
const callParams = funcHasArgs ? 'params' : 'params === undefined ? {} : params'
|
|
87
|
+
return `
|
|
88
|
+
async call${funcName}Method(${params}): Promise<${retType}> {
|
|
89
|
+
return callMethod(${contractName}, this, "${functionSig.name}", ${callParams})
|
|
90
|
+
}
|
|
91
|
+
`
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function getInstanceName(contract: Contract): string {
|
|
95
|
+
return `${contract.name}Instance`
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function genAttach(instanceName: string): string {
|
|
99
|
+
return `
|
|
100
|
+
at(address: string): ${instanceName} {
|
|
101
|
+
return new ${instanceName}(address)
|
|
102
|
+
}
|
|
103
|
+
`
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function contractTypes(contractName: string): string {
|
|
107
|
+
return `${contractName}Types`
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function contractFieldType(contract: Contract): string {
|
|
111
|
+
const hasFields = contract.fieldsSig.names.length > 0
|
|
112
|
+
return hasFields ? `${contractTypes(contract.name)}.Fields` : '{}'
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function genFetchState(contract: Contract): string {
|
|
116
|
+
return `
|
|
117
|
+
async fetchState(): Promise<${contractTypes(contract.name)}.State> {
|
|
118
|
+
return fetchContractState(${contract.name}, this)
|
|
119
|
+
}
|
|
120
|
+
`
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function getEventType(event: EventSig): string {
|
|
124
|
+
return event.name + 'Event'
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function genEventType(event: EventSig): string {
|
|
128
|
+
if (event.fieldNames.length === 0) {
|
|
129
|
+
return `export type ${getEventType(event)} = Omit<ContractEvent, 'fields'>`
|
|
130
|
+
}
|
|
131
|
+
const fieldsType = `{${formatParameters({ names: event.fieldNames, types: event.fieldTypes })}}`
|
|
132
|
+
return `export type ${getEventType(event)} = ContractEvent<${fieldsType}>`
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function genSubscribeSystemEvent(event: SystemEvent): string {
|
|
136
|
+
return `
|
|
137
|
+
subscribe${event.eventSig.name}Event(options: SubscribeOptions<${event.eventType}>, fromCount?: number): EventSubscription {
|
|
138
|
+
return subscribe${event.eventSig.name}Event(this, options, fromCount)
|
|
139
|
+
}
|
|
140
|
+
`
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function genSubscribeEvent(contractName: string, event: EventSig): string {
|
|
144
|
+
const eventType = getEventType(event)
|
|
145
|
+
const scopedEventType = `${contractTypes(contractName)}.${eventType}`
|
|
146
|
+
return `
|
|
147
|
+
subscribe${eventType}(options: SubscribeOptions<${scopedEventType}>, fromCount?: number): EventSubscription {
|
|
148
|
+
return subscribeContractEvent(${contractName}.contract, this, options, "${event.name}", fromCount)
|
|
149
|
+
}
|
|
150
|
+
`
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function genSubscribeAllEvents(contract: Contract, systemEvents: SystemEvent[]): string {
|
|
154
|
+
const contractEventTypes = contract.eventsSig.map((e) => `${contractTypes(contract.name)}.${getEventType(e)}`)
|
|
155
|
+
const systemEventTypes = systemEvents.map((e) => e.eventType)
|
|
156
|
+
const eventTypes = contractEventTypes.concat(systemEventTypes).join(' | ')
|
|
157
|
+
return `
|
|
158
|
+
subscribeAllEvents(options: SubscribeOptions<${eventTypes}>, fromCount?: number): EventSubscription {
|
|
159
|
+
return subscribeAllEvents(${contract.name}.contract, this, options, fromCount)
|
|
160
|
+
}
|
|
161
|
+
`
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function genContractStateType(contract: Contract): string {
|
|
165
|
+
if (contract.fieldsSig.names.length === 0) {
|
|
166
|
+
return `export type State = Omit<ContractState<any>, 'fields'>`
|
|
167
|
+
}
|
|
168
|
+
return `
|
|
169
|
+
export type Fields = {
|
|
170
|
+
${formatParameters(contract.fieldsSig)}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export type State = ContractState<Fields>
|
|
174
|
+
`
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function genTestMethod(contract: Contract, functionSig: FunctionSig): string {
|
|
178
|
+
const funcName = functionSig.name.charAt(0).toUpperCase() + functionSig.name.slice(1)
|
|
179
|
+
const funcHasArgs = functionSig.paramNames.length > 0
|
|
180
|
+
const contractHasFields = contract.fieldsSig.names.length > 0
|
|
181
|
+
const argsType = funcHasArgs
|
|
182
|
+
? `{${formatParameters({ names: functionSig.paramNames, types: functionSig.paramTypes })}}`
|
|
183
|
+
: 'never'
|
|
184
|
+
const fieldsType = contractHasFields ? `${contractFieldType(contract)}` : 'never'
|
|
185
|
+
const params =
|
|
186
|
+
funcHasArgs && contractHasFields
|
|
187
|
+
? `params: TestContractParams<${fieldsType}, ${argsType}>`
|
|
188
|
+
: funcHasArgs
|
|
189
|
+
? `params: Omit<TestContractParams<${fieldsType}, ${argsType}>, 'initialFields'>`
|
|
190
|
+
: contractHasFields
|
|
191
|
+
? `params: Omit<TestContractParams<${fieldsType}, ${argsType}>, 'testArgs'>`
|
|
192
|
+
: `params?: Omit<TestContractParams<${fieldsType}, ${argsType}>, 'testArgs' | 'initialFields'>`
|
|
193
|
+
const tsReturnTypes = functionSig.returnTypes.map((tpe) => toTsType(tpe))
|
|
194
|
+
const retType =
|
|
195
|
+
tsReturnTypes.length === 0
|
|
196
|
+
? `TestContractResult<null>`
|
|
197
|
+
: tsReturnTypes.length === 1
|
|
198
|
+
? `TestContractResult<${tsReturnTypes[0]}>`
|
|
199
|
+
: `TestContractResult<[${tsReturnTypes.join(', ')}]>`
|
|
200
|
+
const callParams = funcHasArgs || contractHasFields ? 'params' : 'params === undefined ? {} : params'
|
|
201
|
+
return `
|
|
202
|
+
async test${funcName}Method(${params}): Promise<${retType}> {
|
|
203
|
+
return testMethod(this, "${functionSig.name}", ${callParams})
|
|
204
|
+
}
|
|
205
|
+
`
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
type SystemEvent = {
|
|
209
|
+
eventSig: EventSig
|
|
210
|
+
eventIndex: number
|
|
211
|
+
eventType: string
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function genContract(project: Project, contract: Contract, artifactRelativePath: string): string {
|
|
215
|
+
const contractInfo = project.projectArtifact.infos.get(contract.name)
|
|
216
|
+
if (contractInfo === undefined) {
|
|
217
|
+
throw new Error(`Contract info does not exist: ${contract.name}`)
|
|
218
|
+
}
|
|
219
|
+
const systemEvents: SystemEvent[] = [
|
|
220
|
+
{
|
|
221
|
+
eventSig: Contract.ContractCreatedEvent,
|
|
222
|
+
eventIndex: Contract.ContractCreatedEventIndex,
|
|
223
|
+
eventType: 'ContractCreatedEvent'
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
eventSig: Contract.ContractDestroyedEvent,
|
|
227
|
+
eventIndex: Contract.ContractDestroyedEventIndex,
|
|
228
|
+
eventType: 'ContractDestroyedEvent'
|
|
229
|
+
}
|
|
230
|
+
]
|
|
231
|
+
const source = `
|
|
232
|
+
${header}
|
|
233
|
+
|
|
234
|
+
import {
|
|
235
|
+
Address, Contract, ContractState, TestContractResult, HexString, ContractFactory,
|
|
236
|
+
SubscribeOptions, EventSubscription, CallContractParams, CallContractResult,
|
|
237
|
+
TestContractParams, ContractEvent, subscribeContractCreatedEvent, subscribeContractDestroyedEvent, subscribeContractEvent, subscribeAllEvents, testMethod, callMethod, fetchContractState,
|
|
238
|
+
ContractCreatedEvent, ContractDestroyedEvent, ContractInstance
|
|
239
|
+
} from '@alephium/web3'
|
|
240
|
+
import { default as ${contract.name}ContractJson } from '../${artifactRelativePath}'
|
|
241
|
+
|
|
242
|
+
// Custom types for the contract
|
|
243
|
+
export namespace ${contract.name}Types {
|
|
244
|
+
${genContractStateType(contract)}
|
|
245
|
+
${contract.eventsSig.map((e) => genEventType(e)).join('\n')}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
class Factory extends ContractFactory<${contract.name}Instance, ${contractFieldType(contract)}> {
|
|
249
|
+
${genAttach(getInstanceName(contract))}
|
|
250
|
+
${contract.functions.map((f) => genTestMethod(contract, f)).join('\n')}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Use this object to test and deploy the contract
|
|
254
|
+
export const ${contract.name} = new Factory(Contract.fromJson(
|
|
255
|
+
${contract.name}ContractJson,
|
|
256
|
+
'${contractInfo.bytecodeDebugPatch}',
|
|
257
|
+
'${contractInfo.codeHashDebug}',
|
|
258
|
+
))
|
|
259
|
+
|
|
260
|
+
// Use this class to interact with the blockchain
|
|
261
|
+
export class ${contract.name}Instance extends ContractInstance {
|
|
262
|
+
constructor(address: Address) {
|
|
263
|
+
super(address)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
${genFetchState(contract)}
|
|
267
|
+
${systemEvents.map((e) => genSubscribeSystemEvent(e)).join('\n')}
|
|
268
|
+
${contract.eventsSig.map((e) => genSubscribeEvent(contract.name, e)).join('\n')}
|
|
269
|
+
${genSubscribeAllEvents(contract, systemEvents)}
|
|
270
|
+
${contract.functions.map((f) => genCallMethod(contract.name, f)).join('\n')}
|
|
271
|
+
}
|
|
272
|
+
`
|
|
273
|
+
return prettier.format(source, { parser: 'typescript' })
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function genScript(script: Script): string {
|
|
277
|
+
console.log(`Generating code for script ${script.name}`)
|
|
278
|
+
const usePreapprovedAssets = script.functions[0].usePreapprovedAssets
|
|
279
|
+
const fieldsType = script.fieldsSig.names.length > 0 ? `{${formatParameters(script.fieldsSig)}}` : '{}'
|
|
280
|
+
const paramsType = usePreapprovedAssets
|
|
281
|
+
? `ExecuteScriptParams<${fieldsType}>`
|
|
282
|
+
: `Omit<ExecuteScriptParams<${fieldsType}>, 'attoAlphAmount' | 'tokens'>`
|
|
283
|
+
return `
|
|
284
|
+
export namespace ${script.name} {
|
|
285
|
+
export async function execute(signer: SignerProvider, params: ${paramsType}): Promise<ExecuteScriptResult> {
|
|
286
|
+
const signerParams = await script.txParamsForExecution(signer, params)
|
|
287
|
+
return await signer.signAndSubmitExecuteScriptTx(signerParams)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export const script = Script.fromJson(${script.name}ScriptJson)
|
|
291
|
+
}
|
|
292
|
+
`
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function genScripts(project: Project, outDir: string, exports: string[]) {
|
|
296
|
+
const artifactDir = project.artifactsRootDir
|
|
297
|
+
exports.push('./scripts')
|
|
298
|
+
const scriptPath = path.join(outDir, 'scripts.ts')
|
|
299
|
+
const scripts = Array.from(project.scripts.values())
|
|
300
|
+
const importArtifacts = Array.from(scripts)
|
|
301
|
+
.map((s) => {
|
|
302
|
+
const artifactPath = s.sourceInfo.getArtifactPath(artifactDir)
|
|
303
|
+
const artifactRelativePath = path.relative(artifactDir, artifactPath)
|
|
304
|
+
return `import { default as ${s.artifact.name}ScriptJson } from '../${artifactRelativePath}'`
|
|
305
|
+
})
|
|
306
|
+
.join('\n')
|
|
307
|
+
const scriptsSource = scripts.map((s) => genScript(s.artifact)).join('\n')
|
|
308
|
+
const source = `
|
|
309
|
+
${header}
|
|
310
|
+
|
|
311
|
+
import {
|
|
312
|
+
ExecuteScriptParams,
|
|
313
|
+
ExecuteScriptResult,
|
|
314
|
+
Script,
|
|
315
|
+
SignerProvider,
|
|
316
|
+
HexString
|
|
317
|
+
} from '@alephium/web3'
|
|
318
|
+
${importArtifacts}
|
|
319
|
+
|
|
320
|
+
${scriptsSource}
|
|
321
|
+
`
|
|
322
|
+
const formatted = prettier.format(source, { parser: 'typescript' })
|
|
323
|
+
fs.writeFileSync(scriptPath, formatted, 'utf8')
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function genIndexTs(outDir: string, exports: string[]) {
|
|
327
|
+
const indexPath = path.join(outDir, 'index.ts')
|
|
328
|
+
const exportStatements = exports.map((e) => `export * from "${e}"`).join('\n')
|
|
329
|
+
const source = prettier.format(header + exportStatements, { parser: 'typescript' })
|
|
330
|
+
fs.writeFileSync(indexPath, source, 'utf8')
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function genContracts(project: Project, outDir: string, exports: string[]) {
|
|
334
|
+
const artifactDir = project.artifactsRootDir
|
|
335
|
+
Array.from(project.contracts.values()).forEach((c) => {
|
|
336
|
+
console.log(`Generating code for contract ${c.artifact.name}`)
|
|
337
|
+
exports.push(`./${c.artifact.name}`)
|
|
338
|
+
const filename = `${c.artifact.name}.ts`
|
|
339
|
+
const sourcePath = path.join(outDir, filename)
|
|
340
|
+
const artifactPath = c.sourceInfo.getArtifactPath(artifactDir)
|
|
341
|
+
const artifactRelativePath = path.relative(artifactDir, artifactPath)
|
|
342
|
+
const sourceCode = genContract(project, c.artifact, artifactRelativePath)
|
|
343
|
+
fs.writeFileSync(sourcePath, sourceCode, 'utf8')
|
|
344
|
+
})
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export function codegen(project: Project) {
|
|
348
|
+
const outDirTemp = path.join(project.artifactsRootDir, 'ts')
|
|
349
|
+
const outDir = path.isAbsolute(outDirTemp) ? outDirTemp : path.resolve(outDirTemp)
|
|
350
|
+
if (!fs.existsSync(outDir)) {
|
|
351
|
+
fs.mkdirSync(outDir, { recursive: true })
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const exports: string[] = []
|
|
355
|
+
try {
|
|
356
|
+
genContracts(project, outDir, exports)
|
|
357
|
+
genScripts(project, outDir, exports)
|
|
358
|
+
genIndexTs(outDir, exports)
|
|
359
|
+
} catch (error) {
|
|
360
|
+
console.log(`Failed to generate code: ${error}`)
|
|
361
|
+
}
|
|
362
|
+
}
|
package/src/contract/contract.ts
CHANGED
|
@@ -59,6 +59,7 @@ import { getCurrentNodeProvider } from '../global'
|
|
|
59
59
|
import * as path from 'path'
|
|
60
60
|
import { EventSubscription, subscribeToEvents } from './events'
|
|
61
61
|
import { ONE_ALPH } from '../constants'
|
|
62
|
+
import { codegen } from './codegen'
|
|
62
63
|
|
|
63
64
|
export type FieldsSig = node.FieldsSig
|
|
64
65
|
export type EventSig = node.EventSig
|
|
@@ -429,6 +430,7 @@ export class Project {
|
|
|
429
430
|
projectArtifact
|
|
430
431
|
)
|
|
431
432
|
await project.saveArtifactsToFile(projectRootDir)
|
|
433
|
+
codegen(project)
|
|
432
434
|
return project
|
|
433
435
|
}
|
|
434
436
|
|