@alephium/web3 0.0.3 → 0.1.0-rc.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.
Files changed (144) hide show
  1. package/.editorconfig +13 -0
  2. package/.eslintignore +2 -0
  3. package/.eslintrc.json +21 -0
  4. package/README.md +2 -2
  5. package/contracts/add.ral +7 -3
  6. package/contracts/greeter.ral +3 -1
  7. package/contracts/greeter_interface.ral +3 -0
  8. package/contracts/greeter_main.ral +9 -0
  9. package/contracts/main.ral +3 -5
  10. package/dev/user.conf +8 -4
  11. package/dist/alephium-web3.min.js +1 -1
  12. package/dist/alephium-web3.min.js.map +1 -1
  13. package/dist/scripts/check-versions.d.ts +1 -0
  14. package/dist/scripts/check-versions.js +39 -0
  15. package/dist/{cli → scripts}/create-project.d.ts +0 -0
  16. package/dist/scripts/create-project.js +124 -0
  17. package/dist/scripts/header.d.ts +0 -0
  18. package/dist/scripts/header.js +18 -0
  19. package/dist/scripts/rename-gitignore.d.ts +1 -0
  20. package/dist/scripts/rename-gitignore.js +24 -0
  21. package/dist/scripts/start-devnet.d.ts +1 -0
  22. package/dist/scripts/start-devnet.js +131 -0
  23. package/dist/scripts/stop-devnet.d.ts +1 -0
  24. package/dist/scripts/stop-devnet.js +32 -0
  25. package/dist/{api → src/api}/api-alephium.d.ts +287 -168
  26. package/dist/{api → src/api}/api-alephium.js +497 -115
  27. package/dist/{api → src/api}/api-explorer.d.ts +117 -19
  28. package/dist/{api → src/api}/api-explorer.js +206 -46
  29. package/dist/src/api/index.d.ts +10 -0
  30. package/dist/src/api/index.js +55 -0
  31. package/dist/{lib → src}/constants.d.ts +0 -0
  32. package/dist/{lib → src}/constants.js +0 -0
  33. package/dist/src/contract/contract.d.ts +182 -0
  34. package/dist/src/contract/contract.js +760 -0
  35. package/dist/src/contract/events.d.ts +29 -0
  36. package/dist/src/contract/events.js +77 -0
  37. package/dist/src/contract/index.d.ts +3 -0
  38. package/dist/src/contract/index.js +32 -0
  39. package/dist/src/contract/ralph.d.ts +12 -0
  40. package/dist/src/contract/ralph.js +362 -0
  41. package/dist/src/index.d.ts +5 -0
  42. package/dist/src/index.js +34 -0
  43. package/dist/src/signer/index.d.ts +2 -0
  44. package/dist/src/signer/index.js +31 -0
  45. package/dist/src/signer/node-wallet.d.ts +13 -0
  46. package/dist/src/signer/node-wallet.js +60 -0
  47. package/dist/src/signer/signer.d.ts +143 -0
  48. package/dist/src/signer/signer.js +184 -0
  49. package/dist/src/test/index.d.ts +7 -0
  50. package/dist/src/test/index.js +41 -0
  51. package/dist/src/test/privatekey-wallet.d.ts +12 -0
  52. package/dist/src/test/privatekey-wallet.js +68 -0
  53. package/dist/{lib → src/utils}/address.d.ts +0 -0
  54. package/dist/{lib → src/utils}/address.js +1 -1
  55. package/dist/{lib → src/utils}/bs58.d.ts +2 -2
  56. package/dist/{lib → src/utils}/bs58.js +3 -1
  57. package/dist/{lib → src/utils}/djb2.d.ts +0 -0
  58. package/dist/{lib → src/utils}/djb2.js +1 -1
  59. package/dist/src/utils/index.d.ts +6 -0
  60. package/dist/src/utils/index.js +35 -0
  61. package/dist/{lib → src/utils}/password-crypto.d.ts +0 -0
  62. package/dist/{lib → src/utils}/password-crypto.js +8 -7
  63. package/dist/src/utils/transaction.d.ts +2 -0
  64. package/dist/src/utils/transaction.js +58 -0
  65. package/dist/src/utils/utils.d.ts +30 -0
  66. package/dist/{lib → src/utils}/utils.js +83 -19
  67. package/gitignore +5 -4
  68. package/package.json +56 -40
  69. package/scripts/create-project.ts +136 -0
  70. package/scripts/header.js +17 -0
  71. package/scripts/start-devnet.js +3 -3
  72. package/src/api/api-alephium.ts +2323 -0
  73. package/src/api/api-explorer.ts +808 -0
  74. package/src/api/index.ts +35 -0
  75. package/src/constants.ts +20 -0
  76. package/src/contract/contract.ts +1014 -0
  77. package/src/contract/events.ts +102 -0
  78. package/src/contract/index.ts +21 -0
  79. package/src/contract/ralph.test.ts +178 -0
  80. package/src/contract/ralph.ts +362 -0
  81. package/src/fixtures/address.json +36 -0
  82. package/src/fixtures/balance.json +9 -0
  83. package/src/fixtures/self-clique.json +19 -0
  84. package/src/fixtures/transaction.json +13 -0
  85. package/src/fixtures/transactions.json +179 -0
  86. package/src/index.ts +24 -0
  87. package/src/signer/fixtures/genesis.json +26 -0
  88. package/src/signer/fixtures/wallets.json +26 -0
  89. package/src/signer/index.ts +20 -0
  90. package/src/signer/node-wallet.ts +74 -0
  91. package/src/signer/signer.ts +313 -0
  92. package/src/test/index.ts +32 -0
  93. package/src/test/privatekey-wallet.ts +58 -0
  94. package/src/utils/address.test.ts +47 -0
  95. package/src/utils/address.ts +39 -0
  96. package/src/utils/bs58.ts +26 -0
  97. package/src/utils/djb2.test.ts +35 -0
  98. package/src/utils/djb2.ts +25 -0
  99. package/src/utils/index.ts +24 -0
  100. package/src/utils/password-crypto.test.ts +27 -0
  101. package/src/utils/password-crypto.ts +77 -0
  102. package/src/utils/transaction.test.ts +50 -0
  103. package/src/utils/transaction.ts +39 -0
  104. package/src/utils/utils.test.ts +160 -0
  105. package/src/utils/utils.ts +209 -0
  106. package/templates/{README.md → base/README.md} +4 -4
  107. package/templates/base/package.json +35 -0
  108. package/templates/base/src/greeter.ts +41 -0
  109. package/templates/base/tsconfig.json +19 -0
  110. package/templates/react/README.md +34 -0
  111. package/templates/react/config-overrides.js +18 -0
  112. package/templates/react/package.json +66 -0
  113. package/templates/react/src/App.tsx +42 -0
  114. package/templates/react/src/artifacts/greeter.ral.json +26 -0
  115. package/templates/react/src/artifacts/greeter_main.ral.json +22 -0
  116. package/templates/shared/.eslintrc.json +12 -0
  117. package/templates/shared/scripts/header.js +0 -0
  118. package/test/contract.test.ts +161 -0
  119. package/test/events.test.ts +139 -0
  120. package/webpack.config.js +1 -1
  121. package/contracts/greeter-main.ral +0 -8
  122. package/dist/cli/create-project.js +0 -87
  123. package/dist/lib/clique.d.ts +0 -23
  124. package/dist/lib/clique.js +0 -149
  125. package/dist/lib/contract.d.ts +0 -152
  126. package/dist/lib/contract.js +0 -711
  127. package/dist/lib/explorer.d.ts +0 -8
  128. package/dist/lib/explorer.js +0 -46
  129. package/dist/lib/index.d.ts +0 -12
  130. package/dist/lib/index.js +0 -60
  131. package/dist/lib/node.d.ts +0 -10
  132. package/dist/lib/node.js +0 -64
  133. package/dist/lib/numbers.d.ts +0 -7
  134. package/dist/lib/numbers.js +0 -128
  135. package/dist/lib/signer.d.ts +0 -17
  136. package/dist/lib/signer.js +0 -70
  137. package/dist/lib/storage-browser.d.ts +0 -9
  138. package/dist/lib/storage-browser.js +0 -52
  139. package/dist/lib/storage-node.d.ts +0 -9
  140. package/dist/lib/storage-node.js +0 -65
  141. package/dist/lib/utils.d.ts +0 -11
  142. package/dist/lib/wallet.d.ts +0 -30
  143. package/dist/lib/wallet.js +0 -142
  144. package/templates/package.json +0 -29
@@ -0,0 +1,1014 @@
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 { Buffer } from 'buffer/'
20
+ import * as cryptojs from 'crypto-js'
21
+ import * as crypto from 'crypto'
22
+ import fs from 'fs'
23
+ import { promises as fsPromises } from 'fs'
24
+ import { NodeProvider } from '../api'
25
+ import { node } from '../api'
26
+ import { SignDeployContractTxParams, SignExecuteScriptTxParams, SignerWithNodeProvider } from '../signer'
27
+ import * as ralph from './ralph'
28
+ import { bs58, binToHex, contractIdFromAddress, assertType, Eq } from '../utils'
29
+
30
+ export abstract class Common {
31
+ readonly sourceCodeSha256: string
32
+ readonly functions: node.FunctionSig[]
33
+
34
+ static readonly importRegex = new RegExp('^import "[a-z][a-z_0-9]*.ral"', 'mg')
35
+ static readonly contractRegex = new RegExp('^TxContract [A-Z][a-zA-Z0-9]*', 'mg')
36
+ static readonly interfaceRegex = new RegExp('^Interface [A-Z][a-zA-Z0-9]* \\{', 'mg')
37
+ static readonly scriptRegex = new RegExp('^TxScript [A-Z][a-zA-Z0-9]*', 'mg')
38
+
39
+ private static _artifactCache: Map<string, Contract | Script> = new Map<string, Contract | Script>()
40
+ static artifactCacheCapacity = 20
41
+ protected static _getArtifactFromCache(codeHash: string): Contract | Script | undefined {
42
+ return this._artifactCache.get(codeHash)
43
+ }
44
+ protected static _putArtifactToCache(contract: Contract): void {
45
+ if (!this._artifactCache.has(contract.codeHash)) {
46
+ if (this._artifactCache.size >= this.artifactCacheCapacity) {
47
+ const keyToDelete = this._artifactCache.keys().next().value
48
+ this._artifactCache.delete(keyToDelete)
49
+ }
50
+ this._artifactCache.set(contract.codeHash, contract)
51
+ }
52
+ }
53
+
54
+ constructor(sourceCodeSha256: string, functions: node.FunctionSig[]) {
55
+ this.sourceCodeSha256 = sourceCodeSha256
56
+ this.functions = functions
57
+ }
58
+
59
+ protected static _contractPath(fileName: string): string {
60
+ if (fileName.endsWith('.json')) {
61
+ return `./contracts/${fileName.slice(0, -5)}`
62
+ } else {
63
+ return `./contracts/${fileName}`
64
+ }
65
+ }
66
+
67
+ protected static _artifactPath(fileName: string): string {
68
+ if (fileName.endsWith('.json')) {
69
+ return `./artifacts/${fileName}`
70
+ } else {
71
+ return `./artifacts/${fileName}.json`
72
+ }
73
+ }
74
+
75
+ protected static _artifactsFolder(): string {
76
+ return './artifacts/'
77
+ }
78
+
79
+ protected static async _handleImports(contractStr: string, importsCache: string[]): Promise<string> {
80
+ const localImportsCache: string[] = []
81
+ let result = contractStr.replace(Common.importRegex, (match) => {
82
+ localImportsCache.push(match)
83
+ return ''
84
+ })
85
+ for (const myImport of localImportsCache) {
86
+ const fileName = myImport.slice(8, -1)
87
+ if (!importsCache.includes(fileName)) {
88
+ importsCache.push(fileName)
89
+ const importContractStr = await Common._loadContractStr(fileName, importsCache, (code) =>
90
+ Contract.checkCodeType(fileName, code)
91
+ )
92
+ result = result.concat('\n', importContractStr)
93
+ }
94
+ }
95
+ return result
96
+ }
97
+
98
+ protected static async _loadContractStr(
99
+ fileName: string,
100
+ importsCache: string[],
101
+ validate: (fileName: string) => void
102
+ ): Promise<string> {
103
+ const contractPath = this._contractPath(fileName)
104
+ const contractBuffer = await fsPromises.readFile(contractPath)
105
+ const contractStr = contractBuffer.toString()
106
+
107
+ validate(contractStr)
108
+ return Common._handleImports(contractStr, importsCache)
109
+ }
110
+
111
+ static checkFileNameExtension(fileName: string): void {
112
+ if (!fileName.endsWith('.ral')) {
113
+ throw new Error('Smart contract file name should end with ".ral"')
114
+ }
115
+ }
116
+
117
+ protected static async _from<T extends { sourceCodeSha256: string }>(
118
+ provider: NodeProvider,
119
+ fileName: string,
120
+ loadContractStr: (fileName: string, importsCache: string[]) => Promise<string>,
121
+ compile: (provider: NodeProvider, fileName: string, contractStr: string, contractHash: string) => Promise<T>
122
+ ): Promise<T> {
123
+ Common.checkFileNameExtension(fileName)
124
+
125
+ const contractStr = await loadContractStr(fileName, [])
126
+ const contractHash = cryptojs.SHA256(contractStr).toString()
127
+ const existingContract = this._getArtifactFromCache(contractHash)
128
+ if (typeof existingContract !== 'undefined') {
129
+ return existingContract as unknown as T
130
+ } else {
131
+ return compile(provider, fileName, contractStr, contractHash)
132
+ }
133
+ }
134
+
135
+ protected _saveToFile(fileName: string): Promise<void> {
136
+ const artifactPath = Common._artifactPath(fileName)
137
+ return fsPromises.writeFile(artifactPath, this.toString())
138
+ }
139
+
140
+ abstract buildByteCodeToDeploy(initialFields?: Fields): string
141
+ }
142
+
143
+ export class Contract extends Common {
144
+ readonly bytecode: string
145
+ readonly codeHash: string
146
+ readonly fieldsSig: node.FieldsSig
147
+ readonly eventsSig: node.EventSig[]
148
+
149
+ constructor(
150
+ sourceCodeSha256: string,
151
+ bytecode: string,
152
+ codeHash: string,
153
+ fieldsSig: node.FieldsSig,
154
+ eventsSig: node.EventSig[],
155
+ functions: node.FunctionSig[]
156
+ ) {
157
+ super(sourceCodeSha256, functions)
158
+ this.bytecode = bytecode
159
+ this.codeHash = codeHash
160
+ this.fieldsSig = fieldsSig
161
+ this.eventsSig = eventsSig
162
+ }
163
+
164
+ static checkCodeType(fileName: string, contractStr: string): void {
165
+ const interfaceMatches = contractStr.match(Contract.interfaceRegex)
166
+ if (interfaceMatches) {
167
+ return
168
+ }
169
+
170
+ const contractMatches = contractStr.match(Contract.contractRegex)
171
+ if (contractMatches === null) {
172
+ throw new Error(`No contract found in: ${fileName}`)
173
+ } else if (contractMatches.length > 1) {
174
+ throw new Error(`Multiple contracts in: ${fileName}`)
175
+ } else {
176
+ return
177
+ }
178
+ }
179
+
180
+ private static async loadContractStr(fileName: string, importsCache: string[]): Promise<string> {
181
+ return Common._loadContractStr(fileName, importsCache, (code) => Contract.checkCodeType(fileName, code))
182
+ }
183
+
184
+ static async fromSource(provider: NodeProvider, fileName: string): Promise<Contract> {
185
+ if (!fs.existsSync(Common._artifactsFolder())) {
186
+ fs.mkdirSync(Common._artifactsFolder(), { recursive: true })
187
+ }
188
+ const contract = await Common._from(
189
+ provider,
190
+ fileName,
191
+ (fileName, importCaches) => Contract.loadContractStr(fileName, importCaches),
192
+ Contract.compile
193
+ )
194
+ this._putArtifactToCache(contract)
195
+ return contract
196
+ }
197
+
198
+ private static async compile(
199
+ provider: NodeProvider,
200
+ fileName: string,
201
+ contractStr: string,
202
+ contractHash: string
203
+ ): Promise<Contract> {
204
+ const compiled = await provider.contracts.postContractsCompileContract({ code: contractStr })
205
+ const artifact = new Contract(
206
+ contractHash,
207
+ compiled.bytecode,
208
+ compiled.codeHash,
209
+ compiled.fields,
210
+ compiled.events,
211
+ compiled.functions
212
+ )
213
+ await artifact._saveToFile(fileName)
214
+ return artifact
215
+ }
216
+
217
+ // TODO: safely parse json
218
+ static fromJson(artifact: any): Contract {
219
+ if (
220
+ artifact.sourceCodeSha256 == null ||
221
+ artifact.bytecode == null ||
222
+ artifact.codeHash == null ||
223
+ artifact.fieldsSig == null ||
224
+ artifact.eventsSig == null ||
225
+ artifact.functions == null
226
+ ) {
227
+ throw Error('The artifact JSON for contract is incomplete')
228
+ }
229
+ const contract = new Contract(
230
+ artifact.sourceCodeSha256,
231
+ artifact.bytecode,
232
+ artifact.codeHash,
233
+ artifact.fieldsSig,
234
+ artifact.eventsSig,
235
+ artifact.functions
236
+ )
237
+ this._putArtifactToCache(contract)
238
+ return contract
239
+ }
240
+
241
+ // support both 'code.ral' and 'code.ral.json'
242
+ static async fromArtifactFile(fileName: string): Promise<Contract> {
243
+ const artifactPath = Contract._artifactPath(fileName)
244
+ const content = await fsPromises.readFile(artifactPath)
245
+ const artifact = JSON.parse(content.toString())
246
+ return Contract.fromJson(artifact)
247
+ }
248
+
249
+ override toString(): string {
250
+ return JSON.stringify(
251
+ {
252
+ sourceCodeSha256: this.sourceCodeSha256,
253
+ bytecode: this.bytecode,
254
+ codeHash: this.codeHash,
255
+ fieldsSig: this.fieldsSig,
256
+ eventsSig: this.eventsSig,
257
+ functions: this.functions
258
+ },
259
+ null,
260
+ 2
261
+ )
262
+ }
263
+
264
+ toState(fields: Fields, asset: Asset, address?: string): ContractState {
265
+ const addressDef = typeof address !== 'undefined' ? address : Contract.randomAddress()
266
+ return {
267
+ address: addressDef,
268
+ contractId: binToHex(contractIdFromAddress(addressDef)),
269
+ bytecode: this.bytecode,
270
+ codeHash: this.codeHash,
271
+ fields: fields,
272
+ fieldsSig: this.fieldsSig,
273
+ asset: asset
274
+ }
275
+ }
276
+
277
+ static randomAddress(): string {
278
+ const bytes = crypto.randomBytes(33)
279
+ bytes[0] = 3
280
+ return bs58.encode(bytes)
281
+ }
282
+
283
+ private async _test(
284
+ provider: NodeProvider,
285
+ funcName: string,
286
+ params: TestContractParams,
287
+ expectPublic: boolean,
288
+ accessType: string
289
+ ): Promise<TestContractResult> {
290
+ const apiParams: node.TestContract = this.toTestContract(funcName, params)
291
+ const apiResult = await provider.contracts.postContractsTestContract(apiParams)
292
+
293
+ const methodIndex =
294
+ typeof params.testMethodIndex !== 'undefined' ? params.testMethodIndex : this.getMethodIndex(funcName)
295
+ const isPublic = this.functions[`${methodIndex}`].signature.indexOf('pub ') !== -1
296
+ if (isPublic === expectPublic) {
297
+ const result = await this.fromTestContractResult(methodIndex, apiResult)
298
+ return result
299
+ } else {
300
+ throw new Error(`The test method ${funcName} is not ${accessType}`)
301
+ }
302
+ }
303
+
304
+ async testPublicMethod(
305
+ provider: NodeProvider,
306
+ funcName: string,
307
+ params: TestContractParams
308
+ ): Promise<TestContractResult> {
309
+ return this._test(provider, funcName, params, true, 'public')
310
+ }
311
+
312
+ async testPrivateMethod(
313
+ provider: NodeProvider,
314
+ funcName: string,
315
+ params: TestContractParams
316
+ ): Promise<TestContractResult> {
317
+ return this._test(provider, funcName, params, false, 'private')
318
+ }
319
+
320
+ toApiFields(fields?: Fields): node.Val[] {
321
+ if (typeof fields === 'undefined') {
322
+ return []
323
+ } else {
324
+ return toApiFields(fields, this.fieldsSig)
325
+ }
326
+ }
327
+
328
+ toApiArgs(funcName: string, args?: Arguments): node.Val[] {
329
+ if (args) {
330
+ const func = this.functions.find((func) => func.name == funcName)
331
+ if (func == null) {
332
+ throw new Error(`Invalid function name: ${funcName}`)
333
+ }
334
+
335
+ return toApiArgs(args, func)
336
+ } else {
337
+ return []
338
+ }
339
+ }
340
+
341
+ getMethodIndex(funcName: string): number {
342
+ return this.functions.findIndex((func) => func.name === funcName)
343
+ }
344
+
345
+ toApiContractStates(states?: ContractState[]): node.ContractState[] | undefined {
346
+ return typeof states != 'undefined' ? states.map((state) => toApiContractState(state)) : undefined
347
+ }
348
+
349
+ toTestContract(funcName: string, params: TestContractParams): node.TestContract {
350
+ return {
351
+ group: params.group,
352
+ address: params.address,
353
+ bytecode: this.bytecode,
354
+ initialFields: this.toApiFields(params.initialFields),
355
+ initialAsset: typeof params.initialAsset !== 'undefined' ? toApiAsset(params.initialAsset) : undefined,
356
+ methodIndex: this.getMethodIndex(funcName),
357
+ args: this.toApiArgs(funcName, params.testArgs),
358
+ existingContracts: this.toApiContractStates(params.existingContracts),
359
+ inputAssets: toApiInputAssets(params.inputAssets)
360
+ }
361
+ }
362
+
363
+ static async fromCodeHash(codeHash: string): Promise<Contract> {
364
+ const cached = this._getArtifactFromCache(codeHash)
365
+ if (typeof cached !== 'undefined') {
366
+ return cached as Contract
367
+ }
368
+
369
+ const files = await fsPromises.readdir(Common._artifactsFolder())
370
+ for (const file of files) {
371
+ if (file.endsWith('.ral.json')) {
372
+ try {
373
+ const contract = await Contract.fromArtifactFile(file)
374
+ if (contract.codeHash === codeHash) {
375
+ return contract as Contract
376
+ }
377
+ } catch (_) {}
378
+ }
379
+ }
380
+
381
+ throw new Error(`Unknown code with code hash: ${codeHash}`)
382
+ }
383
+
384
+ static async getFieldsSig(state: node.ContractState): Promise<node.FieldsSig> {
385
+ return Contract.fromCodeHash(state.codeHash).then((contract) => contract.fieldsSig)
386
+ }
387
+
388
+ async fromApiContractState(state: node.ContractState): Promise<ContractState> {
389
+ const contract = await Contract.fromCodeHash(state.codeHash)
390
+ return {
391
+ address: state.address,
392
+ contractId: binToHex(contractIdFromAddress(state.address)),
393
+ bytecode: state.bytecode,
394
+ initialStateHash: state.initialStateHash,
395
+ codeHash: state.codeHash,
396
+ fields: fromApiFields(state.fields, contract.fieldsSig),
397
+ fieldsSig: await Contract.getFieldsSig(state),
398
+ asset: fromApiAsset(state.asset)
399
+ }
400
+ }
401
+
402
+ static ContractCreatedEvent: node.EventSig = {
403
+ name: 'ContractCreated',
404
+ signature: 'event ContractCreated(address:Address)',
405
+ fieldNames: ['address'],
406
+ fieldTypes: ['Address']
407
+ }
408
+
409
+ static ContractDestroyedEvent: node.EventSig = {
410
+ name: 'ContractDestroyed',
411
+ signature: 'event ContractDestroyed(address:Address)',
412
+ fieldNames: ['address'],
413
+ fieldTypes: ['Address']
414
+ }
415
+
416
+ static async fromApiEvent(
417
+ event: node.ContractEventByTxId,
418
+ codeHash: string | undefined
419
+ ): Promise<ContractEventByTxId> {
420
+ let eventSig: node.EventSig
421
+
422
+ if (event.eventIndex == -1) {
423
+ eventSig = this.ContractCreatedEvent
424
+ } else if (event.eventIndex == -2) {
425
+ eventSig = this.ContractDestroyedEvent
426
+ } else {
427
+ const contract = await Contract.fromCodeHash(codeHash!)
428
+ eventSig = contract.eventsSig[event.eventIndex]
429
+ }
430
+
431
+ return {
432
+ blockHash: event.blockHash,
433
+ contractAddress: event.contractAddress,
434
+ name: eventSig.name,
435
+ fields: fromApiEventFields(event.fields, eventSig)
436
+ }
437
+ }
438
+
439
+ async fromTestContractResult(methodIndex: number, result: node.TestContractResult): Promise<TestContractResult> {
440
+ const addressToCodeHash = new Map<string, string>()
441
+ addressToCodeHash.set(result.address, result.codeHash)
442
+ result.contracts.forEach((contract) => addressToCodeHash.set(contract.address, contract.codeHash))
443
+ return {
444
+ address: result.address,
445
+ contractId: binToHex(contractIdFromAddress(result.address)),
446
+ returns: fromApiArray(result.returns, this.functions[`${methodIndex}`].returnTypes),
447
+ gasUsed: result.gasUsed,
448
+ contracts: await Promise.all(result.contracts.map((contract) => this.fromApiContractState(contract))),
449
+ txOutputs: result.txOutputs.map(fromApiOutput),
450
+ events: await Promise.all(
451
+ result.events.map((event) => {
452
+ const contractAddress = event.contractAddress
453
+ const codeHash = addressToCodeHash.get(contractAddress)
454
+ if (typeof codeHash !== 'undefined' || event.eventIndex < 0) {
455
+ return Contract.fromApiEvent(event, codeHash)
456
+ } else {
457
+ throw Error(`Cannot find codeHash for the contract address: ${contractAddress}`)
458
+ }
459
+ })
460
+ )
461
+ }
462
+ }
463
+
464
+ async paramsForDeployment(params: BuildDeployContractTx): Promise<SignDeployContractTxParams> {
465
+ const bytecode = this.buildByteCodeToDeploy(params.initialFields ? params.initialFields : {})
466
+ const signerParams: SignDeployContractTxParams = {
467
+ signerAddress: params.signerAddress,
468
+ bytecode: bytecode,
469
+ initialAlphAmount: extractOptionalNumber256(params.initialAlphAmount),
470
+ issueTokenAmount: extractOptionalNumber256(params.issueTokenAmount),
471
+ initialTokenAmounts: params.initialTokenAmounts?.map(toApiToken),
472
+ gasAmount: params.gasAmount,
473
+ gasPrice: extractOptionalNumber256(params.gasPrice)
474
+ }
475
+ return signerParams
476
+ }
477
+
478
+ async transactionForDeployment(
479
+ signer: SignerWithNodeProvider,
480
+ params: Omit<BuildDeployContractTx, 'signerAddress'>
481
+ ): Promise<DeployContractTransaction> {
482
+ const signerParams = await this.paramsForDeployment({
483
+ ...params,
484
+ signerAddress: (await signer.getAccounts())[0].address
485
+ })
486
+ const response = await signer.buildContractCreationTx(signerParams)
487
+ return fromApiDeployContractUnsignedTx(response)
488
+ }
489
+
490
+ buildByteCodeToDeploy(initialFields: Fields): string {
491
+ return ralph.buildContractByteCode(this.bytecode, initialFields, this.fieldsSig)
492
+ }
493
+ }
494
+
495
+ export class Script extends Common {
496
+ readonly bytecodeTemplate: string
497
+ readonly fieldsSig: node.FieldsSig
498
+
499
+ constructor(
500
+ sourceCodeSha256: string,
501
+ bytecodeTemplate: string,
502
+ fieldsSig: node.FieldsSig,
503
+ functions: node.FunctionSig[]
504
+ ) {
505
+ super(sourceCodeSha256, functions)
506
+ this.bytecodeTemplate = bytecodeTemplate
507
+ this.fieldsSig = fieldsSig
508
+ }
509
+
510
+ static checkCodeType(fileName: string, contractStr: string): void {
511
+ const scriptMatches = contractStr.match(this.scriptRegex)
512
+ if (scriptMatches === null) {
513
+ throw new Error(`No script found in: ${fileName}`)
514
+ } else if (scriptMatches.length > 1) {
515
+ throw new Error(`Multiple scripts in: ${fileName}`)
516
+ } else {
517
+ return
518
+ }
519
+ }
520
+
521
+ private static async loadContractStr(fileName: string, importsCache: string[]): Promise<string> {
522
+ return Common._loadContractStr(fileName, importsCache, (code) => Script.checkCodeType(fileName, code))
523
+ }
524
+
525
+ static async fromSource(provider: NodeProvider, fileName: string): Promise<Script> {
526
+ return Common._from(
527
+ provider,
528
+ fileName,
529
+ (fileName, importsCache) => Script.loadContractStr(fileName, importsCache),
530
+ Script.compile
531
+ )
532
+ }
533
+
534
+ private static async compile(
535
+ provider: NodeProvider,
536
+ fileName: string,
537
+ scriptStr: string,
538
+ contractHash: string
539
+ ): Promise<Script> {
540
+ const compiled = await provider.contracts.postContractsCompileScript({ code: scriptStr })
541
+ const artifact = new Script(contractHash, compiled.bytecodeTemplate, compiled.fields, compiled.functions)
542
+ await artifact._saveToFile(fileName)
543
+ return artifact
544
+ }
545
+
546
+ // TODO: safely parse json
547
+ static fromJson(artifact: any): Script {
548
+ if (
549
+ artifact.sourceCodeSha256 == null ||
550
+ artifact.bytecodeTemplate == null ||
551
+ artifact.fieldsSig == null ||
552
+ artifact.functions == null
553
+ ) {
554
+ throw Error('The artifact JSON for script is incomplete')
555
+ }
556
+ return new Script(artifact.sourceCodeSha256, artifact.bytecodeTemplate, artifact.fieldsSig, artifact.functions)
557
+ }
558
+
559
+ static async fromArtifactFile(fileName: string): Promise<Script> {
560
+ const artifactPath = Common._artifactPath(fileName)
561
+ const content = await fsPromises.readFile(artifactPath)
562
+ const artifact = JSON.parse(content.toString())
563
+ return this.fromJson(artifact)
564
+ }
565
+
566
+ override toString(): string {
567
+ return JSON.stringify(
568
+ {
569
+ sourceCodeSha256: this.sourceCodeSha256,
570
+ bytecodeTemplate: this.bytecodeTemplate,
571
+ fieldsSig: this.fieldsSig,
572
+ functions: this.functions
573
+ },
574
+ null,
575
+ 2
576
+ )
577
+ }
578
+
579
+ async paramsForDeployment(params: BuildExecuteScriptTx): Promise<SignExecuteScriptTxParams> {
580
+ const signerParams: SignExecuteScriptTxParams = {
581
+ signerAddress: params.signerAddress,
582
+ bytecode: this.buildByteCodeToDeploy(params.initialFields ? params.initialFields : {}),
583
+ alphAmount: extractOptionalNumber256(params.alphAmount),
584
+ tokens: typeof params.tokens !== 'undefined' ? params.tokens.map(toApiToken) : undefined,
585
+ gasAmount: params.gasAmount,
586
+ gasPrice: extractOptionalNumber256(params.gasPrice)
587
+ }
588
+ return signerParams
589
+ }
590
+
591
+ async transactionForDeployment(
592
+ signer: SignerWithNodeProvider,
593
+ params: Omit<BuildExecuteScriptTx, 'signerAddress'>
594
+ ): Promise<BuildScriptTxResult> {
595
+ const signerParams = await this.paramsForDeployment({
596
+ ...params,
597
+ signerAddress: (await signer.getAccounts())[0].address
598
+ })
599
+ return await signer.buildScriptTx(signerParams)
600
+ }
601
+
602
+ buildByteCodeToDeploy(initialFields: Fields): string {
603
+ return ralph.buildScriptByteCode(this.bytecodeTemplate, initialFields, this.fieldsSig)
604
+ }
605
+ }
606
+
607
+ export type Number256 = number | bigint | string
608
+ export type Val = Number256 | boolean | string | Val[]
609
+ export type NamedVals = Record<string, Val>
610
+ export type Fields = NamedVals
611
+ export type Arguments = NamedVals
612
+
613
+ function extractBoolean(v: Val): boolean {
614
+ if (typeof v === 'boolean') {
615
+ return v
616
+ } else {
617
+ throw new Error(`Invalid boolean value: ${v}`)
618
+ }
619
+ }
620
+
621
+ // TODO: check integer bounds
622
+ function extractNumber256(v: Val): string {
623
+ if ((typeof v === 'number' && Number.isInteger(v)) || typeof v === 'bigint') {
624
+ return v.toString()
625
+ } else if (typeof v === 'string') {
626
+ return v
627
+ } else {
628
+ throw new Error(`Invalid 256 bit number: ${v}`)
629
+ }
630
+ }
631
+
632
+ function extractOptionalNumber256(v?: Val): string | undefined {
633
+ return typeof v !== 'undefined' ? extractNumber256(v) : undefined
634
+ }
635
+
636
+ // TODO: check hex string
637
+ function extractByteVec(v: Val): string {
638
+ if (typeof v === 'string') {
639
+ // try to convert from address to contract id
640
+ try {
641
+ const address = bs58.decode(v)
642
+ if (address.length == 33 && address[0] == 3) {
643
+ return Buffer.from(address.slice(1)).toString('hex')
644
+ }
645
+ } catch (_) {
646
+ return v as string
647
+ }
648
+ return v as string
649
+ } else {
650
+ throw new Error(`Invalid string: ${v}`)
651
+ }
652
+ }
653
+
654
+ function extractBs58(v: Val): string {
655
+ if (typeof v === 'string') {
656
+ try {
657
+ bs58.decode(v)
658
+ return v as string
659
+ } catch (error) {
660
+ throw new Error(`Invalid base58 string: ${v}`)
661
+ }
662
+ } else {
663
+ throw new Error(`Invalid string: ${v}`)
664
+ }
665
+ }
666
+
667
+ function decodeNumber256(n: string): Number256 {
668
+ if (Number.isSafeInteger(Number.parseInt(n))) {
669
+ return Number(n)
670
+ } else {
671
+ return BigInt(n)
672
+ }
673
+ }
674
+
675
+ export function extractArray(tpe: string, v: Val): node.Val {
676
+ if (!Array.isArray(v)) {
677
+ throw new Error(`Expected array, got ${v}`)
678
+ }
679
+
680
+ const semiColonIndex = tpe.lastIndexOf(';')
681
+ if (semiColonIndex == -1) {
682
+ throw new Error(`Invalid Val type: ${tpe}`)
683
+ }
684
+
685
+ const subType = tpe.slice(1, semiColonIndex)
686
+ const dim = parseInt(tpe.slice(semiColonIndex + 1, -1))
687
+ if ((v as Val[]).length != dim) {
688
+ throw new Error(`Invalid val dimension: ${v}`)
689
+ } else {
690
+ return { value: (v as Val[]).map((v) => toApiVal(v, subType)), type: 'Array' }
691
+ }
692
+ }
693
+
694
+ export function toApiVal(v: Val, tpe: string): node.Val {
695
+ if (tpe === 'Bool') {
696
+ return { value: extractBoolean(v), type: tpe }
697
+ } else if (tpe === 'U256' || tpe === 'I256') {
698
+ return { value: extractNumber256(v), type: tpe }
699
+ } else if (tpe === 'ByteVec') {
700
+ return { value: extractByteVec(v), type: tpe }
701
+ } else if (tpe === 'Address') {
702
+ return { value: extractBs58(v), type: tpe }
703
+ } else {
704
+ return extractArray(tpe, v)
705
+ }
706
+ }
707
+
708
+ function decodeArrayType(tpe: string): [baseType: string, dims: number[]] {
709
+ const semiColonIndex = tpe.lastIndexOf(';')
710
+ if (semiColonIndex === -1) {
711
+ throw new Error(`Invalid Val type: ${tpe}`)
712
+ }
713
+
714
+ const subType = tpe.slice(1, semiColonIndex)
715
+ const dim = parseInt(tpe.slice(semiColonIndex + 1, -1))
716
+ if (subType[0] == '[') {
717
+ const [baseType, subDim] = decodeArrayType(subType)
718
+ return [baseType, (subDim.unshift(dim), subDim)]
719
+ } else {
720
+ return [subType, [dim]]
721
+ }
722
+ }
723
+
724
+ function foldVals(vals: Val[], dims: number[]): Val {
725
+ if (dims.length == 1) {
726
+ return vals
727
+ } else {
728
+ const result: Val[] = []
729
+ const chunkSize = vals.length / dims[0]
730
+ const chunkDims = dims.slice(1)
731
+ for (let i = 0; i < vals.length; i += chunkSize) {
732
+ const chunk = vals.slice(i, i + chunkSize)
733
+ result.push(foldVals(chunk, chunkDims))
734
+ }
735
+ return result
736
+ }
737
+ }
738
+
739
+ function _fromApiVal(vals: node.Val[], valIndex: number, tpe: string): [result: Val, nextIndex: number] {
740
+ if (vals.length === 0) {
741
+ throw new Error('Not enough Vals')
742
+ }
743
+
744
+ const firstVal = vals[`${valIndex}`]
745
+ if (tpe === 'Bool' && firstVal.type === tpe) {
746
+ return [firstVal.value as boolean, valIndex + 1]
747
+ } else if ((tpe === 'U256' || tpe === 'I256') && firstVal.type === tpe) {
748
+ return [decodeNumber256(firstVal.value as string), valIndex + 1]
749
+ } else if ((tpe === 'ByteVec' || tpe === 'Address') && firstVal.type === tpe) {
750
+ return [firstVal.value as string, valIndex + 1]
751
+ } else {
752
+ const [baseType, dims] = decodeArrayType(tpe)
753
+ const arraySize = dims.reduce((a, b) => a * b)
754
+ const nextIndex = valIndex + arraySize
755
+ const valsToUse = vals.slice(valIndex, nextIndex)
756
+ if (valsToUse.length == arraySize && valsToUse.every((val) => val.type === baseType)) {
757
+ const localVals = valsToUse.map((val) => fromApiVal(val, baseType))
758
+ return [foldVals(localVals, dims), nextIndex]
759
+ } else {
760
+ throw new Error(`Invalid array Val type: ${valsToUse}, ${tpe}`)
761
+ }
762
+ }
763
+ }
764
+
765
+ function fromApiFields(vals: node.Val[], fieldsSig: node.FieldsSig): Fields {
766
+ return fromApiVals(vals, fieldsSig.names, fieldsSig.types)
767
+ }
768
+
769
+ function fromApiEventFields(vals: node.Val[], eventSig: node.EventSig): Fields {
770
+ return fromApiVals(vals, eventSig.fieldNames, eventSig.fieldTypes)
771
+ }
772
+
773
+ function fromApiVals(vals: node.Val[], names: string[], types: string[]): Fields {
774
+ let valIndex = 0
775
+ const result: Fields = {}
776
+ types.forEach((currentType, index) => {
777
+ const currentName = names[`${index}`]
778
+ const [val, nextIndex] = _fromApiVal(vals, valIndex, currentType)
779
+ valIndex = nextIndex
780
+ result[`${currentName}`] = val
781
+ })
782
+ return result
783
+ }
784
+
785
+ function fromApiArray(vals: node.Val[], types: string[]): Val[] {
786
+ let valIndex = 0
787
+ const result: Val[] = []
788
+ for (const currentType of types) {
789
+ const [val, nextIndex] = _fromApiVal(vals, valIndex, currentType)
790
+ result.push(val)
791
+ valIndex = nextIndex
792
+ }
793
+ return result
794
+ }
795
+
796
+ function fromApiVal(v: node.Val, tpe: string): Val {
797
+ if (v.type === 'Bool' && v.type === tpe) {
798
+ return v.value as boolean
799
+ } else if ((v.type === 'U256' || v.type === 'I256') && v.type === tpe) {
800
+ return decodeNumber256(v.value as string)
801
+ } else if ((v.type === 'ByteVec' || v.type === 'Address') && v.type === tpe) {
802
+ return v.value as string
803
+ } else {
804
+ throw new Error(`Invalid node.Val type: ${v}`)
805
+ }
806
+ }
807
+
808
+ export interface Asset {
809
+ alphAmount: Number256
810
+ tokens?: Token[]
811
+ }
812
+
813
+ export interface Token {
814
+ id: string
815
+ amount: Number256
816
+ }
817
+
818
+ function toApiToken(token: Token): node.Token {
819
+ return { id: token.id, amount: extractNumber256(token.amount) }
820
+ }
821
+
822
+ function fromApiToken(token: node.Token): Token {
823
+ return { id: token.id, amount: decodeNumber256(token.amount) }
824
+ }
825
+
826
+ function toApiAsset(asset: Asset): node.AssetState {
827
+ return {
828
+ alphAmount: extractNumber256(asset.alphAmount),
829
+ tokens: typeof asset.tokens !== 'undefined' ? asset.tokens.map(toApiToken) : []
830
+ }
831
+ }
832
+
833
+ function fromApiAsset(asset: node.AssetState): Asset {
834
+ return {
835
+ alphAmount: decodeNumber256(asset.alphAmount),
836
+ tokens: typeof asset.tokens !== 'undefined' ? asset.tokens.map(fromApiToken) : undefined
837
+ }
838
+ }
839
+
840
+ export interface InputAsset {
841
+ address: string
842
+ asset: Asset
843
+ }
844
+
845
+ export interface ContractState {
846
+ address: string
847
+ contractId: string
848
+ bytecode: string
849
+ initialStateHash?: string
850
+ codeHash: string
851
+ fields: Fields
852
+ fieldsSig: node.FieldsSig
853
+ asset: Asset
854
+ }
855
+
856
+ function getVal(vals: NamedVals, name: string): Val {
857
+ if (name in vals) {
858
+ return vals[`${name}`]
859
+ } else {
860
+ throw Error(`No Val exists for ${name}`)
861
+ }
862
+ }
863
+
864
+ function toApiContractState(state: ContractState): node.ContractState {
865
+ return {
866
+ address: state.address,
867
+ bytecode: state.bytecode,
868
+ codeHash: state.codeHash,
869
+ initialStateHash: state.initialStateHash,
870
+ fields: toApiFields(state.fields, state.fieldsSig),
871
+ asset: toApiAsset(state.asset)
872
+ }
873
+ }
874
+
875
+ function toApiFields(fields: Fields, fieldsSig: node.FieldsSig): node.Val[] {
876
+ return toApiVals(fields, fieldsSig.names, fieldsSig.types)
877
+ }
878
+
879
+ function toApiArgs(args: Arguments, funcSig: node.FunctionSig): node.Val[] {
880
+ return toApiVals(args, funcSig.argNames, funcSig.argTypes)
881
+ }
882
+
883
+ function toApiVals(fields: Fields, names: string[], types: string[]): node.Val[] {
884
+ return names.map((name, index) => {
885
+ const val = getVal(fields, name)
886
+ const tpe = types[`${index}`]
887
+ return toApiVal(val, tpe)
888
+ })
889
+ }
890
+
891
+ function toApiInputAsset(inputAsset: InputAsset): node.TestInputAsset {
892
+ return { address: inputAsset.address, asset: toApiAsset(inputAsset.asset) }
893
+ }
894
+
895
+ function toApiInputAssets(inputAssets?: InputAsset[]): node.TestInputAsset[] | undefined {
896
+ return typeof inputAssets !== 'undefined' ? inputAssets.map(toApiInputAsset) : undefined
897
+ }
898
+
899
+ export interface TestContractParams {
900
+ group?: number // default 0
901
+ address?: string
902
+ initialFields?: Fields // default no fields
903
+ initialAsset?: Asset // default 1 ALPH
904
+ testMethodIndex?: number // default 0
905
+ testArgs?: Arguments // default no arguments
906
+ existingContracts?: ContractState[] // default no existing contracts
907
+ inputAssets?: InputAsset[] // default no input asserts
908
+ }
909
+
910
+ export interface ContractEvent {
911
+ blockHash: string
912
+ txId: string
913
+ name: string
914
+ fields: Fields
915
+ }
916
+
917
+ export interface ContractEventByTxId {
918
+ blockHash: string
919
+ contractAddress: string
920
+ name: string
921
+ fields: Fields
922
+ }
923
+
924
+ export interface TestContractResult {
925
+ address: string
926
+ contractId: string
927
+ returns: Val[]
928
+ gasUsed: number
929
+ contracts: ContractState[]
930
+ txOutputs: Output[]
931
+ events: ContractEventByTxId[]
932
+ }
933
+ export declare type Output = AssetOutput | ContractOutput
934
+ export interface AssetOutput extends Asset {
935
+ type: string
936
+ address: string
937
+ lockTime: number
938
+ message: string
939
+ }
940
+ export interface ContractOutput {
941
+ type: string
942
+ address: string
943
+ alphAmount: Number256
944
+ tokens: Token[]
945
+ }
946
+
947
+ function fromApiOutput(output: node.Output): Output {
948
+ if (output.type === 'AssetOutput') {
949
+ const asset = output as node.AssetOutput
950
+ return {
951
+ type: 'AssetOutput',
952
+ address: asset.address,
953
+ alphAmount: decodeNumber256(asset.alphAmount),
954
+ tokens: asset.tokens.map(fromApiToken),
955
+ lockTime: asset.lockTime,
956
+ message: asset.message
957
+ }
958
+ } else if (output.type === 'ContractOutput') {
959
+ const asset = output as node.ContractOutput
960
+ return {
961
+ type: 'ContractOutput',
962
+ address: asset.address,
963
+ alphAmount: decodeNumber256(asset.alphAmount),
964
+ tokens: asset.tokens.map(fromApiToken)
965
+ }
966
+ } else {
967
+ throw new Error(`Unknown output type: ${output}`)
968
+ }
969
+ }
970
+
971
+ export interface DeployContractTransaction {
972
+ fromGroup: number
973
+ toGroup: number
974
+ unsignedTx: string
975
+ txId: string
976
+ contractAddress: string
977
+ contractId: string
978
+ }
979
+
980
+ function fromApiDeployContractUnsignedTx(result: node.BuildDeployContractTxResult): DeployContractTransaction {
981
+ return { ...result, contractId: binToHex(contractIdFromAddress(result.contractAddress)) }
982
+ }
983
+
984
+ type BuildTxParams<T> = Omit<T, 'bytecode'> & { initialFields?: Val[] }
985
+
986
+ export interface BuildDeployContractTx {
987
+ signerAddress: string
988
+ initialFields?: Fields
989
+ initialAlphAmount?: string
990
+ initialTokenAmounts?: Token[]
991
+ issueTokenAmount?: Number256
992
+ gasAmount?: number
993
+ gasPrice?: Number256
994
+ submitTx?: boolean
995
+ }
996
+ assertType<Eq<keyof BuildDeployContractTx, keyof BuildTxParams<SignDeployContractTxParams>>>()
997
+
998
+ export interface BuildExecuteScriptTx {
999
+ signerAddress: string
1000
+ initialFields?: Fields
1001
+ alphAmount?: Number256
1002
+ tokens?: Token[]
1003
+ gasAmount?: number
1004
+ gasPrice?: Number256
1005
+ submitTx?: boolean
1006
+ }
1007
+ assertType<Eq<keyof BuildExecuteScriptTx, keyof BuildTxParams<SignExecuteScriptTxParams>>>()
1008
+
1009
+ export interface BuildScriptTxResult {
1010
+ fromGroup: number
1011
+ toGroup: number
1012
+ unsignedTx: string
1013
+ txId: string
1014
+ }