starknet 4.16.0 → 4.17.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "starknet",
3
- "version": "4.16.0",
3
+ "version": "4.17.0",
4
4
  "description": "JavaScript library for StarkNet",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -21,13 +21,14 @@
21
21
  "build:esm": "tsup --clean false --format esm --platform node",
22
22
  "build:iife": "tsup --clean false --format iife --platform browser",
23
23
  "build:dts": "tsup --clean false --dts-only",
24
- "pretest": "npm run lint",
24
+ "pretest": "npm run lint && npm run ts:check",
25
25
  "test": "jest -i",
26
26
  "posttest": "npm run format",
27
27
  "test:watch": "jest --watch",
28
28
  "docs": "cd www && npm run start",
29
29
  "format": "prettier --loglevel warn --write \"**/*.{ts,js,md,yml,json}\"",
30
- "lint": "eslint . --cache --fix --ext .ts"
30
+ "lint": "eslint . --cache --fix --ext .ts",
31
+ "ts:check": "tsc --noEmit --resolveJsonModule --project tsconfig.eslint.json"
31
32
  },
32
33
  "keywords": [
33
34
  "starknet",
@@ -1,7 +1,8 @@
1
1
  import assert from 'minimalistic-assert';
2
2
 
3
3
  import { AccountInterface } from '../account';
4
- import { Abi, CompiledContract, RawArgs } from '../types';
4
+ import { Abi, CompiledContract, FunctionAbi } from '../types';
5
+ import { CheckCallData } from '../utils/calldata';
5
6
  import { Contract } from './default';
6
7
 
7
8
  export class ContractFactory {
@@ -13,6 +14,8 @@ export class ContractFactory {
13
14
 
14
15
  account: AccountInterface;
15
16
 
17
+ private checkCalldata: CheckCallData;
18
+
16
19
  constructor(
17
20
  compiledContract: CompiledContract,
18
21
  classHash: string,
@@ -23,19 +26,23 @@ export class ContractFactory {
23
26
  this.compiledContract = compiledContract;
24
27
  this.account = account;
25
28
  this.classHash = classHash;
29
+ this.checkCalldata = new CheckCallData(abi);
26
30
  }
27
31
 
28
32
  /**
29
33
  * Deploys contract and returns new instance of the Contract
30
34
  *
31
- * @param constructorCalldata - Constructor Calldata
35
+ * @param args - Array of the constructor arguments for deployment
32
36
  * @param addressSalt (optional) - Address Salt for deployment
33
37
  * @returns deployed Contract
34
38
  */
35
- public async deploy(
36
- constructorCalldata?: RawArgs,
37
- addressSalt?: string | undefined
38
- ): Promise<Contract> {
39
+ public async deploy(args: Array<any> = [], addressSalt?: string | undefined): Promise<Contract> {
40
+ this.checkCalldata.validateMethodAndArgs('DEPLOY', 'constructor', args);
41
+ const { inputs } = this.abi.find((abi) => abi.type === 'constructor') as FunctionAbi;
42
+
43
+ // compile calldata
44
+ const constructorCalldata = this.checkCalldata.compileCalldata(args, inputs);
45
+
39
46
  const {
40
47
  deploy: { contract_address, transaction_hash },
41
48
  } = await this.account.declareDeploy({
@@ -10,7 +10,6 @@ import {
10
10
  AsyncContractFunction,
11
11
  BlockTag,
12
12
  Call,
13
- Calldata,
14
13
  ContractFunction,
15
14
  FunctionAbi,
16
15
  InvokeFunctionResponse,
@@ -19,7 +18,8 @@ import {
19
18
  Result,
20
19
  StructAbi,
21
20
  } from '../types';
22
- import { BigNumberish, toBN, toFelt } from '../utils/number';
21
+ import { CheckCallData } from '../utils/calldata';
22
+ import { BigNumberish, toBN } from '../utils/number';
23
23
  import { CallOptions, ContractInterface } from './interface';
24
24
 
25
25
  function parseFelt(candidate: string): BN {
@@ -121,6 +121,8 @@ export class Contract implements ContractInterface {
121
121
 
122
122
  readonly [key: string]: AsyncContractFunction | any;
123
123
 
124
+ private checkCalldata: CheckCallData;
125
+
124
126
  /**
125
127
  * Contract class to handle contract methods
126
128
  *
@@ -145,6 +147,7 @@ export class Contract implements ContractInterface {
145
147
  }),
146
148
  {}
147
149
  );
150
+ this.checkCalldata = new CheckCallData(abi);
148
151
 
149
152
  Object.defineProperty(this, 'functions', {
150
153
  enumerable: true,
@@ -240,11 +243,11 @@ export class Contract implements ContractInterface {
240
243
  assert(this.address !== null, 'contract is not connected to an address');
241
244
 
242
245
  // validate method and args
243
- this.validateMethodAndArgs('CALL', method, args);
246
+ this.checkCalldata.validateMethodAndArgs('CALL', method, args);
244
247
  const { inputs } = this.abi.find((abi) => abi.name === method) as FunctionAbi;
245
248
 
246
249
  // compile calldata
247
- const calldata = this.compileCalldata(args, inputs);
250
+ const calldata = this.checkCalldata.compileCalldata(args, inputs);
248
251
  return this.providerOrAccount
249
252
  .callContract(
250
253
  {
@@ -265,7 +268,7 @@ export class Contract implements ContractInterface {
265
268
  // ensure contract is connected
266
269
  assert(this.address !== null, 'contract is not connected to an address');
267
270
  // validate method and args
268
- this.validateMethodAndArgs('INVOKE', method, args);
271
+ this.checkCalldata.validateMethodAndArgs('INVOKE', method, args);
269
272
 
270
273
  const { inputs } = this.abi.find((abi) => abi.name === method) as FunctionAbi;
271
274
  const inputsLength = inputs.reduce((acc, input) => {
@@ -281,7 +284,7 @@ export class Contract implements ContractInterface {
281
284
  );
282
285
  }
283
286
  // compile calldata
284
- const calldata = this.compileCalldata(args, inputs);
287
+ const calldata = this.checkCalldata.compileCalldata(args, inputs);
285
288
 
286
289
  const invocation = {
287
290
  contractAddress: this.address,
@@ -318,7 +321,7 @@ export class Contract implements ContractInterface {
318
321
  assert(this.address !== null, 'contract is not connected to an address');
319
322
 
320
323
  // validate method and args
321
- this.validateMethodAndArgs('INVOKE', method, args);
324
+ this.checkCalldata.validateMethodAndArgs('INVOKE', method, args);
322
325
  const invocation = this.populateTransaction[method](...args);
323
326
  if ('estimateInvokeFee' in this.providerOrAccount) {
324
327
  return this.providerOrAccount.estimateInvokeFee(invocation);
@@ -331,167 +334,10 @@ export class Contract implements ContractInterface {
331
334
  return {
332
335
  contractAddress: this.address,
333
336
  entrypoint: method,
334
- calldata: this.compileCalldata(args, inputs),
337
+ calldata: this.checkCalldata.compileCalldata(args, inputs),
335
338
  };
336
339
  }
337
340
 
338
- /**
339
- * Deep parse of the object that has been passed to the method
340
- *
341
- * @param struct - struct that needs to be calculated
342
- * @return {number} - number of members for the given struct
343
- */
344
- private calculateStructMembers(struct: string): number {
345
- return this.structs[struct].members.reduce((acc, member) => {
346
- if (member.type === 'felt') {
347
- return acc + 1;
348
- }
349
- return acc + this.calculateStructMembers(member.type);
350
- }, 0);
351
- }
352
-
353
- /**
354
- * Validates if all arguments that are passed to the method are corresponding to the ones in the abi
355
- *
356
- * @param type - type of the method
357
- * @param method - name of the method
358
- * @param args - arguments that are passed to the method
359
- */
360
- protected validateMethodAndArgs(type: 'INVOKE' | 'CALL', method: string, args: Array<any> = []) {
361
- // ensure provided method exists
362
- const invocableFunctionNames = this.abi
363
- .filter((abi) => {
364
- if (abi.type !== 'function') return false;
365
- const isView = abi.stateMutability === 'view';
366
- return type === 'INVOKE' ? !isView : isView;
367
- })
368
- .map((abi) => abi.name);
369
- assert(
370
- invocableFunctionNames.includes(method),
371
- `${type === 'INVOKE' ? 'invocable' : 'viewable'} method not found in abi`
372
- );
373
-
374
- // ensure args match abi type
375
- const methodAbi = this.abi.find(
376
- (abi) => abi.name === method && abi.type === 'function'
377
- ) as FunctionAbi;
378
- let argPosition = 0;
379
- methodAbi.inputs.forEach((input) => {
380
- if (/_len$/.test(input.name)) {
381
- return;
382
- }
383
- if (input.type === 'felt') {
384
- assert(
385
- typeof args[argPosition] === 'string' ||
386
- typeof args[argPosition] === 'number' ||
387
- args[argPosition] instanceof BN,
388
- `arg ${input.name} should be a felt (string, number, BigNumber)`
389
- );
390
- argPosition += 1;
391
- } else if (input.type in this.structs && typeof args[argPosition] === 'object') {
392
- if (Array.isArray(args[argPosition])) {
393
- const structMembersLength = this.calculateStructMembers(input.type);
394
- assert(
395
- args[argPosition].length === structMembersLength,
396
- `arg should be of length ${structMembersLength}`
397
- );
398
- } else {
399
- this.structs[input.type].members.forEach(({ name }) => {
400
- assert(
401
- Object.keys(args[argPosition]).includes(name),
402
- `arg should have a property ${name}`
403
- );
404
- });
405
- }
406
- argPosition += 1;
407
- } else {
408
- assert(Array.isArray(args[argPosition]), `arg ${input.name} should be an Array`);
409
- if (input.type === 'felt*') {
410
- args[argPosition].forEach((felt: BigNumberish) => {
411
- assert(
412
- typeof felt === 'string' || typeof felt === 'number' || felt instanceof BN,
413
- `arg ${input.name} should be an array of string, number or BigNumber`
414
- );
415
- });
416
- argPosition += 1;
417
- } else if (/\(felt/.test(input.type)) {
418
- const tupleLength = input.type.split(',').length;
419
- assert(
420
- args[argPosition].length === tupleLength,
421
- `arg ${input.name} should have ${tupleLength} elements in tuple`
422
- );
423
- args[argPosition].forEach((felt: BigNumberish) => {
424
- assert(
425
- typeof felt === 'string' || typeof felt === 'number' || felt instanceof BN,
426
- `arg ${input.name} should be an array of string, number or BigNumber`
427
- );
428
- });
429
- argPosition += 1;
430
- } else {
431
- const arrayType = input.type.replace('*', '');
432
- args[argPosition].forEach((struct: any) => {
433
- this.structs[arrayType].members.forEach(({ name }) => {
434
- if (Array.isArray(struct)) {
435
- const structMembersLength = this.calculateStructMembers(arrayType);
436
- assert(
437
- struct.length === structMembersLength,
438
- `arg should be of length ${structMembersLength}`
439
- );
440
- } else {
441
- assert(
442
- Object.keys(struct).includes(name),
443
- `arg ${input.name} should be an array of ${arrayType}`
444
- );
445
- }
446
- });
447
- });
448
- argPosition += 1;
449
- }
450
- }
451
- });
452
- }
453
-
454
- /**
455
- * Deep parse of the object that has been passed to the method
456
- *
457
- * @param element - element that needs to be parsed
458
- * @param type - name of the method
459
- * @return {string | string[]} - parsed arguments in format that contract is expecting
460
- */
461
-
462
- protected parseCalldataValue(
463
- element: ParsedStruct | BigNumberish | BigNumberish[],
464
- type: string
465
- ): string | string[] {
466
- if (element === undefined) {
467
- throw Error('Missing element in calldata');
468
- }
469
- if (Array.isArray(element)) {
470
- const structMemberNum = this.calculateStructMembers(type);
471
- if (element.length !== structMemberNum) {
472
- throw Error('Missing element in calldata');
473
- }
474
- return element.map((el) => toFelt(el));
475
- }
476
- // checking if the passed element is struct or element in struct
477
- if (this.structs[type] && this.structs[type].members.length) {
478
- // going through all the members of the struct and parsing the value
479
- return this.structs[type].members.reduce((acc, member: AbiEntry) => {
480
- // if the member of the struct is another struct this will return array of the felts if not it will be single felt
481
- // TODO: refactor types so member name can be used as keyof ParsedStruct
482
- /* @ts-ignore */
483
- const parsedData = this.parseCalldataValue(element[member.name], member.type);
484
- if (typeof parsedData === 'string') {
485
- acc.push(parsedData);
486
- } else {
487
- acc.push(...parsedData);
488
- }
489
- return acc;
490
- }, [] as string[]);
491
- }
492
- return toFelt(element as BigNumberish);
493
- }
494
-
495
341
  /**
496
342
  * Parse of the response elements that are converted to Object (Struct) by using the abi
497
343
  *
@@ -514,67 +360,6 @@ export class Contract implements ContractInterface {
514
360
  return parseFelt(responseIterator.next().value);
515
361
  }
516
362
 
517
- /**
518
- * Parse one field of the calldata by using input field from the abi for that method
519
- *
520
- * @param args - value of the field
521
- * @param input - input(field) information from the abi that will be used to parse the data
522
- * @return {string | string[]} - parsed arguments in format that contract is expecting
523
- */
524
- protected parseCalldataField(argsIterator: Iterator<any>, input: AbiEntry): string | string[] {
525
- const { name, type } = input;
526
- const { value } = argsIterator.next();
527
-
528
- const parsedCalldata: string[] = [];
529
- switch (true) {
530
- case /\*/.test(type):
531
- if (Array.isArray(value)) {
532
- parsedCalldata.push(toFelt(value.length));
533
- return (value as (BigNumberish | ParsedStruct)[]).reduce((acc, el) => {
534
- if (/felt/.test(type)) {
535
- acc.push(toFelt(el as BigNumberish));
536
- } else {
537
- acc.push(...this.parseCalldataValue(el, type.replace('*', '')));
538
- }
539
- return acc;
540
- }, parsedCalldata);
541
- }
542
- throw Error(`Expected ${name} to be array`);
543
- case type in this.structs:
544
- return this.parseCalldataValue(value as ParsedStruct | BigNumberish[], type);
545
- case /\(felt/.test(type):
546
- if (Array.isArray(value)) {
547
- return value.map((el) => toFelt(el as BigNumberish));
548
- }
549
- throw Error(`Expected ${name} to be array`);
550
- default:
551
- return toFelt(value as BigNumberish);
552
- }
553
- }
554
-
555
- /**
556
- * Parse the calldata by using input fields from the abi for that method
557
- *
558
- * @param args - arguments passed the the method
559
- * @param inputs - list of inputs(fields) that are in the abi
560
- * @return {Calldata} - parsed arguments in format that contract is expecting
561
- */
562
- protected compileCalldata(args: Array<any>, inputs: AbiEntry[]): Calldata {
563
- const argsIterator = args[Symbol.iterator]();
564
- return inputs.reduce((acc, input) => {
565
- if (/_len$/.test(input.name)) {
566
- return acc;
567
- }
568
- const parsedData = this.parseCalldataField(argsIterator, input);
569
- if (Array.isArray(parsedData)) {
570
- acc.push(...parsedData);
571
- } else {
572
- acc.push(parsedData);
573
- }
574
- return acc;
575
- }, [] as Calldata);
576
- }
577
-
578
363
  /**
579
364
  * Parse elements of the response and structuring them into one field by using output property from the abi for that method
580
365
  *
@@ -67,7 +67,7 @@ export class Provider implements ProviderInterface {
67
67
 
68
68
  public async getClassAt(
69
69
  contractAddress: string,
70
- blockIdentifier: BlockIdentifier
70
+ blockIdentifier?: BlockIdentifier
71
71
  ): Promise<ContractClass> {
72
72
  return this.provider.getClassAt(contractAddress, blockIdentifier);
73
73
  }
@@ -113,7 +113,7 @@ export class Provider implements ProviderInterface {
113
113
  public async getStorageAt(
114
114
  contractAddress: string,
115
115
  key: BigNumberish,
116
- blockIdentifier: BlockIdentifier
116
+ blockIdentifier?: BlockIdentifier
117
117
  ): Promise<BigNumberish> {
118
118
  return this.provider.getStorageAt(contractAddress, key, blockIdentifier);
119
119
  }
@@ -150,7 +150,7 @@ export abstract class ProviderInterface {
150
150
 
151
151
  /**
152
152
  * Invokes a function on starknet
153
- * @deprecated This method wont be supported as soon as fees are mandatory
153
+ * @deprecated This method wont be supported as soon as fees are mandatory. Should not be used outside of Account class
154
154
  *
155
155
  * @param invocation the invocation object containing:
156
156
  * - contractAddress - the address of the contract
@@ -187,7 +187,7 @@ export abstract class ProviderInterface {
187
187
 
188
188
  /**
189
189
  * Estimates the fee for a given INVOKE transaction
190
- * @deprecated Please use getInvokeEstimateFee or getDeclareEstimateFee instead
190
+ * @deprecated Please use getInvokeEstimateFee or getDeclareEstimateFee instead. Should not be used outside of Account class
191
191
  *
192
192
  * @param invocation the invocation object containing:
193
193
  * - contractAddress - the address of the contract
@@ -0,0 +1,250 @@
1
+ import BN from 'bn.js';
2
+ import assert from 'minimalistic-assert';
3
+
4
+ import { Abi, AbiEntry, Calldata, FunctionAbi, ParsedStruct, StructAbi } from '../types';
5
+ import { BigNumberish, toFelt } from './number';
6
+
7
+ export class CheckCallData {
8
+ abi: Abi;
9
+
10
+ protected readonly structs: { [name: string]: StructAbi };
11
+
12
+ constructor(abi: Abi) {
13
+ this.abi = abi;
14
+ this.structs = abi
15
+ .filter((abiEntry) => abiEntry.type === 'struct')
16
+ .reduce(
17
+ (acc, abiEntry) => ({
18
+ ...acc,
19
+ [abiEntry.name]: abiEntry,
20
+ }),
21
+ {}
22
+ );
23
+ }
24
+
25
+ /**
26
+ * Parse the calldata by using input fields from the abi for that method
27
+ *
28
+ * @param args - arguments passed the the method
29
+ * @param inputs - list of inputs(fields) that are in the abi
30
+ * @return {Calldata} - parsed arguments in format that contract is expecting
31
+ */
32
+ public compileCalldata(args: Array<any>, inputs: AbiEntry[]): Calldata {
33
+ const argsIterator = args[Symbol.iterator]();
34
+ return inputs.reduce((acc, input) => {
35
+ if (/_len$/.test(input.name)) {
36
+ return acc;
37
+ }
38
+ const parsedData = this.parseCalldataField(argsIterator, input);
39
+ if (Array.isArray(parsedData)) {
40
+ acc.push(...parsedData);
41
+ } else {
42
+ acc.push(parsedData);
43
+ }
44
+ return acc;
45
+ }, [] as Calldata);
46
+ }
47
+
48
+ /**
49
+ * Validates if all arguments that are passed to the method are corresponding to the ones in the abi
50
+ *
51
+ * @param type - type of the method
52
+ * @param method - name of the method
53
+ * @param args - arguments that are passed to the method
54
+ */
55
+ public validateMethodAndArgs(
56
+ type: 'INVOKE' | 'CALL' | 'DEPLOY',
57
+ method: string,
58
+ args: Array<any> = []
59
+ ) {
60
+ // ensure provided method exists
61
+ if (type !== 'DEPLOY') {
62
+ const invocableFunctionNames = this.abi
63
+ .filter((abi) => {
64
+ if (abi.type !== 'function') return false;
65
+ const isView = abi.stateMutability === 'view';
66
+ return type === 'INVOKE' ? !isView : isView;
67
+ })
68
+ .map((abi) => abi.name);
69
+ assert(
70
+ invocableFunctionNames.includes(method),
71
+ `${type === 'INVOKE' ? 'invocable' : 'viewable'} method not found in abi`
72
+ );
73
+ }
74
+
75
+ // ensure args match abi type
76
+ const methodAbi = this.abi.find((abi) =>
77
+ type === 'DEPLOY'
78
+ ? abi.name === method && abi.type === method
79
+ : abi.name === method && abi.type === 'function'
80
+ ) as FunctionAbi;
81
+ let argPosition = 0;
82
+ methodAbi.inputs.forEach((input) => {
83
+ if (/_len$/.test(input.name)) {
84
+ return;
85
+ }
86
+ if (input.type === 'felt') {
87
+ assert(
88
+ typeof args[argPosition] === 'string' ||
89
+ typeof args[argPosition] === 'number' ||
90
+ args[argPosition] instanceof BN,
91
+ `arg ${input.name} should be a felt (string, number, BigNumber)`
92
+ );
93
+ argPosition += 1;
94
+ } else if (input.type in this.structs && typeof args[argPosition] === 'object') {
95
+ if (Array.isArray(args[argPosition])) {
96
+ const structMembersLength = this.calculateStructMembers(input.type);
97
+ assert(
98
+ args[argPosition].length === structMembersLength,
99
+ `arg should be of length ${structMembersLength}`
100
+ );
101
+ } else {
102
+ this.structs[input.type].members.forEach(({ name }) => {
103
+ assert(
104
+ Object.keys(args[argPosition]).includes(name),
105
+ `arg should have a property ${name}`
106
+ );
107
+ });
108
+ }
109
+ argPosition += 1;
110
+ } else {
111
+ assert(Array.isArray(args[argPosition]), `arg ${input.name} should be an Array`);
112
+ if (input.type === 'felt*') {
113
+ args[argPosition].forEach((felt: BigNumberish) => {
114
+ assert(
115
+ typeof felt === 'string' || typeof felt === 'number' || felt instanceof BN,
116
+ `arg ${input.name} should be an array of string, number or BigNumber`
117
+ );
118
+ });
119
+ argPosition += 1;
120
+ } else if (/\(felt/.test(input.type)) {
121
+ const tupleLength = input.type.split(',').length;
122
+ assert(
123
+ args[argPosition].length === tupleLength,
124
+ `arg ${input.name} should have ${tupleLength} elements in tuple`
125
+ );
126
+ args[argPosition].forEach((felt: BigNumberish) => {
127
+ assert(
128
+ typeof felt === 'string' || typeof felt === 'number' || felt instanceof BN,
129
+ `arg ${input.name} should be an array of string, number or BigNumber`
130
+ );
131
+ });
132
+ argPosition += 1;
133
+ } else {
134
+ const arrayType = input.type.replace('*', '');
135
+ args[argPosition].forEach((struct: any) => {
136
+ this.structs[arrayType].members.forEach(({ name }) => {
137
+ if (Array.isArray(struct)) {
138
+ const structMembersLength = this.calculateStructMembers(arrayType);
139
+ assert(
140
+ struct.length === structMembersLength,
141
+ `arg should be of length ${structMembersLength}`
142
+ );
143
+ } else {
144
+ assert(
145
+ Object.keys(struct).includes(name),
146
+ `arg ${input.name} should be an array of ${arrayType}`
147
+ );
148
+ }
149
+ });
150
+ });
151
+ argPosition += 1;
152
+ }
153
+ }
154
+ });
155
+ }
156
+
157
+ /**
158
+ * Parse one field of the calldata by using input field from the abi for that method
159
+ *
160
+ * @param args - value of the field
161
+ * @param input - input(field) information from the abi that will be used to parse the data
162
+ * @return {string | string[]} - parsed arguments in format that contract is expecting
163
+ */
164
+ protected parseCalldataField(argsIterator: Iterator<any>, input: AbiEntry): string | string[] {
165
+ const { name, type } = input;
166
+ const { value } = argsIterator.next();
167
+
168
+ const parsedCalldata: string[] = [];
169
+ switch (true) {
170
+ case /\*/.test(type):
171
+ if (Array.isArray(value)) {
172
+ parsedCalldata.push(toFelt(value.length));
173
+ return (value as (BigNumberish | ParsedStruct)[]).reduce((acc, el) => {
174
+ if (/felt/.test(type)) {
175
+ acc.push(toFelt(el as BigNumberish));
176
+ } else {
177
+ acc.push(...this.parseCalldataValue(el, type.replace('*', '')));
178
+ }
179
+ return acc;
180
+ }, parsedCalldata);
181
+ }
182
+ throw Error(`Expected ${name} to be array`);
183
+ case type in this.structs:
184
+ return this.parseCalldataValue(value as ParsedStruct | BigNumberish[], type);
185
+ case /\(felt/.test(type):
186
+ if (Array.isArray(value)) {
187
+ return value.map((el) => toFelt(el as BigNumberish));
188
+ }
189
+ throw Error(`Expected ${name} to be array`);
190
+ default:
191
+ return toFelt(value as BigNumberish);
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Deep parse of the object that has been passed to the method
197
+ *
198
+ * @param element - element that needs to be parsed
199
+ * @param type - name of the method
200
+ * @return {string | string[]} - parsed arguments in format that contract is expecting
201
+ */
202
+
203
+ protected parseCalldataValue(
204
+ element: ParsedStruct | BigNumberish | BigNumberish[],
205
+ type: string
206
+ ): string | string[] {
207
+ if (element === undefined) {
208
+ throw Error('Missing element in calldata');
209
+ }
210
+ if (Array.isArray(element)) {
211
+ const structMemberNum = this.calculateStructMembers(type);
212
+ if (element.length !== structMemberNum) {
213
+ throw Error('Missing element in calldata');
214
+ }
215
+ return element.map((el) => toFelt(el));
216
+ }
217
+ // checking if the passed element is struct or element in struct
218
+ if (this.structs[type] && this.structs[type].members.length) {
219
+ // going through all the members of the struct and parsing the value
220
+ return this.structs[type].members.reduce((acc, member: AbiEntry) => {
221
+ // if the member of the struct is another struct this will return array of the felts if not it will be single felt
222
+ // TODO: refactor types so member name can be used as keyof ParsedStruct
223
+ /* @ts-ignore */
224
+ const parsedData = this.parseCalldataValue(element[member.name], member.type);
225
+ if (typeof parsedData === 'string') {
226
+ acc.push(parsedData);
227
+ } else {
228
+ acc.push(...parsedData);
229
+ }
230
+ return acc;
231
+ }, [] as string[]);
232
+ }
233
+ return toFelt(element as BigNumberish);
234
+ }
235
+
236
+ /**
237
+ * Deep parse of the object that has been passed to the method
238
+ *
239
+ * @param struct - struct that needs to be calculated
240
+ * @return {number} - number of members for the given struct
241
+ */
242
+ private calculateStructMembers(struct: string): number {
243
+ return this.structs[struct].members.reduce((acc, member) => {
244
+ if (member.type === 'felt') {
245
+ return acc + 1;
246
+ }
247
+ return acc + this.calculateStructMembers(member.type);
248
+ }, 0);
249
+ }
250
+ }