|
4 | 4 | from typing import Any, Dict, Iterable, List, Optional, Tuple, Union |
5 | 5 |
|
6 | 6 | from starknet_py.common import create_compiled_contract, create_sierra_compiled_contract |
7 | | -from starknet_py.constants import FEE_CONTRACT_ADDRESS, QUERY_VERSION_BASE |
| 7 | +from starknet_py.constants import FEE_CONTRACT_ADDRESS, QUERY_VERSION_BASE, ANY_CALLER |
8 | 8 | from starknet_py.hash.address import compute_address |
9 | 9 | from starknet_py.hash.selector import get_selector_from_name |
10 | 10 | from starknet_py.hash.utils import verify_message_signature |
|
17 | 17 | EstimatedFee, |
18 | 18 | Hash, |
19 | 19 | ResourceBounds, |
| 20 | + ExecutionTimeBounds, |
20 | 21 | ResourceBoundsMapping, |
21 | 22 | SentTransactionResponse, |
22 | 23 | SierraContractClass, |
|
34 | 35 | DeployAccountV3, |
35 | 36 | InvokeV1, |
36 | 37 | InvokeV3, |
| 38 | + InvokeOutsideV1, |
| 39 | + InvokeOutsideV2, |
37 | 40 | TypeAccountTransaction, |
38 | 41 | ) |
39 | 42 | from starknet_py.net.models.typed_data import TypedDataDict |
@@ -218,6 +221,54 @@ async def _prepare_invoke( |
218 | 221 |
|
219 | 222 | return _add_max_fee_to_transaction(transaction, max_fee) |
220 | 223 |
|
| 224 | + async def _prepare_invoke_outside_v1( |
| 225 | + self, |
| 226 | + calls: Calls, |
| 227 | + execution_time_bounds: ExecutionTimeBounds, |
| 228 | + caller: AddressRepresentation, |
| 229 | + *, |
| 230 | + nonce: Optional[int] = None, |
| 231 | + ) -> InvokeOutsideV1: |
| 232 | + if nonce is None: |
| 233 | + nonce = await self.get_SNIP9_nonce() |
| 234 | + |
| 235 | + transaction = InvokeOutsideV1( |
| 236 | + calls=list(ensure_iterable(calls)), |
| 237 | + execute_after=execution_time_bounds.execute_after, |
| 238 | + execute_before=execution_time_bounds.execute_before, |
| 239 | + caller_address=parse_address(caller), |
| 240 | + signer_address=self.address, |
| 241 | + nonce=nonce, |
| 242 | + signature=[], # TODO(baitcode): should be default |
| 243 | + version=1, |
| 244 | + ) |
| 245 | + |
| 246 | + return transaction |
| 247 | + |
| 248 | + async def _prepare_invoke_outside_v2( |
| 249 | + self, |
| 250 | + calls: Calls, |
| 251 | + execution_time_bounds: ExecutionTimeBounds, |
| 252 | + caller: AddressRepresentation, |
| 253 | + *, |
| 254 | + nonce: Optional[int] = None, |
| 255 | + ) -> InvokeOutsideV2: |
| 256 | + if nonce is None: |
| 257 | + nonce = await self.get_SNIP9_nonce() |
| 258 | + |
| 259 | + transaction = InvokeOutsideV2( |
| 260 | + calls=list(ensure_iterable(calls)), |
| 261 | + execute_after=execution_time_bounds.execute_after, |
| 262 | + execute_before=execution_time_bounds.execute_before, |
| 263 | + caller_address=parse_address(caller), |
| 264 | + signer_address=self.address, |
| 265 | + nonce=nonce or await self.get_nonce(), |
| 266 | + signature=[], |
| 267 | + version=2, |
| 268 | + ) |
| 269 | + |
| 270 | + return transaction |
| 271 | + |
221 | 272 | async def _prepare_invoke_v3( |
222 | 273 | self, |
223 | 274 | calls: Calls, |
@@ -290,6 +341,31 @@ async def get_nonce( |
290 | 341 | self.address, block_hash=block_hash, block_number=block_number |
291 | 342 | ) |
292 | 343 |
|
| 344 | + async def check_SNIP9_nonce( |
| 345 | + self, |
| 346 | + nonce: int, |
| 347 | + *, |
| 348 | + block_hash: Optional[Union[Hash, Tag]] = None, |
| 349 | + block_number: Optional[Union[int, Tag]] = None, |
| 350 | + ) -> bool: |
| 351 | + (is_valid, ) = await self._client.call_contract( |
| 352 | + Call( |
| 353 | + to_addr=parse_address(self.address), |
| 354 | + selector=get_selector_from_name("is_valid_outside_execution_nonce"), |
| 355 | + calldata=[nonce], |
| 356 | + ), |
| 357 | + block_hash=block_hash, block_number=block_number |
| 358 | + ) |
| 359 | + return bool(is_valid) |
| 360 | + |
| 361 | + async def get_SNIP9_nonce(self) -> int: |
| 362 | + while True: # TODO(baitcode): add a limit to avoid infinite loop |
| 363 | + |
| 364 | + random_stark_address = KeyPair.generate().public_key |
| 365 | + |
| 366 | + if await self.check_SNIP9_nonce(random_stark_address): |
| 367 | + return random_stark_address |
| 368 | + |
293 | 369 | async def get_balance( |
294 | 370 | self, |
295 | 371 | token_address: Optional[AddressRepresentation] = None, |
@@ -344,6 +420,40 @@ async def sign_invoke_v1( |
344 | 420 | signature = self.signer.sign_transaction(execute_tx) |
345 | 421 | return _add_signature_to_transaction(execute_tx, signature) |
346 | 422 |
|
| 423 | + async def sign_execute_outside_v1( |
| 424 | + self, |
| 425 | + calls: Calls, |
| 426 | + execution_time_bounds: ExecutionTimeBounds, |
| 427 | + *, |
| 428 | + caller: AddressRepresentation = ANY_CALLER, |
| 429 | + nonce: Optional[int] = None, |
| 430 | + ) -> InvokeOutsideV1: |
| 431 | + execute_outside_tx = await self._prepare_invoke_outside_v1( |
| 432 | + calls, |
| 433 | + execution_time_bounds, |
| 434 | + caller=caller, |
| 435 | + nonce=nonce or await self.get_SNIP9_nonce(), |
| 436 | + ) |
| 437 | + signature = self.signer.sign_transaction(execute_outside_tx) |
| 438 | + return _add_signature_to_transaction(execute_outside_tx, signature) |
| 439 | + |
| 440 | + async def sign_execute_outside_v2( |
| 441 | + self, |
| 442 | + calls: Calls, |
| 443 | + execution_time_bounds: ExecutionTimeBounds, |
| 444 | + *, |
| 445 | + caller: AddressRepresentation = ANY_CALLER, |
| 446 | + nonce: Optional[int] = None, |
| 447 | + ) -> InvokeOutsideV2: |
| 448 | + execute_outside_tx = await self._prepare_invoke_outside_v2( |
| 449 | + calls, |
| 450 | + execution_time_bounds, |
| 451 | + caller=caller, |
| 452 | + nonce=nonce or await self.get_SNIP9_nonce(), |
| 453 | + ) |
| 454 | + signature = self.signer.sign_transaction(execute_outside_tx) |
| 455 | + return _add_signature_to_transaction(execute_outside_tx, signature) |
| 456 | + |
347 | 457 | async def sign_invoke_v3( |
348 | 458 | self, |
349 | 459 | calls: Calls, |
@@ -594,6 +704,37 @@ async def execute_v3( |
594 | 704 | ) |
595 | 705 | return await self._client.send_transaction(execute_transaction) |
596 | 706 |
|
| 707 | + async def execute_outside_v1( |
| 708 | + self, |
| 709 | + calls: Calls, |
| 710 | + execution_time_bounds: ExecutionTimeBounds, |
| 711 | + caller: AddressRepresentation = ANY_CALLER, |
| 712 | + nonce: Optional[int] = None, |
| 713 | + ) -> SentTransactionResponse: |
| 714 | + execute_transaction = await self.sign_execute_outside_v1( |
| 715 | + calls, |
| 716 | + execution_time_bounds, |
| 717 | + caller=caller, |
| 718 | + nonce=nonce, |
| 719 | + ) |
| 720 | + return await self._client.send_transaction(execute_transaction) |
| 721 | + |
| 722 | + async def execute_outside_v2( |
| 723 | + self, |
| 724 | + calls: Calls, |
| 725 | + execution_time_bounds: ExecutionTimeBounds, |
| 726 | + caller: AddressRepresentation = ANY_CALLER, |
| 727 | + nonce: Optional[int] = None, |
| 728 | + ) -> SentTransactionResponse: |
| 729 | + execute_transaction = await self.sign_execute_outside_v2( |
| 730 | + calls, |
| 731 | + execution_time_bounds, |
| 732 | + caller=caller, |
| 733 | + nonce=nonce, |
| 734 | + ) |
| 735 | + return await self._client.send_transaction(execute_transaction) |
| 736 | + |
| 737 | + |
597 | 738 | def sign_message(self, typed_data: Union[TypedData, TypedDataDict]) -> List[int]: |
598 | 739 | if isinstance(typed_data, TypedData): |
599 | 740 | return self.signer.sign_message(typed_data, self.address) |
|
0 commit comments