@alephium/web3 0.35.0 → 0.36.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.
@@ -20,7 +20,6 @@ import { Buffer } from 'buffer/'
20
20
  import fs from 'fs'
21
21
  import { promises as fsPromises } from 'fs'
22
22
  import {
23
- fromApiArray,
24
23
  fromApiNumber256,
25
24
  toApiNumber256,
26
25
  NamedVals,
@@ -32,9 +31,10 @@ import {
32
31
  Token,
33
32
  Val,
34
33
  fromApiTokens,
35
- fromApiVals,
36
- typeLength,
37
- getDefaultValue
34
+ getDefaultPrimitiveValue,
35
+ PrimitiveTypes,
36
+ decodeArrayType,
37
+ fromApiPrimitiveVal
38
38
  } from '../api'
39
39
  import { CompileProjectResult } from '../api/api-alephium'
40
40
  import {
@@ -84,7 +84,8 @@ enum SourceKind {
84
84
  Contract = 0,
85
85
  Script = 1,
86
86
  AbstractContract = 2,
87
- Interface = 3
87
+ Interface = 3,
88
+ Struct = 4
88
89
  }
89
90
 
90
91
  export type CompilerOptions = node.CompilerOptions & {
@@ -327,10 +328,45 @@ function removeOldArtifacts(dir: string) {
327
328
  }
328
329
  }
329
330
 
331
+ export class Struct {
332
+ name: string
333
+ fieldNames: string[]
334
+ fieldTypes: string[]
335
+ isMutable: boolean[]
336
+
337
+ constructor(name: string, fieldNames: string[], fieldTypes: string[], isMutable: boolean[]) {
338
+ this.name = name
339
+ this.fieldNames = fieldNames
340
+ this.fieldTypes = fieldTypes
341
+ this.isMutable = isMutable
342
+ }
343
+
344
+ static fromJson(json: any): Struct {
345
+ if (json.name === null || json.fieldNames === null || json.fieldTypes === null || json.isMutable === null) {
346
+ throw Error('The JSON for struct is incomplete')
347
+ }
348
+ return new Struct(json.name, json.fieldNames, json.fieldTypes, json.isMutable)
349
+ }
350
+
351
+ static fromStructSig(sig: node.StructSig): Struct {
352
+ return new Struct(sig.name, sig.fieldNames, sig.fieldTypes, sig.isMutable)
353
+ }
354
+
355
+ toJson(): any {
356
+ return {
357
+ name: this.name,
358
+ fieldNames: this.fieldNames,
359
+ fieldTypes: this.fieldTypes,
360
+ isMutable: this.isMutable
361
+ }
362
+ }
363
+ }
364
+
330
365
  export class Project {
331
366
  sourceInfos: SourceInfo[]
332
367
  contracts: Map<string, Compiled<Contract>>
333
368
  scripts: Map<string, Compiled<Script>>
369
+ structs: Struct[]
334
370
  projectArtifact: ProjectArtifact
335
371
 
336
372
  readonly contractsRootDir: string
@@ -346,11 +382,13 @@ export class Project {
346
382
  static readonly contractMatcher = new TypedMatcher('^Contract ([A-Z][a-zA-Z0-9]*)', SourceKind.Contract)
347
383
  static readonly interfaceMatcher = new TypedMatcher('^Interface ([A-Z][a-zA-Z0-9]*)', SourceKind.Interface)
348
384
  static readonly scriptMatcher = new TypedMatcher('^TxScript ([A-Z][a-zA-Z0-9]*)', SourceKind.Script)
385
+ static readonly structMatcher = new TypedMatcher('struct ([A-Z][a-zA-Z0-9]*)', SourceKind.Struct)
349
386
  static readonly matchers = [
350
387
  Project.abstractContractMatcher,
351
388
  Project.contractMatcher,
352
389
  Project.interfaceMatcher,
353
- Project.scriptMatcher
390
+ Project.scriptMatcher,
391
+ Project.structMatcher
354
392
  ]
355
393
 
356
394
  static buildProjectArtifact(
@@ -398,6 +436,7 @@ export class Project {
398
436
  sourceInfos: SourceInfo[],
399
437
  contracts: Map<string, Compiled<Contract>>,
400
438
  scripts: Map<string, Compiled<Script>>,
439
+ structs: Struct[],
401
440
  errorOnWarnings: boolean,
402
441
  projectArtifact: ProjectArtifact
403
442
  ) {
@@ -406,6 +445,7 @@ export class Project {
406
445
  this.sourceInfos = sourceInfos
407
446
  this.contracts = contracts
408
447
  this.scripts = scripts
448
+ this.structs = structs
409
449
  this.projectArtifact = projectArtifact
410
450
 
411
451
  if (errorOnWarnings) {
@@ -448,6 +488,24 @@ export class Project {
448
488
  return script.artifact
449
489
  }
450
490
 
491
+ private static async loadStructs(artifactsRootDir: string): Promise<Struct[]> {
492
+ const filePath = path.join(artifactsRootDir, 'structs.ral.json')
493
+ if (!fs.existsSync(filePath)) return []
494
+ const content = await fsPromises.readFile(filePath)
495
+ const json = JSON.parse(content.toString())
496
+ if (!Array.isArray(json)) {
497
+ throw Error(`Invalid structs JSON: ${content}`)
498
+ }
499
+ return Array.from(json).map((item) => Struct.fromJson(item))
500
+ }
501
+
502
+ private async saveStructsToFile(): Promise<void> {
503
+ if (this.structs.length === 0) return
504
+ const structs = this.structs.map((s) => s.toJson())
505
+ const filePath = path.join(this.artifactsRootDir, 'structs.ral.json')
506
+ return fsPromises.writeFile(filePath, JSON.stringify(structs, null, 2))
507
+ }
508
+
451
509
  private async saveArtifactsToFile(projectRootDir: string): Promise<void> {
452
510
  const artifactsRootDir = this.artifactsRootDir
453
511
  const saveToFile = async function (compiled: Compiled<Artifact>): Promise<void> {
@@ -460,6 +518,7 @@ export class Project {
460
518
  }
461
519
  this.contracts.forEach((contract) => saveToFile(contract))
462
520
  this.scripts.forEach((script) => saveToFile(script))
521
+ this.saveStructsToFile()
463
522
  await this.projectArtifact.saveToFile(projectRootDir)
464
523
  }
465
524
 
@@ -525,6 +584,7 @@ export class Project {
525
584
  const result = await Project.getCompileResult(provider, compilerOptions, removeDuplicates)
526
585
  const contracts = new Map<string, Compiled<Contract>>()
527
586
  const scripts = new Map<string, Compiled<Script>>()
587
+ const structs = result.structs === undefined ? [] : result.structs.map((item) => Struct.fromStructSig(item))
528
588
  result.contracts.forEach((contractResult) => {
529
589
  const sourceInfo = sourceInfos.find(
530
590
  (sourceInfo) => sourceInfo.type === SourceKind.Contract && sourceInfo.name === contractResult.name
@@ -533,7 +593,7 @@ export class Project {
533
593
  // this should never happen
534
594
  throw new Error(`SourceInfo does not exist for contract ${contractResult.name}`)
535
595
  }
536
- const contract = Contract.fromCompileResult(contractResult)
596
+ const contract = Contract.fromCompileResult(contractResult, structs)
537
597
  contracts.set(contract.name, new Compiled(sourceInfo, contract, contractResult.warnings))
538
598
  })
539
599
  result.scripts.forEach((scriptResult) => {
@@ -544,7 +604,7 @@ export class Project {
544
604
  // this should never happen
545
605
  throw new Error(`SourceInfo does not exist for script ${scriptResult.name}`)
546
606
  }
547
- const script = Script.fromCompileResult(scriptResult)
607
+ const script = Script.fromCompileResult(scriptResult, structs)
548
608
  scripts.set(script.name, new Compiled(sourceInfo, script, scriptResult.warnings))
549
609
  })
550
610
  const projectArtifact = Project.buildProjectArtifact(
@@ -560,6 +620,7 @@ export class Project {
560
620
  sourceInfos,
561
621
  contracts,
562
622
  scripts,
623
+ structs,
563
624
  errorOnWarnings,
564
625
  projectArtifact
565
626
  )
@@ -580,6 +641,7 @@ export class Project {
580
641
  try {
581
642
  const contracts = new Map<string, Compiled<Contract>>()
582
643
  const scripts = new Map<string, Compiled<Script>>()
644
+ const structs = await Project.loadStructs(artifactsRootDir)
583
645
  for (const sourceInfo of sourceInfos) {
584
646
  const info = projectArtifact.infos.get(sourceInfo.name)
585
647
  if (typeof info === 'undefined') {
@@ -588,10 +650,15 @@ export class Project {
588
650
  const warnings = info.warnings
589
651
  const artifactDir = sourceInfo.getArtifactPath(artifactsRootDir)
590
652
  if (sourceInfo.type === SourceKind.Contract) {
591
- const artifact = await Contract.fromArtifactFile(artifactDir, info.bytecodeDebugPatch, info.codeHashDebug)
653
+ const artifact = await Contract.fromArtifactFile(
654
+ artifactDir,
655
+ info.bytecodeDebugPatch,
656
+ info.codeHashDebug,
657
+ structs
658
+ )
592
659
  contracts.set(artifact.name, new Compiled(sourceInfo, artifact, warnings))
593
660
  } else if (sourceInfo.type === SourceKind.Script) {
594
- const artifact = await Script.fromArtifactFile(artifactDir, info.bytecodeDebugPatch)
661
+ const artifact = await Script.fromArtifactFile(artifactDir, info.bytecodeDebugPatch, structs)
595
662
  scripts.set(artifact.name, new Compiled(sourceInfo, artifact, warnings))
596
663
  }
597
664
  }
@@ -602,6 +669,7 @@ export class Project {
602
669
  sourceInfos,
603
670
  contracts,
604
671
  scripts,
672
+ structs,
605
673
  errorOnWarnings,
606
674
  projectArtifact
607
675
  )
@@ -821,6 +889,7 @@ export class Contract extends Artifact {
821
889
  readonly eventsSig: EventSig[]
822
890
  readonly constants: Constant[]
823
891
  readonly enums: Enum[]
892
+ readonly structs: Struct[]
824
893
  readonly stdInterfaceId?: HexString
825
894
 
826
895
  readonly bytecodeDebug: string
@@ -838,6 +907,7 @@ export class Contract extends Artifact {
838
907
  functions: FunctionSig[],
839
908
  constants: Constant[],
840
909
  enums: Enum[],
910
+ structs: Struct[],
841
911
  stdInterfaceId?: HexString
842
912
  ) {
843
913
  super(version, name, functions)
@@ -848,6 +918,7 @@ export class Contract extends Artifact {
848
918
  this.eventsSig = eventsSig
849
919
  this.constants = constants
850
920
  this.enums = enums
921
+ this.structs = structs
851
922
  this.stdInterfaceId = stdInterfaceId
852
923
 
853
924
  this.bytecodeDebug = ralph.buildDebugBytecode(this.bytecode, this.bytecodeDebugPatch)
@@ -855,7 +926,7 @@ export class Contract extends Artifact {
855
926
  }
856
927
 
857
928
  // TODO: safely parse json
858
- static fromJson(artifact: any, bytecodeDebugPatch = '', codeHashDebug = ''): Contract {
929
+ static fromJson(artifact: any, bytecodeDebugPatch = '', codeHashDebug = '', structs: Struct[] = []): Contract {
859
930
  if (
860
931
  artifact.version == null ||
861
932
  artifact.name == null ||
@@ -881,12 +952,13 @@ export class Contract extends Artifact {
881
952
  artifact.functions,
882
953
  artifact.constants,
883
954
  artifact.enums,
955
+ structs,
884
956
  artifact.stdInterfaceId === null ? undefined : artifact.stdInterfaceId
885
957
  )
886
958
  return contract
887
959
  }
888
960
 
889
- static fromCompileResult(result: node.CompileContractResult): Contract {
961
+ static fromCompileResult(result: node.CompileContractResult, structs: Struct[] = []): Contract {
890
962
  return new Contract(
891
963
  result.version,
892
964
  result.name,
@@ -899,15 +971,21 @@ export class Contract extends Artifact {
899
971
  result.functions,
900
972
  result.constants,
901
973
  result.enums,
974
+ structs,
902
975
  result.stdInterfaceId
903
976
  )
904
977
  }
905
978
 
906
979
  // support both 'code.ral' and 'code.ral.json'
907
- static async fromArtifactFile(path: string, bytecodeDebugPatch: string, codeHashDebug: string): Promise<Contract> {
980
+ static async fromArtifactFile(
981
+ path: string,
982
+ bytecodeDebugPatch: string,
983
+ codeHashDebug: string,
984
+ structs: Struct[] = []
985
+ ): Promise<Contract> {
908
986
  const content = await fsPromises.readFile(path)
909
987
  const artifact = JSON.parse(content.toString())
910
- return Contract.fromJson(artifact, bytecodeDebugPatch, codeHashDebug)
988
+ return Contract.fromJson(artifact, bytecodeDebugPatch, codeHashDebug, structs)
911
989
  }
912
990
 
913
991
  override toString(): string {
@@ -937,10 +1015,7 @@ export class Contract extends Artifact {
937
1015
  types: this.fieldsSig.types.slice(0, -1),
938
1016
  isMutable: this.fieldsSig.isMutable.slice(0, -1)
939
1017
  }
940
- return fields.names.reduce((acc, key, index) => {
941
- acc[`${key}`] = getDefaultValue(fields.types[`${index}`])
942
- return acc
943
- }, {})
1018
+ return getDefaultValue(fields, this.structs)
944
1019
  }
945
1020
 
946
1021
  toState<T extends Fields>(fields: T, asset: Asset, address?: string): ContractState<T> {
@@ -975,7 +1050,7 @@ export class Contract extends Artifact {
975
1050
  if (typeof fields === 'undefined') {
976
1051
  return []
977
1052
  } else {
978
- return toApiFields(fields, this.fieldsSig)
1053
+ return toApiFields(fields, this.fieldsSig, this.structs)
979
1054
  }
980
1055
  }
981
1056
 
@@ -986,7 +1061,7 @@ export class Contract extends Artifact {
986
1061
  throw new Error(`Invalid function name: ${funcName}`)
987
1062
  }
988
1063
 
989
- return toApiArgs(args, func)
1064
+ return toApiArgs(args, func, this.structs)
990
1065
  } else {
991
1066
  return []
992
1067
  }
@@ -997,14 +1072,22 @@ export class Contract extends Artifact {
997
1072
  }
998
1073
 
999
1074
  toApiContractStates(states?: ContractState[]): node.ContractState[] | undefined {
1000
- return typeof states != 'undefined' ? states.map((state) => toApiContractState(state)) : undefined
1075
+ return typeof states != 'undefined' ? states.map((state) => toApiContractState(state, this.structs)) : undefined
1001
1076
  }
1002
1077
 
1003
1078
  toApiTestContractParams(funcName: string, params: TestContractParams): node.TestContract {
1004
- const immFields =
1005
- params.initialFields === undefined ? [] : extractFields(params.initialFields, this.fieldsSig, false)
1006
- const mutFields =
1007
- params.initialFields === undefined ? [] : extractFields(params.initialFields, this.fieldsSig, true)
1079
+ const allFields =
1080
+ params.initialFields === undefined
1081
+ ? []
1082
+ : ralph.flattenFields(
1083
+ params.initialFields,
1084
+ this.fieldsSig.names,
1085
+ this.fieldsSig.types,
1086
+ this.fieldsSig.isMutable,
1087
+ this.structs
1088
+ )
1089
+ const immFields = allFields.filter((f) => !f.isMutable).map((f) => toApiVal(f.value, f.type))
1090
+ const mutFields = allFields.filter((f) => f.isMutable).map((f) => toApiVal(f.value, f.type))
1008
1091
  return {
1009
1092
  group: params.group,
1010
1093
  blockHash: params.blockHash,
@@ -1030,7 +1113,7 @@ export class Contract extends Artifact {
1030
1113
  bytecode: state.bytecode,
1031
1114
  initialStateHash: state.initialStateHash,
1032
1115
  codeHash: state.codeHash,
1033
- fields: fromApiFields(state.immFields, state.mutFields, this.fieldsSig),
1116
+ fields: fromApiFields(state.immFields, state.mutFields, this.fieldsSig, this.structs),
1034
1117
  fieldsSig: this.fieldsSig,
1035
1118
  asset: fromApiAsset(state.asset)
1036
1119
  }
@@ -1101,7 +1184,7 @@ export class Contract extends Artifact {
1101
1184
  ): TestContractResult<unknown> {
1102
1185
  const methodIndex = this.functions.findIndex((sig) => sig.name === methodName)
1103
1186
  const returnTypes = this.functions[`${methodIndex}`].returnTypes
1104
- const rawReturn = fromApiArray(result.returns, returnTypes)
1187
+ const rawReturn = fromApiArray(result.returns, returnTypes, this.structs)
1105
1188
  const returns = rawReturn.length === 0 ? null : rawReturn.length === 1 ? rawReturn[0] : rawReturn
1106
1189
 
1107
1190
  const addressToCodeHash = new Map<string, string>()
@@ -1133,6 +1216,7 @@ export class Contract extends Artifact {
1133
1216
  bytecode: bytecode,
1134
1217
  initialAttoAlphAmount: params?.initialAttoAlphAmount,
1135
1218
  issueTokenAmount: params?.issueTokenAmount,
1219
+ issueTokenTo: params?.issueTokenTo,
1136
1220
  initialTokenAmounts: params?.initialTokenAmounts,
1137
1221
  gasAmount: params?.gasAmount,
1138
1222
  gasPrice: params?.gasPrice
@@ -1142,7 +1226,12 @@ export class Contract extends Artifact {
1142
1226
 
1143
1227
  buildByteCodeToDeploy(initialFields: Fields, isDevnet: boolean): string {
1144
1228
  try {
1145
- return ralph.buildContractByteCode(isDevnet ? this.bytecodeDebug : this.bytecode, initialFields, this.fieldsSig)
1229
+ return ralph.buildContractByteCode(
1230
+ isDevnet ? this.bytecodeDebug : this.bytecode,
1231
+ initialFields,
1232
+ this.fieldsSig,
1233
+ this.structs
1234
+ )
1146
1235
  } catch (error) {
1147
1236
  throw new Error(`Failed to build bytecode for contract ${this.name}, error: ${error}`)
1148
1237
  }
@@ -1172,7 +1261,7 @@ export class Contract extends Artifact {
1172
1261
  methodIndex: number
1173
1262
  ): node.CallContract {
1174
1263
  const functionSig = this.functions[`${methodIndex}`]
1175
- const args = toApiVals(params.args ?? {}, functionSig.paramNames, functionSig.paramTypes)
1264
+ const args = toApiArgs(params.args ?? {}, functionSig, this.structs)
1176
1265
  return {
1177
1266
  ...params,
1178
1267
  group: groupIndex,
@@ -1190,7 +1279,7 @@ export class Contract extends Artifact {
1190
1279
  ): CallContractResult<unknown> {
1191
1280
  const returnTypes = this.functions[`${methodIndex}`].returnTypes
1192
1281
  const callResult = tryGetCallResult(result)
1193
- const rawReturn = fromApiArray(callResult.returns, returnTypes)
1282
+ const rawReturn = fromApiArray(callResult.returns, returnTypes, this.structs)
1194
1283
  const returns = rawReturn.length === 0 ? null : rawReturn.length === 1 ? rawReturn[0] : rawReturn
1195
1284
 
1196
1285
  const addressToCodeHash = new Map<string, string>()
@@ -1211,6 +1300,7 @@ export class Script extends Artifact {
1211
1300
  readonly bytecodeTemplate: string
1212
1301
  readonly bytecodeDebugPatch: string
1213
1302
  readonly fieldsSig: FieldsSig
1303
+ readonly structs: Struct[]
1214
1304
 
1215
1305
  constructor(
1216
1306
  version: string,
@@ -1218,27 +1308,30 @@ export class Script extends Artifact {
1218
1308
  bytecodeTemplate: string,
1219
1309
  bytecodeDebugPatch: string,
1220
1310
  fieldsSig: FieldsSig,
1221
- functions: FunctionSig[]
1311
+ functions: FunctionSig[],
1312
+ structs: Struct[]
1222
1313
  ) {
1223
1314
  super(version, name, functions)
1224
1315
  this.bytecodeTemplate = bytecodeTemplate
1225
1316
  this.bytecodeDebugPatch = bytecodeDebugPatch
1226
1317
  this.fieldsSig = fieldsSig
1318
+ this.structs = structs
1227
1319
  }
1228
1320
 
1229
- static fromCompileResult(result: node.CompileScriptResult): Script {
1321
+ static fromCompileResult(result: node.CompileScriptResult, structs: Struct[] = []): Script {
1230
1322
  return new Script(
1231
1323
  result.version,
1232
1324
  result.name,
1233
1325
  result.bytecodeTemplate,
1234
1326
  result.bytecodeDebugPatch,
1235
1327
  result.fields,
1236
- result.functions
1328
+ result.functions,
1329
+ structs
1237
1330
  )
1238
1331
  }
1239
1332
 
1240
1333
  // TODO: safely parse json
1241
- static fromJson(artifact: any, bytecodeDebugPatch = ''): Script {
1334
+ static fromJson(artifact: any, bytecodeDebugPatch = '', structs: Struct[] = []): Script {
1242
1335
  if (
1243
1336
  artifact.version == null ||
1244
1337
  artifact.name == null ||
@@ -1254,14 +1347,15 @@ export class Script extends Artifact {
1254
1347
  artifact.bytecodeTemplate,
1255
1348
  bytecodeDebugPatch,
1256
1349
  artifact.fieldsSig,
1257
- artifact.functions
1350
+ artifact.functions,
1351
+ structs
1258
1352
  )
1259
1353
  }
1260
1354
 
1261
- static async fromArtifactFile(path: string, bytecodeDebugPatch: string): Promise<Script> {
1355
+ static async fromArtifactFile(path: string, bytecodeDebugPatch: string, structs: Struct[] = []): Promise<Script> {
1262
1356
  const content = await fsPromises.readFile(path)
1263
1357
  const artifact = JSON.parse(content.toString())
1264
- return this.fromJson(artifact, bytecodeDebugPatch)
1358
+ return this.fromJson(artifact, bytecodeDebugPatch, structs)
1265
1359
  }
1266
1360
 
1267
1361
  override toString(): string {
@@ -1294,34 +1388,86 @@ export class Script extends Artifact {
1294
1388
 
1295
1389
  buildByteCodeToDeploy(initialFields: Fields): string {
1296
1390
  try {
1297
- return ralph.buildScriptByteCode(this.bytecodeTemplate, initialFields, this.fieldsSig)
1391
+ return ralph.buildScriptByteCode(this.bytecodeTemplate, initialFields, this.fieldsSig, this.structs)
1298
1392
  } catch (error) {
1299
1393
  throw new Error(`Failed to build bytecode for script ${this.name}, error: ${error}`)
1300
1394
  }
1301
1395
  }
1302
1396
  }
1303
1397
 
1304
- function fromApiFields(immFields: node.Val[], mutFields: node.Val[], fieldsSig: node.FieldsSig): Fields {
1305
- const vals: node.Val[] = []
1306
- let immIndex = 0
1307
- let mutIndex = 0
1308
- const isMutable = fieldsSig.types.flatMap((tpe, index) =>
1309
- Array(typeLength(tpe)).fill(fieldsSig.isMutable[`${index}`])
1310
- )
1311
- isMutable.forEach((mutable) => {
1312
- if (mutable) {
1313
- vals.push(mutFields[`${mutIndex}`])
1314
- mutIndex += 1
1315
- } else {
1316
- vals.push(immFields[`${immIndex}`])
1317
- immIndex += 1
1318
- }
1319
- })
1320
- return fromApiVals(vals, fieldsSig.names, fieldsSig.types)
1398
+ export function fromApiFields(
1399
+ immFields: node.Val[],
1400
+ mutFields: node.Val[],
1401
+ fieldsSig: FieldsSig,
1402
+ structs: Struct[]
1403
+ ): NamedVals {
1404
+ let [immIndex, mutIndex] = [0, 0]
1405
+ const func = (type: string, isMutable: boolean): Val => {
1406
+ const nodeVal = isMutable ? mutFields[mutIndex++] : immFields[immIndex++]
1407
+ return fromApiPrimitiveVal(nodeVal, type)
1408
+ }
1409
+
1410
+ return fieldsSig.names.reduce((acc, name, index) => {
1411
+ const fieldType = fieldsSig.types[`${index}`]
1412
+ const isMutable = fieldsSig.isMutable[`${index}`]
1413
+ acc[`${name}`] = buildVal(isMutable, fieldType, structs, func)
1414
+ return acc
1415
+ }, {})
1416
+ }
1417
+
1418
+ function buildVal(
1419
+ isMutable: boolean,
1420
+ type: string,
1421
+ structs: Struct[],
1422
+ func: (primitiveType: string, isMutable: boolean) => Val
1423
+ ): Val {
1424
+ if (type.startsWith('[')) {
1425
+ const [baseType, size] = decodeArrayType(type)
1426
+ return Array.from(Array(size).keys()).map(() => buildVal(isMutable, baseType, structs, func))
1427
+ }
1428
+ const struct = structs.find((s) => s.name === type)
1429
+ if (struct !== undefined) {
1430
+ return struct.fieldNames.reduce((acc, name, index) => {
1431
+ const fieldType = struct.fieldTypes[`${index}`]
1432
+ const isFieldMutable = isMutable && struct.isMutable[`${index}`]
1433
+ acc[`${name}`] = buildVal(isFieldMutable, fieldType, structs, func)
1434
+ return acc
1435
+ }, {})
1436
+ }
1437
+ const primitiveType = PrimitiveTypes.includes(type) ? type : 'ByteVec' // contract type
1438
+ return func(primitiveType, isMutable)
1439
+ }
1440
+
1441
+ export function getDefaultValue(fieldsSig: FieldsSig, structs: Struct[]): Fields {
1442
+ return fieldsSig.names.reduce((acc, name, index) => {
1443
+ const type = fieldsSig.types[`${index}`]
1444
+ acc[`${name}`] = buildVal(false, type, structs, getDefaultPrimitiveValue)
1445
+ return acc
1446
+ }, {})
1447
+ }
1448
+
1449
+ function fromApiVal(iter: IterableIterator<node.Val>, type: string, structs: Struct[], systemEvent = false): Val {
1450
+ const func = (primitiveType: string): Val => {
1451
+ const currentValue = iter.next()
1452
+ if (currentValue.done) throw Error('Not enough vals')
1453
+ return fromApiPrimitiveVal(currentValue.value, primitiveType, systemEvent)
1454
+ }
1455
+ return buildVal(false, type, structs, func)
1456
+ }
1457
+
1458
+ export function fromApiArray(values: node.Val[], types: string[], structs: Struct[]): Val[] {
1459
+ const iter = values.values()
1460
+ return types.map((type) => fromApiVal(iter, type, structs))
1321
1461
  }
1322
1462
 
1323
- function fromApiEventFields(vals: node.Val[], eventSig: node.EventSig, systemEvent = false): Fields {
1324
- return fromApiVals(vals, eventSig.fieldNames, eventSig.fieldTypes, systemEvent)
1463
+ export function fromApiEventFields(vals: node.Val[], eventSig: node.EventSig, systemEvent = false): Fields {
1464
+ const iter = vals.values()
1465
+ return eventSig.fieldNames.reduce((acc, name, index) => {
1466
+ const type = eventSig.fieldTypes[`${index}`]
1467
+ // currently event does not support struct type
1468
+ acc[`${name}`] = fromApiVal(iter, type, [], systemEvent)
1469
+ return acc
1470
+ }, {})
1325
1471
  }
1326
1472
 
1327
1473
  export interface Asset {
@@ -1359,50 +1505,33 @@ export interface ContractState<T extends Fields = Fields> {
1359
1505
  asset: Asset
1360
1506
  }
1361
1507
 
1362
- function getVal(vals: NamedVals, name: string): Val {
1363
- if (name in vals) {
1364
- return vals[`${name}`]
1365
- } else {
1366
- throw Error(`No Val exists for ${name}`)
1367
- }
1368
- }
1369
-
1370
- function extractFields(fields: NamedVals, fieldsSig: FieldsSig, mutable: boolean) {
1371
- const fieldIndexes = fieldsSig.names
1372
- .map((_, index) => index)
1373
- .filter((index) => fieldsSig.isMutable[`${index}`] === mutable)
1374
- const fieldNames = fieldIndexes.map((index) => fieldsSig.names[`${index}`])
1375
- const fieldTypes = fieldIndexes.map((index) => fieldsSig.types[`${index}`])
1376
- return toApiVals(fields, fieldNames, fieldTypes)
1377
- }
1378
-
1379
- function toApiContractState(state: ContractState): node.ContractState {
1508
+ function toApiContractState(state: ContractState, structs: Struct[]): node.ContractState {
1380
1509
  const stateFields = state.fields ?? {}
1510
+ const fieldsSig = state.fieldsSig
1511
+ const allFields = ralph.flattenFields(stateFields, fieldsSig.names, fieldsSig.types, fieldsSig.isMutable, structs)
1512
+ const immFields = allFields.filter((f) => !f.isMutable).map((f) => toApiVal(f.value, f.type))
1513
+ const mutFields = allFields.filter((f) => f.isMutable).map((f) => toApiVal(f.value, f.type))
1381
1514
  return {
1382
1515
  address: state.address,
1383
1516
  bytecode: state.bytecode,
1384
1517
  codeHash: state.codeHash,
1385
1518
  initialStateHash: state.initialStateHash,
1386
- immFields: extractFields(stateFields, state.fieldsSig, false),
1387
- mutFields: extractFields(stateFields, state.fieldsSig, true),
1519
+ immFields,
1520
+ mutFields,
1388
1521
  asset: toApiAsset(state.asset)
1389
1522
  }
1390
1523
  }
1391
1524
 
1392
- function toApiFields(fields: Fields, fieldsSig: FieldsSig): node.Val[] {
1393
- return toApiVals(fields, fieldsSig.names, fieldsSig.types)
1525
+ function toApiFields(fields: Fields, fieldsSig: FieldsSig, structs: Struct[]): node.Val[] {
1526
+ return ralph
1527
+ .flattenFields(fields, fieldsSig.names, fieldsSig.types, fieldsSig.isMutable, structs)
1528
+ .map((f) => toApiVal(f.value, f.type))
1394
1529
  }
1395
1530
 
1396
- function toApiArgs(args: Arguments, funcSig: FunctionSig): node.Val[] {
1397
- return toApiVals(args, funcSig.paramNames, funcSig.paramTypes)
1398
- }
1399
-
1400
- export function toApiVals(fields: Fields, names: string[], types: string[]): node.Val[] {
1401
- return names.map((name, index) => {
1402
- const val = getVal(fields, name)
1403
- const tpe = types[`${index}`]
1404
- return toApiVal(val, tpe)
1405
- })
1531
+ function toApiArgs(args: Arguments, funcSig: FunctionSig, structs: Struct[]): node.Val[] {
1532
+ return ralph
1533
+ .flattenFields(args, funcSig.paramNames, funcSig.paramTypes, funcSig.paramIsMutable, structs)
1534
+ .map((f) => toApiVal(f.value, f.type))
1406
1535
  }
1407
1536
 
1408
1537
  function toApiInputAsset(inputAsset: InputAsset): node.TestInputAsset {
@@ -1782,9 +1911,7 @@ export function decodeEvent<F extends Fields, M extends ContractEvent<F>>(
1782
1911
  throw new Error('Invalid event index: ' + event.eventIndex + ', expected: ' + targetEventIndex)
1783
1912
  }
1784
1913
  const eventSig = contract.eventsSig[`${targetEventIndex}`]
1785
- const fieldNames = eventSig.fieldNames
1786
- const fieldTypes = eventSig.fieldTypes
1787
- const fields = fromApiVals(event.fields, fieldNames, fieldTypes)
1914
+ const fields = fromApiEventFields(event.fields, eventSig)
1788
1915
  return {
1789
1916
  contractAddress: instance.address,
1790
1917
  blockHash: event.blockHash,