starknet 2.3.0 → 2.6.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 (61) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/__tests__/account.test.ts +3 -3
  3. package/__tests__/provider.test.ts +3 -3
  4. package/__tests__/signer.test.ts +17 -0
  5. package/__tests__/utils/ellipticalCurve.test.ts +1 -1
  6. package/__tests__/utils/typedData.test.ts +72 -0
  7. package/contract.d.ts +9 -3
  8. package/contract.js +23 -5
  9. package/dist/contract.d.ts +6 -3
  10. package/dist/contract.js +19 -5
  11. package/dist/index.d.ts +1 -0
  12. package/dist/index.js +2 -1
  13. package/dist/provider/default.d.ts +17 -17
  14. package/dist/provider/default.js +37 -32
  15. package/dist/provider/interface.d.ts +16 -16
  16. package/dist/signer/default.d.ts +20 -3
  17. package/dist/signer/default.js +61 -20
  18. package/dist/signer/interface.d.ts +22 -3
  19. package/dist/types.d.ts +10 -7
  20. package/dist/utils/ellipticCurve.d.ts +8 -1
  21. package/dist/utils/ellipticCurve.js +48 -9
  22. package/dist/utils/stark.d.ts +2 -6
  23. package/dist/utils/stark.js +1 -5
  24. package/dist/utils/typedData/index.d.ts +91 -0
  25. package/dist/utils/typedData/index.js +183 -0
  26. package/dist/utils/typedData/types.d.ts +82 -0
  27. package/dist/utils/typedData/types.js +47 -0
  28. package/dist/utils/typedData/utils.d.ts +24 -0
  29. package/dist/utils/typedData/utils.js +15 -0
  30. package/index.d.ts +1 -0
  31. package/index.js +3 -1
  32. package/package.json +3 -1
  33. package/provider/default.d.ts +21 -16
  34. package/provider/default.js +50 -39
  35. package/provider/interface.d.ts +22 -16
  36. package/signer/default.d.ts +20 -3
  37. package/signer/default.js +60 -17
  38. package/signer/interface.d.ts +22 -3
  39. package/src/contract.ts +17 -14
  40. package/src/index.ts +1 -0
  41. package/src/provider/default.ts +37 -31
  42. package/src/provider/interface.ts +21 -16
  43. package/src/signer/default.ts +49 -17
  44. package/src/signer/interface.ts +26 -3
  45. package/src/types.ts +16 -7
  46. package/src/utils/ellipticCurve.ts +31 -9
  47. package/src/utils/stark.ts +4 -8
  48. package/src/utils/typedData/index.ts +176 -0
  49. package/src/utils/typedData/types.ts +82 -0
  50. package/src/utils/typedData/utils.ts +13 -0
  51. package/types.d.ts +12 -8
  52. package/utils/ellipticCurve.d.ts +12 -1
  53. package/utils/ellipticCurve.js +72 -23
  54. package/utils/stark.d.ts +2 -8
  55. package/utils/stark.js +1 -6
  56. package/utils/typedData/index.d.ts +113 -0
  57. package/utils/typedData/index.js +247 -0
  58. package/utils/typedData/types.d.ts +103 -0
  59. package/utils/typedData/types.js +57 -0
  60. package/utils/typedData/utils.d.ts +27 -0
  61. package/utils/typedData/utils.js +15 -0
@@ -3,6 +3,7 @@ import urljoin from 'url-join';
3
3
 
4
4
  import {
5
5
  AddTransactionResponse,
6
+ BlockNumber,
6
7
  CallContractResponse,
7
8
  CallContractTransaction,
8
9
  CompiledContract,
@@ -11,6 +12,7 @@ import {
11
12
  GetContractAddressesResponse,
12
13
  GetTransactionResponse,
13
14
  GetTransactionStatusResponse,
15
+ Signature,
14
16
  Transaction,
15
17
  } from '../types';
16
18
  import { parse, stringify } from '../utils/json';
@@ -18,7 +20,7 @@ import { BigNumberish, toBN, toHex } from '../utils/number';
18
20
  import { compressProgram, formatSignature, randomAddress } from '../utils/stark';
19
21
  import { ProviderInterface } from './interface';
20
22
 
21
- type NetworkName = 'mainnet-alpha' | 'georli-alpha';
23
+ type NetworkName = 'mainnet-alpha' | 'goerli-alpha';
22
24
 
23
25
  type ProviderOptions =
24
26
  | {
@@ -39,7 +41,7 @@ export class Provider implements ProviderInterface {
39
41
 
40
42
  public gatewayUrl: string;
41
43
 
42
- constructor(optionsOrProvider: ProviderOptions | Provider = { network: 'georli-alpha' }) {
44
+ constructor(optionsOrProvider: ProviderOptions | Provider = { network: 'goerli-alpha' }) {
43
45
  if (optionsOrProvider instanceof Provider) {
44
46
  this.baseUrl = optionsOrProvider.baseUrl;
45
47
  this.feederGatewayUrl = optionsOrProvider.feederGatewayUrl;
@@ -59,7 +61,7 @@ export class Provider implements ProviderInterface {
59
61
  switch (name) {
60
62
  case 'mainnet-alpha':
61
63
  return 'https://alpha-mainnet.starknet.io';
62
- case 'georli-alpha':
64
+ case 'goerli-alpha':
63
65
  default:
64
66
  return 'https://alpha4.starknet.io';
65
67
  }
@@ -83,20 +85,20 @@ export class Provider implements ProviderInterface {
83
85
  *
84
86
  * [Reference](https://github.com/starkware-libs/cairo-lang/blob/f464ec4797361b6be8989e36e02ec690e74ef285/src/starkware/starknet/services/api/feeder_gateway/feeder_gateway_client.py#L17-L25)
85
87
  *
86
- * @param invokeTx - transaction to be invoked
87
- * @param blockId
88
+ * @param invokeTransaction - transaction to be invoked
89
+ * @param blockNumber
88
90
  * @returns the result of the function on the smart contract.
89
91
  */
90
92
  public async callContract(
91
- invokeTx: CallContractTransaction,
92
- blockId?: number
93
+ invokeTransaction: CallContractTransaction,
94
+ blockNumber: BlockNumber = null
93
95
  ): Promise<CallContractResponse> {
94
96
  const { data } = await axios.post<CallContractResponse>(
95
- urljoin(this.feederGatewayUrl, 'call_contract', `?blockId=${blockId ?? 'null'}`),
97
+ urljoin(this.feederGatewayUrl, 'call_contract', `?blockNumber=${blockNumber}`),
96
98
  {
97
99
  signature: [],
98
100
  calldata: [],
99
- ...invokeTx,
101
+ ...invokeTransaction,
100
102
  }
101
103
  );
102
104
  return data;
@@ -107,12 +109,12 @@ export class Provider implements ProviderInterface {
107
109
  *
108
110
  * [Reference](https://github.com/starkware-libs/cairo-lang/blob/f464ec4797361b6be8989e36e02ec690e74ef285/src/starkware/starknet/services/api/feeder_gateway/feeder_gateway_client.py#L27-L31)
109
111
  *
110
- * @param blockId
111
- * @returns the block object { block_id, previous_block_id, state_root, status, timestamp, transaction_receipts, transactions }
112
+ * @param blockNumber
113
+ * @returns the block object { block_number, previous_block_number, state_root, status, timestamp, transaction_receipts, transactions }
112
114
  */
113
- public async getBlock(blockId?: number): Promise<GetBlockResponse> {
115
+ public async getBlock(blockNumber: BlockNumber = null): Promise<GetBlockResponse> {
114
116
  const { data } = await axios.get<GetBlockResponse>(
115
- urljoin(this.feederGatewayUrl, 'get_block', `?blockId=${blockId ?? 'null'}`)
117
+ urljoin(this.feederGatewayUrl, 'get_block', `?blockNumber=${blockNumber}`)
116
118
  );
117
119
  return data;
118
120
  }
@@ -123,15 +125,18 @@ export class Provider implements ProviderInterface {
123
125
  * [Reference](https://github.com/starkware-libs/cairo-lang/blob/f464ec4797361b6be8989e36e02ec690e74ef285/src/starkware/starknet/services/api/feeder_gateway/feeder_gateway_client.py#L33-L36)
124
126
  *
125
127
  * @param contractAddress
126
- * @param blockId
128
+ * @param blockNumber
127
129
  * @returns Bytecode and ABI of compiled contract
128
130
  */
129
- public async getCode(contractAddress: string, blockId?: number): Promise<GetCodeResponse> {
131
+ public async getCode(
132
+ contractAddress: string,
133
+ blockNumber: BlockNumber = null
134
+ ): Promise<GetCodeResponse> {
130
135
  const { data } = await axios.get<GetCodeResponse>(
131
136
  urljoin(
132
137
  this.feederGatewayUrl,
133
138
  'get_code',
134
- `?contractAddress=${contractAddress}&blockId=${blockId ?? 'null'}`
139
+ `?contractAddress=${contractAddress}&blockNumber=${blockNumber}`
135
140
  )
136
141
  );
137
142
  return data;
@@ -145,19 +150,19 @@ export class Provider implements ProviderInterface {
145
150
  *
146
151
  * @param contractAddress
147
152
  * @param key - from getStorageVarAddress('<STORAGE_VARIABLE_NAME>') (WIP)
148
- * @param blockId
153
+ * @param blockNumber
149
154
  * @returns the value of the storage variable
150
155
  */
151
156
  public async getStorageAt(
152
157
  contractAddress: string,
153
158
  key: number,
154
- blockId?: number
159
+ blockNumber: BlockNumber = null
155
160
  ): Promise<object> {
156
161
  const { data } = await axios.get<object>(
157
162
  urljoin(
158
163
  this.feederGatewayUrl,
159
164
  'get_storage_at',
160
- `?contractAddress=${contractAddress}&key=${key}&blockId=${blockId ?? 'null'}`
165
+ `?contractAddress=${contractAddress}&key=${key}&blockNumber=${blockNumber}`
161
166
  )
162
167
  );
163
168
  return data;
@@ -169,7 +174,7 @@ export class Provider implements ProviderInterface {
169
174
  * [Reference](https://github.com/starkware-libs/cairo-lang/blob/f464ec4797361b6be8989e36e02ec690e74ef285/src/starkware/starknet/services/api/feeder_gateway/feeder_gateway_client.py#L48-L52)
170
175
  *
171
176
  * @param txHash
172
- * @returns the transaction status object { block_id, tx_status: NOT_RECEIVED | RECEIVED | PENDING | REJECTED | ACCEPTED_ONCHAIN }
177
+ * @returns the transaction status object { block_number, tx_status: NOT_RECEIVED | RECEIVED | PENDING | REJECTED | ACCEPTED_ONCHAIN }
173
178
  */
174
179
  public async getTransactionStatus(txHash: BigNumberish): Promise<GetTransactionStatusResponse> {
175
180
  const txHashBn = toBN(txHash);
@@ -189,7 +194,7 @@ export class Provider implements ProviderInterface {
189
194
  * [Reference](https://github.com/starkware-libs/cairo-lang/blob/f464ec4797361b6be8989e36e02ec690e74ef285/src/starkware/starknet/services/api/feeder_gateway/feeder_gateway_client.py#L54-L58)
190
195
  *
191
196
  * @param txHash
192
- * @returns the transacton object { transaction_id, status, transaction, block_id?, block_number?, transaction_index?, transaction_failure_reason? }
197
+ * @returns the transacton object { transaction_id, status, transaction, block_number?, block_number?, transaction_index?, transaction_failure_reason? }
193
198
  */
194
199
  public async getTransaction(txHash: BigNumberish): Promise<GetTransactionResponse> {
195
200
  const txHashBn = toBN(txHash);
@@ -204,17 +209,19 @@ export class Provider implements ProviderInterface {
204
209
  *
205
210
  * [Reference](https://github.com/starkware-libs/cairo-lang/blob/f464ec4797361b6be8989e36e02ec690e74ef285/src/starkware/starknet/services/api/gateway/gateway_client.py#L13-L17)
206
211
  *
207
- * @param tx - transaction to be invoked
212
+ * @param transaction - transaction to be invoked
208
213
  * @returns a confirmation of invoking a function on the starknet contract
209
214
  */
210
- public async addTransaction(tx: Transaction): Promise<AddTransactionResponse> {
211
- const signature = tx.type === 'INVOKE_FUNCTION' && formatSignature(tx.signature);
212
- const contract_address_salt = tx.type === 'DEPLOY' && toHex(toBN(tx.contract_address_salt));
215
+ public async addTransaction(transaction: Transaction): Promise<AddTransactionResponse> {
216
+ const signature =
217
+ transaction.type === 'INVOKE_FUNCTION' && formatSignature(transaction.signature);
218
+ const contract_address_salt =
219
+ transaction.type === 'DEPLOY' && toHex(toBN(transaction.contract_address_salt));
213
220
 
214
221
  const { data } = await axios.post<AddTransactionResponse>(
215
222
  urljoin(this.gatewayUrl, 'add_transaction'),
216
223
  stringify({
217
- ...tx, // the tx can contain BigInts, so we use our own `stringify`
224
+ ...transaction, // the tx can contain BigInts, so we use our own `stringify`
218
225
  ...(Array.isArray(signature) && { signature }), // not needed on deploy tx
219
226
  ...(contract_address_salt && { contract_address_salt }), // not needed on invoke tx
220
227
  }),
@@ -263,7 +270,7 @@ export class Provider implements ProviderInterface {
263
270
  contractAddress: string,
264
271
  entrypointSelector: string,
265
272
  calldata?: string[],
266
- signature?: [BigNumberish, BigNumberish]
273
+ signature?: Signature
267
274
  ): Promise<AddTransactionResponse> {
268
275
  return this.addTransaction({
269
276
  type: 'INVOKE_FUNCTION',
@@ -276,16 +283,15 @@ export class Provider implements ProviderInterface {
276
283
 
277
284
  public async waitForTx(txHash: BigNumberish, retryInterval: number = 8000) {
278
285
  let onchain = false;
286
+ await wait(retryInterval);
287
+
279
288
  while (!onchain) {
280
289
  // eslint-disable-next-line no-await-in-loop
281
290
  await wait(retryInterval);
282
291
  // eslint-disable-next-line no-await-in-loop
283
292
  const res = await this.getTransactionStatus(txHash);
284
293
 
285
- if (
286
- res.tx_status === 'ACCEPTED_ONCHAIN' ||
287
- (res.tx_status === 'PENDING' && res.block_hash !== 'pending') // This is needed as of today. In the future there will be a different status for pending transactions.
288
- ) {
294
+ if (res.tx_status === 'ACCEPTED_ON_L1' || res.tx_status === 'ACCEPTED_ON_L2') {
289
295
  onchain = true;
290
296
  } else if (res.tx_status === 'REJECTED') {
291
297
  throw Error('REJECTED');
@@ -1,5 +1,6 @@
1
1
  import type {
2
2
  AddTransactionResponse,
3
+ BlockNumber,
3
4
  CallContractResponse,
4
5
  CallContractTransaction,
5
6
  CompiledContract,
@@ -8,6 +9,7 @@ import type {
8
9
  GetContractAddressesResponse,
9
10
  GetTransactionResponse,
10
11
  GetTransactionStatusResponse,
12
+ Signature,
11
13
  Transaction,
12
14
  } from '../types';
13
15
  import type { BigNumberish } from '../utils/number';
@@ -32,13 +34,13 @@ export abstract class ProviderInterface {
32
34
  *
33
35
  * [Reference](https://github.com/starkware-libs/cairo-lang/blob/f464ec4797361b6be8989e36e02ec690e74ef285/src/starkware/starknet/services/api/feeder_gateway/feeder_gateway_client.py#L17-L25)
34
36
  *
35
- * @param invokeTx - transaction to be invoked
36
- * @param blockId
37
+ * @param invokeTransaction - transaction to be invoked
38
+ * @param blockNumber
37
39
  * @returns the result of the function on the smart contract.
38
40
  */
39
41
  public abstract callContract(
40
- invokeTx: CallContractTransaction,
41
- blockId?: number
42
+ invokeTransaction: CallContractTransaction,
43
+ blockNumber?: BlockNumber
42
44
  ): Promise<CallContractResponse>;
43
45
 
44
46
  /**
@@ -46,10 +48,10 @@ export abstract class ProviderInterface {
46
48
  *
47
49
  * [Reference](https://github.com/starkware-libs/cairo-lang/blob/f464ec4797361b6be8989e36e02ec690e74ef285/src/starkware/starknet/services/api/feeder_gateway/feeder_gateway_client.py#L27-L31)
48
50
  *
49
- * @param blockId
50
- * @returns the block object { block_id, previous_block_id, state_root, status, timestamp, transaction_receipts, transactions }
51
+ * @param blockNumber
52
+ * @returns the block object { block_number, previous_block_number, state_root, status, timestamp, transaction_receipts, transactions }
51
53
  */
52
- public abstract getBlock(blockId?: number): Promise<GetBlockResponse>;
54
+ public abstract getBlock(blockNumber?: BlockNumber): Promise<GetBlockResponse>;
53
55
 
54
56
  /**
55
57
  * Gets the code of the deployed contract.
@@ -57,10 +59,13 @@ export abstract class ProviderInterface {
57
59
  * [Reference](https://github.com/starkware-libs/cairo-lang/blob/f464ec4797361b6be8989e36e02ec690e74ef285/src/starkware/starknet/services/api/feeder_gateway/feeder_gateway_client.py#L33-L36)
58
60
  *
59
61
  * @param contractAddress
60
- * @param blockId
62
+ * @param blockNumber
61
63
  * @returns Bytecode and ABI of compiled contract
62
64
  */
63
- public abstract getCode(contractAddress: string, blockId?: number): Promise<GetCodeResponse>;
65
+ public abstract getCode(
66
+ contractAddress: string,
67
+ blockNumber?: BlockNumber
68
+ ): Promise<GetCodeResponse>;
64
69
 
65
70
  // TODO: add proper type
66
71
  /**
@@ -70,13 +75,13 @@ export abstract class ProviderInterface {
70
75
  *
71
76
  * @param contractAddress
72
77
  * @param key - from getStorageVarAddress('<STORAGE_VARIABLE_NAME>') (WIP)
73
- * @param blockId
78
+ * @param blockNumber
74
79
  * @returns the value of the storage variable
75
80
  */
76
81
  public abstract getStorageAt(
77
82
  contractAddress: string,
78
83
  key: number,
79
- blockId?: number
84
+ blockNumber?: BlockNumber
80
85
  ): Promise<object>;
81
86
 
82
87
  /**
@@ -85,7 +90,7 @@ export abstract class ProviderInterface {
85
90
  * [Reference](https://github.com/starkware-libs/cairo-lang/blob/f464ec4797361b6be8989e36e02ec690e74ef285/src/starkware/starknet/services/api/feeder_gateway/feeder_gateway_client.py#L48-L52)
86
91
  *
87
92
  * @param txHash
88
- * @returns the transaction status object { block_id, tx_status: NOT_RECEIVED | RECEIVED | PENDING | REJECTED | ACCEPTED_ONCHAIN }
93
+ * @returns the transaction status object { block_number, tx_status: NOT_RECEIVED | RECEIVED | PENDING | REJECTED | ACCEPTED_ONCHAIN }
89
94
  */
90
95
  public abstract getTransactionStatus(txHash: BigNumberish): Promise<GetTransactionStatusResponse>;
91
96
 
@@ -95,7 +100,7 @@ export abstract class ProviderInterface {
95
100
  * [Reference](https://github.com/starkware-libs/cairo-lang/blob/f464ec4797361b6be8989e36e02ec690e74ef285/src/starkware/starknet/services/api/feeder_gateway/feeder_gateway_client.py#L54-L58)
96
101
  *
97
102
  * @param txHash
98
- * @returns the transacton object { transaction_id, status, transaction, block_id?, block_number?, transaction_index?, transaction_failure_reason? }
103
+ * @returns the transacton object { transaction_id, status, transaction, block_number?, block_number?, transaction_index?, transaction_failure_reason? }
99
104
  */
100
105
  public abstract getTransaction(txHash: BigNumberish): Promise<GetTransactionResponse>;
101
106
 
@@ -104,10 +109,10 @@ export abstract class ProviderInterface {
104
109
  *
105
110
  * [Reference](https://github.com/starkware-libs/cairo-lang/blob/f464ec4797361b6be8989e36e02ec690e74ef285/src/starkware/starknet/services/api/gateway/gateway_client.py#L13-L17)
106
111
  *
107
- * @param tx - transaction to be invoked
112
+ * @param transaction - transaction to be invoked
108
113
  * @returns a confirmation of invoking a function on the starknet contract
109
114
  */
110
- public abstract addTransaction(tx: Transaction): Promise<AddTransactionResponse>;
115
+ public abstract addTransaction(transaction: Transaction): Promise<AddTransactionResponse>;
111
116
 
112
117
  /**
113
118
  * Deploys a given compiled contract (json) to starknet
@@ -135,7 +140,7 @@ export abstract class ProviderInterface {
135
140
  contractAddress: string,
136
141
  entrypointSelector: string,
137
142
  calldata?: string[],
138
- signature?: [BigNumberish, BigNumberish]
143
+ signature?: Signature
139
144
  ): Promise<AddTransactionResponse>;
140
145
 
141
146
  public abstract waitForTx(txHash: BigNumberish, retryInterval?: number): Promise<void>;
@@ -1,12 +1,13 @@
1
1
  import assert from 'minimalistic-assert';
2
2
 
3
3
  import { Provider } from '../provider';
4
- import { AddTransactionResponse, KeyPair, Transaction } from '../types';
4
+ import { AddTransactionResponse, KeyPair, Signature, Transaction } from '../types';
5
5
  import { sign } from '../utils/ellipticCurve';
6
6
  import { addHexPrefix } from '../utils/encode';
7
7
  import { hashMessage } from '../utils/hash';
8
8
  import { toBN } from '../utils/number';
9
9
  import { getSelectorFromName } from '../utils/stark';
10
+ import { TypedData, getMessageHash } from '../utils/typedData';
10
11
  import { SignerInterface } from './interface';
11
12
 
12
13
  export class Signer extends Provider implements SignerInterface {
@@ -25,45 +26,76 @@ export class Signer extends Provider implements SignerInterface {
25
26
  *
26
27
  * [Reference](https://github.com/starkware-libs/cairo-lang/blob/f464ec4797361b6be8989e36e02ec690e74ef285/src/starkware/starknet/services/api/gateway/gateway_client.py#L13-L17)
27
28
  *
28
- * @param tx - transaction to be invoked
29
+ * @param transaction - transaction to be invoked
29
30
  * @returns a confirmation of invoking a function on the starknet contract
30
31
  */
31
- public override async addTransaction(tx: Transaction): Promise<AddTransactionResponse> {
32
- if (tx.type === 'DEPLOY') return super.addTransaction(tx);
32
+ public override async addTransaction(transaction: Transaction): Promise<AddTransactionResponse> {
33
+ if (transaction.type === 'DEPLOY') return super.addTransaction(transaction);
33
34
 
34
- assert(!tx.signature, "Adding signatures to a signer tx currently isn't supported");
35
+ assert(
36
+ !transaction.signature,
37
+ "Adding signatures to a signer transaction currently isn't supported"
38
+ );
35
39
 
36
- const { result } = await this.callContract({
37
- contract_address: this.address,
38
- entry_point_selector: getSelectorFromName('get_nonce'),
39
- });
40
- const nonceBn = toBN(result[0]);
41
- const calldataDecimal = (tx.calldata || []).map((x) => toBN(x).toString());
40
+ let nonceBn;
41
+ if (transaction.nonce) {
42
+ nonceBn = toBN(transaction.nonce);
43
+ } else {
44
+ const { result } = await this.callContract({
45
+ contract_address: this.address,
46
+ entry_point_selector: getSelectorFromName('get_nonce'),
47
+ });
48
+ nonceBn = toBN(result[0]);
49
+ }
50
+
51
+ const calldataDecimal = (transaction.calldata || []).map((x) => toBN(x).toString());
42
52
 
43
53
  const msgHash = addHexPrefix(
44
54
  hashMessage(
45
55
  this.address,
46
- tx.contract_address,
47
- tx.entry_point_selector,
56
+ transaction.contract_address,
57
+ transaction.entry_point_selector,
48
58
  calldataDecimal,
49
59
  nonceBn.toString()
50
60
  )
51
61
  );
52
62
 
53
- const { r, s } = sign(this.keyPair, msgHash);
63
+ const signature = sign(this.keyPair, msgHash);
54
64
 
55
65
  return super.addTransaction({
56
66
  type: 'INVOKE_FUNCTION',
57
67
  entry_point_selector: getSelectorFromName('execute'),
58
68
  calldata: [
59
- tx.contract_address,
60
- tx.entry_point_selector,
69
+ transaction.contract_address,
70
+ transaction.entry_point_selector,
61
71
  calldataDecimal.length.toString(),
62
72
  ...calldataDecimal,
63
73
  nonceBn.toString(),
64
74
  ].map((x) => toBN(x).toString()),
65
75
  contract_address: this.address,
66
- signature: [r, s],
76
+ signature,
67
77
  });
68
78
  }
79
+
80
+ /**
81
+ * Sign an JSON object with the starknet private key and return the signature
82
+ *
83
+ * @param json - JSON object to be signed
84
+ * @returns the signature of the JSON object
85
+ * @throws {Error} if the JSON object is not a valid JSON
86
+ */
87
+ public async signMessage(typedData: TypedData): Promise<Signature> {
88
+ return sign(this.keyPair, await this.hashMessage(typedData));
89
+ }
90
+
91
+ /**
92
+ * Hash a JSON object with pederson hash and return the hash
93
+ *
94
+ * @param json - JSON object to be hashed
95
+ * @returns the hash of the JSON object
96
+ * @throws {Error} if the JSON object is not a valid JSON
97
+ */
98
+ public async hashMessage(typedData: TypedData): Promise<string> {
99
+ return getMessageHash(typedData, this.address);
100
+ }
69
101
  }
@@ -1,5 +1,6 @@
1
1
  import { Provider } from '../provider';
2
- import { AddTransactionResponse, Transaction } from '../types';
2
+ import { AddTransactionResponse, Signature, Transaction } from '../types';
3
+ import { TypedData } from '../utils/typedData/types';
3
4
 
4
5
  export abstract class SignerInterface extends Provider {
5
6
  public abstract address: string;
@@ -8,8 +9,30 @@ export abstract class SignerInterface extends Provider {
8
9
  *
9
10
  * [Reference](https://github.com/starkware-libs/cairo-lang/blob/f464ec4797361b6be8989e36e02ec690e74ef285/src/starkware/starknet/services/api/gateway/gateway_client.py#L13-L17)
10
11
  *
11
- * @param tx - transaction to be invoked
12
+ * @param transaction - transaction to be invoked
12
13
  * @returns a confirmation of invoking a function on the starknet contract
13
14
  */
14
- public abstract override addTransaction(tx: Transaction): Promise<AddTransactionResponse>;
15
+ public abstract override addTransaction(
16
+ transaction: Transaction
17
+ ): Promise<AddTransactionResponse>;
18
+
19
+ /**
20
+ * Sign an JSON object for off-chain usage with the starknet private key and return the signature
21
+ * This adds a message prefix so it cant be interchanged with transactions
22
+ *
23
+ * @param json - JSON object to be signed
24
+ * @returns the signature of the JSON object
25
+ * @throws {Error} if the JSON object is not a valid JSON
26
+ */
27
+ public abstract signMessage(typedData: TypedData): Promise<Signature>;
28
+
29
+ /**
30
+ * Hash a JSON object with pederson hash and return the hash
31
+ * This adds a message prefix so it cant be interchanged with transactions
32
+ *
33
+ * @param json - JSON object to be hashed
34
+ * @returns the hash of the JSON object
35
+ * @throws {Error} if the JSON object is not a valid JSON
36
+ */
37
+ public abstract hashMessage(typedData: TypedData): Promise<string>;
15
38
  }
package/src/types.ts CHANGED
@@ -3,15 +3,21 @@ import type { ec as EC } from 'elliptic';
3
3
  import type { BigNumberish } from './utils/number';
4
4
 
5
5
  export type KeyPair = EC.KeyPair;
6
- export type Signature = EC.Signature;
6
+ export type Signature = BigNumberish[];
7
7
 
8
8
  export type GetContractAddressesResponse = {
9
9
  Starknet: string;
10
10
  GpsStatementVerifier: string;
11
11
  };
12
12
 
13
- export type Status = 'NOT_RECEIVED' | 'RECEIVED' | 'PENDING' | 'REJECTED' | 'ACCEPTED_ONCHAIN';
14
- export type TxStatus = 'TRANSACTION_RECEIVED';
13
+ export type Status =
14
+ | 'NOT_RECEIVED'
15
+ | 'RECEIVED'
16
+ | 'PENDING'
17
+ | 'ACCEPTED_ON_L2'
18
+ | 'ACCEPTED_ON_L1'
19
+ | 'REJECTED';
20
+ export type TransactionStatus = 'TRANSACTION_RECEIVED';
15
21
  export type Type = 'DEPLOY' | 'INVOKE_FUNCTION';
16
22
  export type EntryPointType = 'EXTERNAL';
17
23
  export type CompressedProgram = string;
@@ -37,6 +43,7 @@ export type Abi = FunctionAbi | StructAbi;
37
43
 
38
44
  export type EntryPointsByType = object;
39
45
  export type Program = object;
46
+ export type BlockNumber = 'pending' | null | number;
40
47
 
41
48
  export type CompiledContract = {
42
49
  abi: Abi[];
@@ -51,15 +58,17 @@ export type DeployTransaction = {
51
58
  contract_definition: CompressedCompiledContract;
52
59
  contract_address_salt: BigNumberish;
53
60
  constructor_calldata: string[];
61
+ nonce?: BigNumberish;
54
62
  };
55
63
 
56
64
  export type InvokeFunctionTransaction = {
57
65
  type: 'INVOKE_FUNCTION';
58
66
  contract_address: string;
59
- signature?: [BigNumberish, BigNumberish];
67
+ signature?: Signature;
60
68
  entry_point_type?: EntryPointType;
61
69
  entry_point_selector: string;
62
70
  calldata?: string[];
71
+ nonce?: BigNumberish;
63
72
  };
64
73
 
65
74
  export type CallContractTransaction = Omit<InvokeFunctionTransaction, 'type'>;
@@ -87,7 +96,7 @@ export type GetBlockResponse = {
87
96
  payload: string[];
88
97
  from_address: string;
89
98
  }[];
90
- block_number: number;
99
+ block_number: BlockNumber;
91
100
  status: Status;
92
101
  transaction_index: number;
93
102
  };
@@ -110,13 +119,13 @@ export type GetTransactionResponse = {
110
119
  status: Status;
111
120
  transaction: Transaction;
112
121
  block_hash: string;
113
- block_number: number;
122
+ block_number: BlockNumber;
114
123
  transaction_index: number;
115
124
  transaction_hash: string;
116
125
  };
117
126
 
118
127
  export type AddTransactionResponse = {
119
- code: TxStatus;
128
+ code: TransactionStatus;
120
129
  transaction_hash: string;
121
130
  address?: string;
122
131
  };
@@ -53,6 +53,17 @@ export function getStarkKey(keyPair: KeyPair): string {
53
53
  return addHexPrefix(sanitizeBytes((keyPair as any).pub.getX().toString(16), 2));
54
54
  }
55
55
 
56
+ /**
57
+ * Takes a public key and casts it into `elliptic` KeyPair format.
58
+ *
59
+ * @param publicKey - public key which should get casted to a KeyPair
60
+ * @returns keyPair with public key only, which can be used to verify signatures, but cant sign anything
61
+ */
62
+ export function getKeyPairFromPublicKey(publicKey: BigNumberish): KeyPair {
63
+ const publicKeyBn = toBN(publicKey);
64
+ return ec.keyFromPublic(removeHexPrefix(toHex(publicKeyBn)), 'hex');
65
+ }
66
+
56
67
  /*
57
68
  Signs a message using the provided key.
58
69
  key should be an KeyPair with a valid private key.
@@ -69,7 +80,13 @@ export function sign(keyPair: KeyPair, msgHash: string): Signature {
69
80
  assertInRange(r, ONE, toBN(addHexPrefix(MAX_ECDSA_VAL)), 'r');
70
81
  assertInRange(s, ONE, toBN(addHexPrefix(EC_ORDER)), 's');
71
82
  assertInRange(w, ONE, toBN(addHexPrefix(MAX_ECDSA_VAL)), 'w');
72
- return msgSignature;
83
+ return [r, s];
84
+ }
85
+
86
+ function chunkArray(arr: any[], n: number): any[][] {
87
+ return Array(Math.ceil(arr.length / n))
88
+ .fill('')
89
+ .map((_, i) => arr.slice(i * n, i * n + n));
73
90
  }
74
91
 
75
92
  /*
@@ -78,15 +95,20 @@ export function sign(keyPair: KeyPair, msgHash: string): Signature {
78
95
  msgSignature should be an Signature.
79
96
  Returns a boolean true if the verification succeeds.
80
97
  */
81
- export function verify(keyPair: KeyPair, msgHash: string, sig: Signature): boolean {
98
+ export function verify(keyPair: KeyPair | KeyPair[], msgHash: string, sig: Signature): boolean {
99
+ const keyPairArray = Array.isArray(keyPair) ? keyPair : [keyPair];
82
100
  const msgHashBN = toBN(addHexPrefix(msgHash));
101
+ assert(sig.length % 2 === 0, 'Signature must be an array of length dividable by 2');
83
102
  assertInRange(msgHashBN, ZERO, toBN(addHexPrefix(MAX_ECDSA_VAL)), 'msgHash');
84
- const { r, s } = sig;
85
- const w = s.invm(ec.n!);
86
- // Verify signature has valid length.
87
- assertInRange(r, ONE, toBN(addHexPrefix(MAX_ECDSA_VAL)), 'r');
88
- assertInRange(s, ONE, toBN(addHexPrefix(EC_ORDER)), 's');
89
- assertInRange(w, ONE, toBN(addHexPrefix(MAX_ECDSA_VAL)), 'w');
103
+ assert(keyPairArray.length === sig.length / 2, 'Signature and keyPair length must be equal');
90
104
 
91
- return keyPair.verify(fixMessage(msgHash), sig);
105
+ return chunkArray(sig, 2).every(([r, s], i) => {
106
+ const rBN = toBN(r);
107
+ const sBN = toBN(s);
108
+ const w = sBN.invm((ec as any).n);
109
+ assertInRange(rBN, ONE, toBN(addHexPrefix(MAX_ECDSA_VAL)), 'r');
110
+ assertInRange(sBN, ONE, toBN(addHexPrefix(EC_ORDER)), 's');
111
+ assertInRange(w, ONE, toBN(addHexPrefix(MAX_ECDSA_VAL)), 'w');
112
+ return ec.verify(fixMessage(msgHash), { r: rBN, s: sBN }, keyPairArray[i]) ?? false;
113
+ });
92
114
  }
@@ -1,11 +1,11 @@
1
1
  import { gzip } from 'pako';
2
2
 
3
- import { CompressedProgram, Program } from '../types';
3
+ import { CompressedProgram, Program, Signature } from '../types';
4
4
  import { genKeyPair, getStarkKey } from './ellipticCurve';
5
5
  import { addHexPrefix, btoaUniversal } from './encode';
6
6
  import { starknetKeccak } from './hash';
7
7
  import { stringify } from './json';
8
- import { BigNumberish, toBN, toHex } from './number';
8
+ import { toBN, toHex } from './number';
9
9
 
10
10
  /**
11
11
  * Function to compress compiled cairo program
@@ -41,15 +41,11 @@ export function makeAddress(input: string): string {
41
41
  return addHexPrefix(input).toLowerCase();
42
42
  }
43
43
 
44
- export function formatSignature(sig?: [BigNumberish, BigNumberish]): [string, string] | [] {
44
+ export function formatSignature(sig?: Signature): string[] {
45
45
  if (!sig) return [];
46
46
  try {
47
- return sig.map((x) => toBN(x)).map((x) => x.toString()) as [string, string];
47
+ return sig.map((x) => toBN(x)).map((x) => x.toString());
48
48
  } catch (e) {
49
49
  return [];
50
50
  }
51
51
  }
52
-
53
- export function compileStructToCalldata<S extends { [k: string]: string }>(struct: S): string[] {
54
- return Object.values(struct);
55
- }