starknet 3.7.0 → 3.10.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 (79) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +18 -53
  3. package/__mocks__/ArgentAccount.json +32022 -38726
  4. package/__tests__/accountContract.test.ts +42 -32
  5. package/__tests__/contract.test.ts +20 -6
  6. package/__tests__/utils/__snapshots__/utils.browser.test.ts.snap +2 -2
  7. package/__tests__/utils/__snapshots__/utils.test.ts.snap +2 -2
  8. package/__tests__/utils/ellipticalCurve.test.ts +26 -8
  9. package/__tests__/utils/transactionHash.test.ts +17 -0
  10. package/__tests__/utils/utils.test.ts +10 -0
  11. package/account/default.d.ts +11 -1
  12. package/account/default.js +58 -50
  13. package/constants.d.ts +9 -0
  14. package/constants.js +13 -0
  15. package/contract/default.d.ts +12 -2
  16. package/contract/default.js +27 -20
  17. package/contract/interface.d.ts +21 -3
  18. package/dist/account/default.d.ts +5 -1
  19. package/dist/account/default.js +46 -30
  20. package/dist/constants.d.ts +9 -0
  21. package/dist/constants.js +12 -1
  22. package/dist/contract/default.d.ts +6 -3
  23. package/dist/contract/default.js +23 -20
  24. package/dist/contract/interface.d.ts +9 -4
  25. package/dist/provider/default.d.ts +5 -2
  26. package/dist/provider/default.js +36 -19
  27. package/dist/provider/interface.d.ts +2 -0
  28. package/dist/provider/utils.d.ts +1 -2
  29. package/dist/provider/utils.js +7 -8
  30. package/dist/signer/default.js +4 -2
  31. package/dist/signer/ledger.js +4 -2
  32. package/dist/types/api.d.ts +4 -1
  33. package/dist/types/lib.d.ts +1 -0
  34. package/dist/types/signer.d.ts +2 -0
  35. package/dist/utils/hash.d.ts +5 -3
  36. package/dist/utils/hash.js +25 -23
  37. package/dist/utils/stark.d.ts +3 -0
  38. package/dist/utils/stark.js +8 -1
  39. package/dist/utils/transaction.d.ts +2 -0
  40. package/dist/utils/transaction.js +5 -1
  41. package/dist/utils/typedData/index.d.ts +2 -2
  42. package/dist/utils/typedData/types.d.ts +3 -3
  43. package/dist/utils/typedData/utils.d.ts +1 -1
  44. package/package.json +1 -1
  45. package/provider/default.d.ts +7 -2
  46. package/provider/default.js +49 -27
  47. package/provider/interface.d.ts +2 -0
  48. package/provider/utils.d.ts +1 -2
  49. package/provider/utils.js +7 -8
  50. package/signer/default.js +12 -5
  51. package/signer/ledger.js +12 -5
  52. package/src/account/default.ts +47 -18
  53. package/src/constants.ts +10 -0
  54. package/src/contract/default.ts +32 -19
  55. package/src/contract/interface.ts +21 -3
  56. package/src/provider/default.ts +37 -12
  57. package/src/provider/interface.ts +3 -0
  58. package/src/provider/utils.ts +7 -8
  59. package/src/signer/default.ts +10 -5
  60. package/src/signer/ledger.ts +10 -5
  61. package/src/types/api.ts +4 -1
  62. package/src/types/lib.ts +1 -0
  63. package/src/types/signer.ts +2 -0
  64. package/src/utils/hash.ts +70 -26
  65. package/src/utils/stark.ts +8 -1
  66. package/src/utils/transaction.ts +7 -0
  67. package/types/api.d.ts +4 -1
  68. package/types/lib.d.ts +1 -0
  69. package/types/signer.d.ts +2 -0
  70. package/utils/hash.d.ts +25 -7
  71. package/utils/hash.js +60 -26
  72. package/utils/stark.d.ts +4 -0
  73. package/utils/stark.js +13 -1
  74. package/utils/transaction.d.ts +5 -0
  75. package/utils/transaction.js +12 -1
  76. package/utils/typedData/index.d.ts +2 -2
  77. package/utils/typedData/types.d.ts +3 -3
  78. package/utils/typedData/utils.d.ts +1 -1
  79. package/__tests__/constancts.ts +0 -2
@@ -1,6 +1,8 @@
1
1
  import assert from 'minimalistic-assert';
2
2
 
3
+ import { ZERO } from '../constants';
3
4
  import { Provider } from '../provider';
5
+ import { BlockIdentifier } from '../provider/utils';
4
6
  import { Signer, SignerInterface } from '../signer';
5
7
  import {
6
8
  Abi,
@@ -8,6 +10,7 @@ import {
8
10
  Call,
9
11
  EstimateFeeResponse,
10
12
  InvocationsDetails,
13
+ InvocationsSignerDetails,
11
14
  InvokeFunctionTransaction,
12
15
  KeyPair,
13
16
  Signature,
@@ -16,13 +19,14 @@ import {
16
19
  import { sign } from '../utils/ellipticCurve';
17
20
  import {
18
21
  computeHashOnElements,
22
+ feeTransactionVersion,
19
23
  getSelectorFromName,
20
- transactionPrefix,
21
24
  transactionVersion,
22
25
  } from '../utils/hash';
23
26
  import { BigNumberish, bigNumberishArrayToDecimalStringArray, toBN, toHex } from '../utils/number';
24
- import { compileCalldata } from '../utils/stark';
25
- import { fromCallsToExecuteCalldata } from '../utils/transaction';
27
+ import { encodeShortString } from '../utils/shortString';
28
+ import { compileCalldata, estimatedFeeToMaxFee } from '../utils/stark';
29
+ import { fromCallsToExecuteCalldataWithNonce } from '../utils/transaction';
26
30
  import { TypedData, getMessageHash } from '../utils/typedData';
27
31
  import { AccountInterface } from './interface';
28
32
 
@@ -46,23 +50,39 @@ export class Account extends Provider implements AccountInterface {
46
50
  return toHex(toBN(result[0]));
47
51
  }
48
52
 
49
- public async estimateFee(calls: Call | Call[]): Promise<EstimateFeeResponse> {
53
+ public async estimateFee(
54
+ calls: Call | Call[],
55
+ {
56
+ nonce: providedNonce,
57
+ blockIdentifier = 'pending',
58
+ }: { nonce?: BigNumberish; blockIdentifier?: BlockIdentifier } = {}
59
+ ): Promise<EstimateFeeResponse> {
50
60
  const transactions = Array.isArray(calls) ? calls : [calls];
51
- const nonce = await this.getNonce();
52
- const signerDetails = {
61
+ const nonce = providedNonce ?? (await this.getNonce());
62
+ const version = toBN(feeTransactionVersion);
63
+
64
+ const signerDetails: InvocationsSignerDetails = {
53
65
  walletAddress: this.address,
54
66
  nonce: toBN(nonce),
55
- maxFee: toBN('0'),
67
+ maxFee: ZERO,
68
+ version,
69
+ chainId: this.chainId,
56
70
  };
71
+
57
72
  const signature = await this.signer.signTransaction(transactions, signerDetails);
58
73
 
59
- const calldata = [...fromCallsToExecuteCalldata(transactions), signerDetails.nonce.toString()];
60
- return this.fetchEndpoint('estimate_fee', undefined, {
61
- contract_address: this.address,
62
- entry_point_selector: getSelectorFromName('__execute__'),
63
- calldata,
64
- signature: bigNumberishArrayToDecimalStringArray(signature),
65
- });
74
+ const calldata = fromCallsToExecuteCalldataWithNonce(transactions, nonce);
75
+ return this.fetchEndpoint(
76
+ 'estimate_fee',
77
+ { blockIdentifier },
78
+ {
79
+ contract_address: this.address,
80
+ entry_point_selector: getSelectorFromName('__execute__'),
81
+ calldata,
82
+ version: toHex(version),
83
+ signature: bigNumberishArrayToDecimalStringArray(signature),
84
+ }
85
+ );
66
86
  }
67
87
 
68
88
  /**
@@ -80,16 +100,25 @@ export class Account extends Provider implements AccountInterface {
80
100
  ): Promise<AddTransactionResponse> {
81
101
  const transactions = Array.isArray(calls) ? calls : [calls];
82
102
  const nonce = toBN(transactionsDetail.nonce ?? (await this.getNonce()));
83
- const maxFee = transactionsDetail.maxFee ?? (await this.estimateFee(transactions)).amount;
84
- const signerDetails = {
103
+ let maxFee: BigNumberish = '0';
104
+ if (transactionsDetail.maxFee || transactionsDetail.maxFee === 0) {
105
+ maxFee = transactionsDetail.maxFee;
106
+ } else {
107
+ const estimatedFee = (await this.estimateFee(transactions, { nonce })).amount;
108
+ maxFee = estimatedFeeToMaxFee(estimatedFee).toString();
109
+ }
110
+
111
+ const signerDetails: InvocationsSignerDetails = {
85
112
  walletAddress: this.address,
86
113
  nonce,
87
114
  maxFee,
115
+ version: toBN(transactionVersion),
116
+ chainId: this.chainId,
88
117
  };
89
118
 
90
119
  const signature = await this.signer.signTransaction(transactions, signerDetails, abis);
91
120
 
92
- const calldata = [...fromCallsToExecuteCalldata(transactions), signerDetails.nonce.toString()];
121
+ const calldata = fromCallsToExecuteCalldataWithNonce(transactions, nonce);
93
122
  return this.fetchEndpoint('add_transaction', undefined, {
94
123
  type: 'INVOKE_FUNCTION',
95
124
  contract_address: this.address,
@@ -139,7 +168,7 @@ export class Account extends Provider implements AccountInterface {
139
168
  .map(computeHashOnElements);
140
169
 
141
170
  return computeHashOnElements([
142
- transactionPrefix,
171
+ encodeShortString('StarkNet Transaction'),
143
172
  account,
144
173
  computeHashOnElements(hashArray),
145
174
  nonce,
package/src/constants.ts CHANGED
@@ -8,6 +8,16 @@ export const TWO = toBN(2);
8
8
  export const MASK_250 = TWO.pow(toBN(250)).sub(ONE); // 2 ** 250 - 1
9
9
  export const MASK_251 = TWO.pow(toBN(251));
10
10
 
11
+ export enum StarknetChainId {
12
+ MAINNET = '0x534e5f4d41494e', // encodeShortString('SN_MAIN'),
13
+ TESTNET = '0x534e5f474f45524c49', // encodeShortString('SN_GOERLI'),
14
+ }
15
+ export enum TransactionHashPrefix {
16
+ DEPLOY = '0x6465706c6f79', // encodeShortString('deploy'),
17
+ INVOKE = '0x696e766f6b65', // encodeShortString('invoke'),
18
+ L1_HANDLER = '0x6c315f68616e646c6572', // encodeShortString('l1_handler'),
19
+ }
20
+
11
21
  /**
12
22
  * The following is taken from https://github.com/starkware-libs/starkex-resources/blob/master/crypto/starkware/crypto/signature/pedersen_params.json but converted to hex, because JS is very bad handling big integers by default
13
23
  * Please do not edit until the JSON changes.
@@ -3,6 +3,7 @@ import assert from 'minimalistic-assert';
3
3
 
4
4
  import { AccountInterface } from '../account';
5
5
  import { ProviderInterface, defaultProvider } from '../provider';
6
+ import { BlockIdentifier } from '../provider/utils';
6
7
  import {
7
8
  Abi,
8
9
  AbiEntry,
@@ -45,7 +46,18 @@ function buildCall(contract: Contract, functionAbi: FunctionAbi): AsyncContractF
45
46
  */
46
47
  function buildInvoke(contract: Contract, functionAbi: FunctionAbi): AsyncContractFunction {
47
48
  return async function (...args: Array<any>): Promise<any> {
48
- return contract.invoke(functionAbi.name, args);
49
+ const { inputs } = functionAbi;
50
+ const inputsLength = inputs.reduce((acc, input) => {
51
+ if (!/_len$/.test(input.name)) {
52
+ return acc + 1;
53
+ }
54
+ return acc;
55
+ }, 0);
56
+ const options = {};
57
+ if (inputsLength + 1 === args.length && typeof args[args.length - 1] === 'object') {
58
+ Object.assign(options, args.pop());
59
+ }
60
+ return contract.invoke(functionAbi.name, args, options);
49
61
  };
50
62
  }
51
63
 
@@ -528,7 +540,11 @@ export class Contract implements ContractInterface {
528
540
  }, [] as Result);
529
541
  }
530
542
 
531
- public invoke(method: string, args: Array<any> = []): Promise<AddTransactionResponse> {
543
+ public invoke(
544
+ method: string,
545
+ args: Array<any> = [],
546
+ options: Overrides = {}
547
+ ): Promise<AddTransactionResponse> {
532
548
  // ensure contract is connected
533
549
  assert(this.address !== null, 'contract isnt connected to an address');
534
550
  // validate method and args
@@ -542,11 +558,6 @@ export class Contract implements ContractInterface {
542
558
  return acc;
543
559
  }, 0);
544
560
 
545
- const overrides: Overrides = {};
546
- if (args.length === inputsLength + 1 && Array.isArray(args[args.length - 1])) {
547
- Object.assign(overrides, args.pop());
548
- }
549
-
550
561
  if (args.length !== inputsLength) {
551
562
  throw Error(
552
563
  `Invalid number of arguments, expected ${inputsLength} arguments, but got ${args.length}`
@@ -562,31 +573,33 @@ export class Contract implements ContractInterface {
562
573
  };
563
574
  if ('execute' in this.providerOrAccount) {
564
575
  return this.providerOrAccount.execute(invocation, undefined, {
565
- maxFee: overrides.maxFee,
566
- nonce: overrides.nonce,
576
+ maxFee: options.maxFee,
577
+ nonce: options.nonce,
567
578
  });
568
579
  }
569
580
 
570
581
  return this.providerOrAccount.invokeFunction({
571
582
  ...invocation,
572
- signature: overrides.signature || [],
583
+ signature: options.signature || [],
573
584
  });
574
585
  }
575
586
 
576
- public async call(method: string, args: Array<any> = []): Promise<Result> {
587
+ public async call(
588
+ method: string,
589
+ args: Array<any> = [],
590
+ {
591
+ blockIdentifier = 'pending',
592
+ }: {
593
+ blockIdentifier?: BlockIdentifier;
594
+ } = {}
595
+ ): Promise<Result> {
577
596
  // ensure contract is connected
578
597
  assert(this.address !== null, 'contract isnt connected to an address');
579
598
 
580
599
  // validate method and args
581
600
  this.validateMethodAndArgs('CALL', method, args);
582
601
  const { inputs } = this.abi.find((abi) => abi.name === method) as FunctionAbi;
583
- const inputsLength = inputs.length;
584
- const options = {
585
- blockIdentifier: null,
586
- };
587
- if (args.length === inputsLength + 1 && typeof args[args.length - 1] === 'object') {
588
- Object.assign(options, args.pop());
589
- }
602
+
590
603
  // compile calldata
591
604
  const calldata = this.compileCalldata(args, inputs);
592
605
  return this.providerOrAccount
@@ -596,7 +609,7 @@ export class Contract implements ContractInterface {
596
609
  calldata,
597
610
  entrypoint: method,
598
611
  },
599
- options
612
+ { blockIdentifier }
600
613
  )
601
614
  .then((x) => this.parseResponse(method, x.result));
602
615
  }
@@ -1,11 +1,13 @@
1
1
  import { AccountInterface } from '../account';
2
2
  import { ProviderInterface } from '../provider';
3
+ import { BlockIdentifier } from '../provider/utils';
3
4
  import {
4
5
  Abi,
5
6
  AddTransactionResponse,
6
7
  AsyncContractFunction,
7
8
  ContractFunction,
8
9
  Invocation,
10
+ Overrides,
9
11
  Result,
10
12
  } from '../types';
11
13
 
@@ -57,7 +59,13 @@ export abstract class ContractInterface {
57
59
  * @param args Array of the arguments for the call
58
60
  * @returns Result of the call as an array with key value pars
59
61
  */
60
- public abstract call(method: string, args?: Array<any>): Promise<Result>;
62
+ public abstract call(
63
+ method: string,
64
+ args?: Array<any>,
65
+ options?: {
66
+ blockIdentifier?: BlockIdentifier;
67
+ }
68
+ ): Promise<Result>;
61
69
 
62
70
  /**
63
71
  * Invokes a method on a contract
@@ -66,7 +74,11 @@ export abstract class ContractInterface {
66
74
  * @param args Array of the arguments for the invoke
67
75
  * @returns Add Transaction Response
68
76
  */
69
- public abstract invoke(method: string, args?: Array<any>): Promise<AddTransactionResponse>;
77
+ public abstract invoke(
78
+ method: string,
79
+ args?: Array<any>,
80
+ options?: Overrides
81
+ ): Promise<AddTransactionResponse>;
70
82
 
71
83
  /**
72
84
  * Calls a method on a contract
@@ -74,7 +86,13 @@ export abstract class ContractInterface {
74
86
  * @param method name of the method
75
87
  * @param args Array of the arguments for the call
76
88
  */
77
- public abstract estimate(method: string, args?: Array<any>): Promise<any>;
89
+ public abstract estimate(
90
+ method: string,
91
+ args?: Array<any>,
92
+ options?: {
93
+ blockIdentifier?: BlockIdentifier;
94
+ }
95
+ ): Promise<any>;
78
96
 
79
97
  /**
80
98
  * Calls a method on a contract
@@ -1,6 +1,7 @@
1
1
  import axios, { AxiosRequestHeaders } from 'axios';
2
2
  import urljoin from 'url-join';
3
3
 
4
+ import { StarknetChainId } from '../constants';
4
5
  import {
5
6
  Abi,
6
7
  AddTransactionResponse,
@@ -49,17 +50,22 @@ export class Provider implements ProviderInterface {
49
50
 
50
51
  public gatewayUrl: string;
51
52
 
53
+ public chainId: StarknetChainId;
54
+
52
55
  constructor(optionsOrProvider: ProviderOptions | Provider = { network: 'goerli-alpha' }) {
53
56
  if (optionsOrProvider instanceof Provider) {
54
57
  this.baseUrl = optionsOrProvider.baseUrl;
55
58
  this.feederGatewayUrl = optionsOrProvider.feederGatewayUrl;
56
59
  this.gatewayUrl = optionsOrProvider.gatewayUrl;
60
+ this.chainId =
61
+ optionsOrProvider.chainId ?? Provider.getChainIdFromBaseUrl(optionsOrProvider.baseUrl);
57
62
  } else {
58
63
  const baseUrl =
59
64
  'baseUrl' in optionsOrProvider
60
65
  ? optionsOrProvider.baseUrl
61
66
  : Provider.getNetworkFromName(optionsOrProvider.network);
62
67
  this.baseUrl = baseUrl;
68
+ this.chainId = Provider.getChainIdFromBaseUrl(baseUrl);
63
69
  this.feederGatewayUrl = urljoin(baseUrl, 'feeder_gateway');
64
70
  this.gatewayUrl = urljoin(baseUrl, 'gateway');
65
71
  }
@@ -75,6 +81,19 @@ export class Provider implements ProviderInterface {
75
81
  }
76
82
  }
77
83
 
84
+ protected static getChainIdFromBaseUrl(baseUrl: string): StarknetChainId {
85
+ try {
86
+ const url = new URL(baseUrl);
87
+ if (url.host.includes('mainnet.starknet.io')) {
88
+ return StarknetChainId.MAINNET;
89
+ }
90
+ } catch {
91
+ // eslint-disable-next-line no-console
92
+ console.error(`Could not parse baseUrl: ${baseUrl}`);
93
+ }
94
+ return StarknetChainId.TESTNET;
95
+ }
96
+
78
97
  private getFetchUrl(endpoint: keyof Endpoints) {
79
98
  const gatewayUrlEndpoints = ['add_transaction'];
80
99
 
@@ -168,14 +187,18 @@ export class Provider implements ProviderInterface {
168
187
  */
169
188
  public async callContract(
170
189
  { contractAddress, entrypoint, calldata = [] }: Call,
171
- options: { blockIdentifier: BlockIdentifier } = { blockIdentifier: null }
190
+ { blockIdentifier = 'pending' }: { blockIdentifier?: BlockIdentifier } = {}
172
191
  ): Promise<CallContractResponse> {
173
- return this.fetchEndpoint('call_contract', options, {
174
- signature: [],
175
- contract_address: contractAddress,
176
- entry_point_selector: getSelectorFromName(entrypoint),
177
- calldata,
178
- });
192
+ return this.fetchEndpoint(
193
+ 'call_contract',
194
+ { blockIdentifier },
195
+ {
196
+ signature: [],
197
+ contract_address: contractAddress,
198
+ entry_point_selector: getSelectorFromName(entrypoint),
199
+ calldata,
200
+ }
201
+ );
179
202
  }
180
203
 
181
204
  /**
@@ -203,7 +226,7 @@ export class Provider implements ProviderInterface {
203
226
  */
204
227
  public async getCode(
205
228
  contractAddress: string,
206
- blockIdentifier: BlockIdentifier = null
229
+ blockIdentifier: BlockIdentifier = 'pending'
207
230
  ): Promise<GetCodeResponse> {
208
231
  return this.fetchEndpoint('get_code', { blockIdentifier, contractAddress });
209
232
  }
@@ -223,7 +246,7 @@ export class Provider implements ProviderInterface {
223
246
  public async getStorageAt(
224
247
  contractAddress: string,
225
248
  key: number,
226
- blockIdentifier: BlockIdentifier = null
249
+ blockIdentifier: BlockIdentifier = 'pending'
227
250
  ): Promise<object> {
228
251
  return this.fetchEndpoint('get_storage_at', { blockIdentifier, contractAddress, key });
229
252
  }
@@ -340,7 +363,6 @@ export class Provider implements ProviderInterface {
340
363
 
341
364
  public async waitForTransaction(txHash: BigNumberish, retryInterval: number = 8000) {
342
365
  let onchain = false;
343
- await wait(retryInterval);
344
366
 
345
367
  while (!onchain) {
346
368
  // eslint-disable-next-line no-await-in-loop
@@ -348,9 +370,12 @@ export class Provider implements ProviderInterface {
348
370
  // eslint-disable-next-line no-await-in-loop
349
371
  const res = await this.getTransactionStatus(txHash);
350
372
 
351
- if (res.tx_status === 'ACCEPTED_ON_L1' || res.tx_status === 'ACCEPTED_ON_L2') {
373
+ const successStates = ['ACCEPTED_ON_L1', 'ACCEPTED_ON_L2', 'PENDING'];
374
+ const errorStates = ['REJECTED', 'NOT_RECEIVED'];
375
+
376
+ if (successStates.includes(res.tx_status)) {
352
377
  onchain = true;
353
- } else if (res.tx_status === 'REJECTED' || res.tx_status === 'NOT_RECEIVED') {
378
+ } else if (errorStates.includes(res.tx_status)) {
354
379
  const message = res.tx_failure_reason
355
380
  ? `${res.tx_status}: ${res.tx_failure_reason.code}\n${res.tx_failure_reason.error_message}`
356
381
  : res.tx_status;
@@ -1,3 +1,4 @@
1
+ import { StarknetChainId } from '../constants';
1
2
  import type {
2
3
  AddTransactionResponse,
3
4
  Call,
@@ -21,6 +22,8 @@ export abstract class ProviderInterface {
21
22
 
22
23
  public abstract gatewayUrl: string;
23
24
 
25
+ public abstract chainId: StarknetChainId;
26
+
24
27
  /**
25
28
  * Gets the smart contract address on the goerli testnet.
26
29
  *
@@ -43,6 +43,12 @@ type BlockIdentifierObject =
43
43
  * @returns block identifier object
44
44
  */
45
45
  export function getBlockIdentifier(blockIdentifier: BlockIdentifier): BlockIdentifierObject {
46
+ if (blockIdentifier === null) {
47
+ return { type: 'BLOCK_NUMBER', data: null };
48
+ }
49
+ if (blockIdentifier === 'pending') {
50
+ return { type: 'BLOCK_NUMBER', data: 'pending' };
51
+ }
46
52
  if (typeof blockIdentifier === 'number') {
47
53
  return { type: 'BLOCK_NUMBER', data: blockIdentifier };
48
54
  }
@@ -52,12 +58,6 @@ export function getBlockIdentifier(blockIdentifier: BlockIdentifier): BlockIdent
52
58
  if (typeof blockIdentifier === 'string' && !Number.isNaN(parseInt(blockIdentifier, 10))) {
53
59
  return { type: 'BLOCK_NUMBER', data: parseInt(blockIdentifier, 10) };
54
60
  }
55
- if (blockIdentifier === null) {
56
- return { type: 'BLOCK_NUMBER', data: null };
57
- }
58
- if (blockIdentifier === 'pending') {
59
- return { type: 'BLOCK_NUMBER', data: 'pending' };
60
- }
61
61
  if (typeof blockIdentifier === 'string') {
62
62
  throw new Error(`Invalid block identifier: ${blockIdentifier}`);
63
63
  }
@@ -69,8 +69,7 @@ export function getBlockIdentifier(blockIdentifier: BlockIdentifier): BlockIdent
69
69
  *
70
70
  * [Reference](https://github.com/starkware-libs/cairo-lang/blob/fc97bdd8322a7df043c87c371634b26c15ed6cee/src/starkware/starknet/services/api/feeder_gateway/feeder_gateway_client.py#L164-L173)
71
71
  *
72
- * @param blockNumber
73
- * @param blockHash
72
+ * @param blockIdentifier
74
73
  * @returns block identifier for API request
75
74
  */
76
75
  export function getFormattedBlockIdentifier(blockIdentifier: BlockIdentifier = null): string {
@@ -1,6 +1,7 @@
1
1
  import { Abi, Invocation, InvocationsSignerDetails, KeyPair, Signature } from '../types';
2
2
  import { getStarkKey, sign } from '../utils/ellipticCurve';
3
- import { hashMulticall } from '../utils/hash';
3
+ import { calculcateTransactionHash, getSelectorFromName } from '../utils/hash';
4
+ import { fromCallsToExecuteCalldataWithNonce } from '../utils/transaction';
4
5
  import { TypedData, getMessageHash } from '../utils/typedData';
5
6
  import { SignerInterface } from './interface';
6
7
 
@@ -25,11 +26,15 @@ export class Signer implements SignerInterface {
25
26
  }
26
27
  // now use abi to display decoded data somewhere, but as this signer is headless, we can't do that
27
28
 
28
- const msgHash = hashMulticall(
29
+ const calldata = fromCallsToExecuteCalldataWithNonce(transactions, transactionsDetail.nonce);
30
+
31
+ const msgHash = calculcateTransactionHash(
29
32
  transactionsDetail.walletAddress,
30
- transactions,
31
- transactionsDetail.nonce.toString(),
32
- transactionsDetail.maxFee.toString()
33
+ transactionsDetail.version,
34
+ getSelectorFromName('__execute__'),
35
+ calldata,
36
+ transactionsDetail.maxFee,
37
+ transactionsDetail.chainId
33
38
  );
34
39
 
35
40
  return sign(this.keyPair, msgHash);
@@ -4,7 +4,8 @@ import TransportWebHID from '@ledgerhq/hw-transport-webhid';
4
4
 
5
5
  import { Invocation, InvocationsSignerDetails, Signature } from '../types';
6
6
  import { addHexPrefix } from '../utils/encode';
7
- import { hashMulticall } from '../utils/hash';
7
+ import { calculcateTransactionHash, getSelectorFromName } from '../utils/hash';
8
+ import { fromCallsToExecuteCalldataWithNonce } from '../utils/transaction';
8
9
  import { TypedData, getMessageHash } from '../utils/typedData';
9
10
  import { SignerInterface } from './interface';
10
11
 
@@ -46,11 +47,15 @@ export class LedgerBlindSigner implements SignerInterface {
46
47
  transactions: Invocation[],
47
48
  transactionsDetail: InvocationsSignerDetails
48
49
  ): Promise<Signature> {
49
- const msgHash = hashMulticall(
50
+ const calldata = fromCallsToExecuteCalldataWithNonce(transactions, transactionsDetail.nonce);
51
+
52
+ const msgHash = calculcateTransactionHash(
50
53
  transactionsDetail.walletAddress,
51
- transactions,
52
- transactionsDetail.nonce.toString(),
53
- transactionsDetail.maxFee.toString()
54
+ transactionsDetail.version,
55
+ getSelectorFromName('__execute__'),
56
+ calldata,
57
+ transactionsDetail.maxFee,
58
+ transactionsDetail.chainId
54
59
  );
55
60
 
56
61
  return this.sign(msgHash);
package/src/types/api.ts CHANGED
@@ -75,7 +75,9 @@ export type Endpoints = {
75
75
  RESPONSE: CallContractResponse;
76
76
  };
77
77
  estimate_fee: {
78
- QUERY: never;
78
+ QUERY: {
79
+ blockIdentifier: BlockIdentifier;
80
+ };
79
81
  REQUEST: CallContractTransaction;
80
82
  RESPONSE: EstimateFeeResponse;
81
83
  };
@@ -103,6 +105,7 @@ export type InvokeFunctionTransaction = {
103
105
  calldata?: RawCalldata;
104
106
  nonce?: BigNumberish;
105
107
  max_fee?: BigNumberish;
108
+ version?: BigNumberish;
106
109
  };
107
110
 
108
111
  export type InvokeFunctionTrace = {
package/src/types/lib.ts CHANGED
@@ -24,6 +24,7 @@ export type Call = Omit<Invocation, 'signature'>;
24
24
  export type InvocationsDetails = {
25
25
  nonce?: BigNumberish;
26
26
  maxFee?: BigNumberish;
27
+ version?: BigNumberish;
27
28
  };
28
29
 
29
30
  export type Status =
@@ -1,5 +1,7 @@
1
+ import { StarknetChainId } from '../constants';
1
2
  import { InvocationsDetails } from './lib';
2
3
 
3
4
  export interface InvocationsSignerDetails extends Required<InvocationsDetails> {
4
5
  walletAddress: string;
6
+ chainId: StarknetChainId;
5
7
  }
package/src/utils/hash.ts CHANGED
@@ -2,15 +2,21 @@ import BN from 'bn.js';
2
2
  import { keccak256 } from 'ethereum-cryptography/keccak';
3
3
  import assert from 'minimalistic-assert';
4
4
 
5
- import { CONSTANT_POINTS, FIELD_PRIME, MASK_250, ONE, ZERO } from '../constants';
6
- import { Call } from '../types';
5
+ import {
6
+ CONSTANT_POINTS,
7
+ FIELD_PRIME,
8
+ MASK_250,
9
+ ONE,
10
+ StarknetChainId,
11
+ TransactionHashPrefix,
12
+ ZERO,
13
+ } from '../constants';
7
14
  import { ec } from './ellipticCurve';
8
15
  import { addHexPrefix, buf2hex, utf8ToArray } from './encode';
9
- import { BigNumberish, bigNumberishArrayToDecimalStringArray, toBN, toHex } from './number';
10
- import { encodeShortString } from './shortString';
16
+ import { BigNumberish, toBN, toHex } from './number';
11
17
 
12
- export const transactionPrefix = encodeShortString('StarkNet Transaction');
13
18
  export const transactionVersion = 0;
19
+ export const feeTransactionVersion = toBN(2).pow(toBN(128)).add(toBN(transactionVersion));
14
20
 
15
21
  function keccakHex(value: string): string {
16
22
  return addHexPrefix(buf2hex(keccak256(utf8ToArray(value))));
@@ -64,27 +70,65 @@ export function computeHashOnElements(data: BigNumberish[]) {
64
70
  return [...data, data.length].reduce((x, y) => pedersen([x, y]), 0).toString();
65
71
  }
66
72
 
67
- export function hashMulticall(
68
- account: string,
69
- transactions: Call[],
70
- nonce: string,
71
- maxFee: string
72
- ) {
73
- const hashArray = transactions
74
- .map(({ contractAddress, entrypoint, calldata }) => [
75
- contractAddress,
76
- getSelectorFromName(entrypoint),
77
- computeHashOnElements(calldata || []),
78
- ])
79
- .map(bigNumberishArrayToDecimalStringArray)
80
- .map(computeHashOnElements);
73
+ // following implementation is based on this python implementation:
74
+ // https://github.com/starkware-libs/cairo-lang/blob/b614d1867c64f3fb2cf4a4879348cfcf87c3a5a7/src/starkware/starknet/core/os/transaction_hash/transaction_hash.py
81
75
 
82
- return computeHashOnElements([
83
- transactionPrefix,
84
- account,
85
- computeHashOnElements(hashArray),
86
- nonce,
76
+ export function calculateTransactionHashCommon(
77
+ txHashPrefix: TransactionHashPrefix,
78
+ version: BigNumberish,
79
+ contractAddress: BigNumberish,
80
+ entryPointSelector: BigNumberish,
81
+ calldata: BigNumberish[],
82
+ maxFee: BigNumberish,
83
+ chainId: StarknetChainId,
84
+ additionalData: BigNumberish[] = []
85
+ ): string {
86
+ const calldataHash = computeHashOnElements(calldata);
87
+ const dataToHash = [
88
+ txHashPrefix,
89
+ version,
90
+ contractAddress,
91
+ entryPointSelector,
92
+ calldataHash,
87
93
  maxFee,
88
- transactionVersion,
89
- ]);
94
+ chainId,
95
+ ...additionalData,
96
+ ];
97
+ return computeHashOnElements(dataToHash);
98
+ }
99
+
100
+ export function calculateDeployTransactionHash(
101
+ contractAddress: BigNumberish,
102
+ constructorCalldata: BigNumberish[],
103
+ version: BigNumberish,
104
+ chainId: StarknetChainId
105
+ ): string {
106
+ return calculateTransactionHashCommon(
107
+ TransactionHashPrefix.DEPLOY,
108
+ version,
109
+ contractAddress,
110
+ getSelectorFromName('constructor'),
111
+ constructorCalldata,
112
+ ZERO,
113
+ chainId
114
+ );
115
+ }
116
+
117
+ export function calculcateTransactionHash(
118
+ contractAddress: BigNumberish,
119
+ version: BigNumberish,
120
+ entryPointSelector: BigNumberish,
121
+ calldata: BigNumberish[],
122
+ maxFee: BigNumberish,
123
+ chainId: StarknetChainId
124
+ ): string {
125
+ return calculateTransactionHashCommon(
126
+ TransactionHashPrefix.INVOKE,
127
+ version,
128
+ contractAddress,
129
+ entryPointSelector,
130
+ calldata,
131
+ maxFee,
132
+ chainId
133
+ );
90
134
  }