Skip to content

Commit 114b612

Browse files
committed
Big things:
- Added new Transaction models: InvokeOutsideV1, InvokeOutsideV2 - Added method for SNIP9 nonce verification - Added Account methods for population and broadcasting(execution) of InvokeOutside transactions Small fixes: - Allowed for incomplete definition of ParameterDict as `contains` fiels is often missing and linter is complaining. WIP: miss docs and tests
1 parent bfcdb20 commit 114b612

File tree

5 files changed

+290
-4
lines changed

5 files changed

+290
-4
lines changed

starknet_py/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,6 @@
4545
PUBLIC_KEY_RESPONSE_LENGTH = 65
4646
SIGNATURE_RESPONSE_LENGTH = 65
4747
VERSION_RESPONSE_LENGTH = 3
48+
49+
# SNIP-9 ANY_CALLER
50+
ANY_CALLER = 0x414e595f43414c4c4552

starknet_py/net/account/account.py

Lines changed: 142 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
55

66
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
88
from starknet_py.hash.address import compute_address
99
from starknet_py.hash.selector import get_selector_from_name
1010
from starknet_py.hash.utils import verify_message_signature
@@ -17,6 +17,7 @@
1717
EstimatedFee,
1818
Hash,
1919
ResourceBounds,
20+
ExecutionTimeBounds,
2021
ResourceBoundsMapping,
2122
SentTransactionResponse,
2223
SierraContractClass,
@@ -34,6 +35,8 @@
3435
DeployAccountV3,
3536
InvokeV1,
3637
InvokeV3,
38+
InvokeOutsideV1,
39+
InvokeOutsideV2,
3740
TypeAccountTransaction,
3841
)
3942
from starknet_py.net.models.typed_data import TypedDataDict
@@ -218,6 +221,54 @@ async def _prepare_invoke(
218221

219222
return _add_max_fee_to_transaction(transaction, max_fee)
220223

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+
221272
async def _prepare_invoke_v3(
222273
self,
223274
calls: Calls,
@@ -290,6 +341,31 @@ async def get_nonce(
290341
self.address, block_hash=block_hash, block_number=block_number
291342
)
292343

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+
293369
async def get_balance(
294370
self,
295371
token_address: Optional[AddressRepresentation] = None,
@@ -344,6 +420,40 @@ async def sign_invoke_v1(
344420
signature = self.signer.sign_transaction(execute_tx)
345421
return _add_signature_to_transaction(execute_tx, signature)
346422

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+
347457
async def sign_invoke_v3(
348458
self,
349459
calls: Calls,
@@ -594,6 +704,37 @@ async def execute_v3(
594704
)
595705
return await self._client.send_transaction(execute_transaction)
596706

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+
597738
def sign_message(self, typed_data: Union[TypedData, TypedDataDict]) -> List[int]:
598739
if isinstance(typed_data, TypedData):
599740
return self.signer.sign_message(typed_data, self.address)

starknet_py/net/client_models.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
to true. Consequently, any unknown fields in response will be excluded.
88
"""
99

10+
import datetime
1011
import json
1112
from abc import ABC
1213
from dataclasses import dataclass, field
@@ -114,6 +115,22 @@ class ResourceBounds:
114115
def init_with_zeros():
115116
return ResourceBounds(max_amount=0, max_price_per_unit=0)
116117

118+
@dataclass
119+
class ExecutionTimeBounds:
120+
"""
121+
Dataclass representing time bounds within which the given time bounds.
122+
"""
123+
124+
execute_after: datetime.datetime
125+
execute_before: datetime.datetime
126+
127+
@staticmethod
128+
def init_without_bounds():
129+
return ExecutionTimeBounds(
130+
execute_after=datetime.datetime.min,
131+
execute_before=datetime.datetime.max,
132+
)
133+
117134

118135
@dataclass
119136
class ResourceBoundsMapping:
@@ -175,6 +192,7 @@ class TransactionType(Enum):
175192
DEPLOY_ACCOUNT = "DEPLOY_ACCOUNT"
176193
DEPLOY = "DEPLOY"
177194
L1_HANDLER = "L1_HANDLER"
195+
OUTSIDE = "OUTSIDE"
178196

179197

180198
@dataclass

0 commit comments

Comments
 (0)