py-near 1.1.52__py3-none-any.whl → 1.1.53__py3-none-any.whl

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: py-near
3
- Version: 1.1.52
3
+ Version: 1.1.53
4
4
  Summary: Pretty simple and fully asynchronous framework for working with NEAR blockchain
5
5
  Author: pvolnov
6
6
  Author-email: petr@herewallet.app
@@ -11,12 +11,10 @@ Classifier: Programming Language :: Python :: 3.9
11
11
  Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Programming Language :: Python :: 3.11
13
13
  Requires-Dist: base58 (>=2.1.1,<3.0.0)
14
- Requires-Dist: eth-keys (>=0.6.1,<0.7.0)
15
- Requires-Dist: eth-utils (>=5.2.0,<6.0.0)
16
14
  Requires-Dist: frozenlist (>=1.4.1,<2.0.0)
17
- Requires-Dist: httpx (==0.26.0)
15
+ Requires-Dist: httpx (>=0.26.0)
18
16
  Requires-Dist: loguru (>=0.7.2,<0.8.0)
19
- Requires-Dist: py-near-primitives (==0.2.3)
17
+ Requires-Dist: py-near-primitives (==0.2.4)
20
18
  Requires-Dist: pydantic (>=2.6.2,<3.0.0)
21
19
  Requires-Dist: pynacl (>=1.5.0,<2.0.0)
22
20
  Description-Content-Type: text/markdown
@@ -20,19 +20,10 @@ py_near/exceptions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuF
20
20
  py_near/exceptions/exceptions.py,sha256=DEFipaAHm0y7oCuN2QKzHsiQvUTUQVl-Ce36Ag7n7hs,5509
21
21
  py_near/exceptions/provider.py,sha256=K-wexgjPJ8sw42JePwaP7R5dJEIn9DoFJRvVcURsx6s,7718
22
22
  py_near/models.py,sha256=85ynmLVJnzWvFKq-Z8iIO0waU-2iP-CSkaOGX4mYoL0,11590
23
- py_near/mpc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
- py_near/mpc/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
- py_near/mpc/auth/auth_2fa.py,sha256=ZxO8mGO95ixb0qXnKrfmVxP7ivpDKerH4-udV_vUmbs,3124
26
- py_near/mpc/auth/base.py,sha256=IoFdRWUQcerYZq8TCkpjXJQRcENnzA9WwFbVPgmHgcs,629
27
- py_near/mpc/auth/core.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
- py_near/mpc/auth/default_auth.py,sha256=i70412yXYx3MlFSsPxHLKbF0CXTjC39Qtl34et8kAEw,751
29
- py_near/mpc/auth/keys_auth.py,sha256=DJPi37DnV6r6cSskHEhHBiR_QiaAQJkg0AEsYp6cePA,2359
30
- py_near/mpc/models.py,sha256=9MSfBDqfqkaJu6eJxnwyjgihsexZboehbJ6vAGa4BTU,663
31
- py_near/mpc/wallet.py,sha256=sNr9UlcyVDPF_Zho_4iTupbUoZct3paQlxxDHlHK67k,9768
32
23
  py_near/providers.py,sha256=X_GfddFzmafBatqFJH41Dp_lYvTNcz1Q3Ko2tEnN0VU,16193
33
24
  py_near/transactions.py,sha256=QwEP3qOGxb8OB2vZA_rU20jvkPA0FmJowUKDxbuzOuI,3251
34
25
  py_near/utils.py,sha256=FirRH93ydH1cwjn0-sNrZeIn3BRD6QHedrP2VkAdJ6g,126
35
- py_near-1.1.52.dist-info/LICENSE,sha256=I_GOA9xJ35FiL-KnYXZJdATkbO2KcV2dK2enRGVxzKM,1023
36
- py_near-1.1.52.dist-info/METADATA,sha256=A_pG9PjBofHZQSJFoimIFb33ljVJgC7qT1OCnhlDR9U,4846
37
- py_near-1.1.52.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
38
- py_near-1.1.52.dist-info/RECORD,,
26
+ py_near-1.1.53.dist-info/LICENSE,sha256=I_GOA9xJ35FiL-KnYXZJdATkbO2KcV2dK2enRGVxzKM,1023
27
+ py_near-1.1.53.dist-info/METADATA,sha256=aE8cvfs6Axphip2sszcwmHLdd1cXmXq1d3W54CjgMe4,4763
28
+ py_near-1.1.53.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
29
+ py_near-1.1.53.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.9.0
2
+ Generator: poetry-core 1.9.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
py_near/mpc/__init__.py DELETED
File without changes
File without changes
@@ -1,97 +0,0 @@
1
- import datetime
2
- from hashlib import sha256
3
- from typing import Optional
4
-
5
- import base58
6
- from nacl import signing
7
- from pydantic import BaseModel
8
-
9
- from py_near.account import Account
10
- from py_near.mpc.auth.base import AuthContract
11
-
12
-
13
- class Wallet2FA(BaseModel):
14
- wallet_id: str
15
- public_key: str
16
- delay_to_remove: int
17
- cancellation_at: int
18
- conditions: Optional[str] = None
19
-
20
- @classmethod
21
- def build(cls, data: dict, wallet_id):
22
- return cls(
23
- wallet_id=wallet_id,
24
- public_key=data["public_key"],
25
- cancellation_at=data["cancellation_at"],
26
- delay_to_remove=data["delay_to_remove"],
27
- conditions=data.get("conditions"),
28
- )
29
-
30
-
31
- class AuthContract2FA(AuthContract):
32
- root_pk: signing.SigningKey
33
- contract_id: str = "2fa.auth.hot.tg"
34
-
35
- def __init__(self, root_pk: signing.SigningKey, near_account: Account = None):
36
- self.root_pk = root_pk
37
- self.near_account = near_account
38
- super().__init__()
39
-
40
- def generate_user_payload(self, msg_hash: bytes):
41
- auth_signature = base58.b58encode(self.root_pk.sign(msg_hash).signature).decode(
42
- "utf-8"
43
- )
44
- return auth_signature
45
-
46
- async def get_2fa_for_wallet(self, wallet_id: str) -> Optional[Wallet2FA]:
47
- if not self.near_account:
48
- raise ValueError("Near account is required")
49
- res = (
50
- await self.near_account.view_function(
51
- self.contract_id,
52
- "get_wallet_2fa",
53
- {
54
- "wallet_id": wallet_id,
55
- },
56
- )
57
- ).result
58
- if res:
59
- return Wallet2FA.build(res, wallet_id)
60
-
61
- async def cancel_2fa(
62
- self, user_pk: signing.SigningKey, wallet_id: str, rule_id: int = 0
63
- ):
64
- if not self.near_account:
65
- raise ValueError("Near account is required")
66
- two_fa = await self.get_2fa_for_wallet(wallet_id)
67
- ts = int(datetime.datetime.utcnow().timestamp() * 10**9)
68
-
69
- if two_fa.cancellation_at == 0:
70
- msg_hash = sha256(f"CANCEL_2FA:{wallet_id}:{ts}".encode("utf-8")).digest()
71
- auth_signature = base58.b58encode(user_pk.sign(msg_hash).signature).decode(
72
- "utf-8"
73
- )
74
- return await self.near_account.function_call(
75
- self.contract_id,
76
- "cancel_2fa",
77
- {
78
- "wallet_id": wallet_id,
79
- "user_signature": auth_signature,
80
- "timestamp": ts,
81
- },
82
- included=True,
83
- )
84
- else:
85
- if two_fa.cancellation_at < ts:
86
- raise ValueError(
87
- f"2FA already cancelled at {datetime.datetime.fromtimestamp(two_fa.cancellation_at / 10**9)}"
88
- )
89
- return await self.near_account.function_call(
90
- self.contract_id,
91
- "complete_cancel_2fa",
92
- {
93
- "wallet_id": wallet_id,
94
- "rule_id": rule_id,
95
- },
96
- included=True,
97
- )
py_near/mpc/auth/base.py DELETED
@@ -1,23 +0,0 @@
1
- from py_near.account import Account
2
- from py_near.mpc.models import WalletAccessModel
3
-
4
-
5
- class AuthContract:
6
- contract_id: str
7
-
8
- def __init__(self, near_account: Account = None):
9
- self.near_account = near_account
10
-
11
- async def grant_access(
12
- self,
13
- wallet_id: str,
14
- access: WalletAccessModel,
15
- near_account,
16
- wallet_auth_method=0,
17
- ):
18
- raise NotImplementedError("Grant access is not implemented")
19
-
20
- async def revoke_assess(
21
- self, wallet_id: str, near_account: Account, access_id: int
22
- ):
23
- raise NotImplementedError("Revoke access is not implemented")
py_near/mpc/auth/core.py DELETED
File without changes
@@ -1,29 +0,0 @@
1
- import json
2
-
3
- import base58
4
- from nacl import signing
5
-
6
- from py_near.mpc.auth.base import AuthContract
7
-
8
-
9
- class DefaultAuthContract(AuthContract):
10
- root_pk: signing.SigningKey
11
- contract_id: str = "default.auth.hot.tg"
12
-
13
- def __init__(self, root_pk: signing.SigningKey):
14
- self.root_pk = root_pk
15
- super().__init__()
16
-
17
- def generate_user_payload(self, msg_hash: bytes):
18
- auth_signature = base58.b58encode(self.root_pk.sign(msg_hash).signature).decode(
19
- "utf-8"
20
- )
21
- return json.dumps(
22
- dict(
23
- signature=auth_signature,
24
- public_key=base58.b58encode(
25
- bytes(self.root_pk.verify_key.encode())
26
- ).decode(),
27
- )
28
- )
29
-
@@ -1,83 +0,0 @@
1
- import json
2
- from hashlib import sha256
3
- from typing import List
4
-
5
- import base58
6
- from nacl import signing
7
-
8
- from py_near.account import Account
9
- from py_near.mpc.auth.base import AuthContract
10
- from py_near.mpc.models import WalletAccessModel
11
-
12
-
13
- class KeysAuthContract(AuthContract):
14
- auth_keys: List[signing.SigningKey]
15
- auth_method: int
16
- contract_id: str = "keys.auth.hot.tg"
17
-
18
- def __init__(
19
- self,
20
- auth_keys: List[signing.SigningKey],
21
- auth_method=0,
22
- near_account: Account = None,
23
- ):
24
- self.auth_keys = auth_keys
25
- self.auth_method = auth_method
26
- super().__init__(near_account)
27
-
28
- def generate_user_payload(self, msg_hash: bytes):
29
- signatures = []
30
- for key in self.auth_keys:
31
- signatures.append(
32
- base58.b58encode(key.sign(msg_hash).signature).decode("utf-8")
33
- )
34
- return json.dumps(
35
- dict(
36
- signatures=signatures,
37
- auth_method=0,
38
- )
39
- )
40
-
41
- async def grant_access(
42
- self,
43
- wallet_id: str,
44
- access: WalletAccessModel,
45
- near_account,
46
- wallet_auth_method=0,
47
- ):
48
- access = access.model_dump()
49
- proof_hash = sha256(
50
- f"{json.dumps(access).replace(' ', '')}.{wallet_id}".encode("utf-8")
51
- ).digest()
52
- signatures = []
53
- for pk in self.auth_keys:
54
- signature = base58.b58encode(pk.sign(proof_hash).signature).decode("utf-8")
55
- signatures.append(signature)
56
-
57
- res = await near_account.function_call(
58
- self.contract_id,
59
- "add_wallet_auth_account_id",
60
- {
61
- "wallet_id": wallet_id,
62
- "access": access,
63
- "signatures": signatures,
64
- "wallet_auth_method": wallet_auth_method,
65
- "auth_method": self.auth_method,
66
- },
67
- included=True,
68
- )
69
- return res
70
-
71
- async def get_rules(self, wallet_id: str):
72
- if not self.near_account:
73
- raise ValueError("Near account is required")
74
- res = (
75
- await self.near_account.view_function(
76
- self.contract_id,
77
- "get_wallet",
78
- {
79
- "wallet_id": wallet_id,
80
- },
81
- )
82
- ).result
83
- return res
py_near/mpc/models.py DELETED
@@ -1,32 +0,0 @@
1
- from enum import Enum
2
- from typing import Optional, List
3
-
4
- from pydantic import BaseModel
5
-
6
-
7
- class CurveType(int, Enum):
8
- SECP256K1 = 0
9
- ED25519 = 1
10
-
11
-
12
- _WALLET_REGISTER = "mpc.hot.tg"
13
-
14
-
15
- class WalletAccessModel(BaseModel):
16
- account_id: str
17
- metadata: Optional[str] = None
18
- chain_id: int
19
- msg: Optional[str] = None
20
-
21
-
22
- class WalletModel(BaseModel):
23
- access_list: List[WalletAccessModel]
24
- key_gen: int = 0
25
-
26
- @classmethod
27
- def build(cls, data: dict):
28
- return cls(
29
- access_list=[WalletAccessModel(**x) for x in data["access_list"]],
30
- block_height=data["block_height"],
31
- key_gen=data["key_gen"],
32
- )
py_near/mpc/wallet.py DELETED
@@ -1,261 +0,0 @@
1
- import json
2
- from hashlib import sha256
3
- from typing import List, Optional
4
-
5
- import base58
6
- import httpx
7
- from eth_keys.datatypes import Signature
8
- from eth_keys.exceptions import BadSignature
9
- from eth_utils import keccak
10
- from loguru import logger
11
- from nacl import encoding, signing
12
-
13
- from py_near.account import Account
14
- from py_near.mpc.auth.auth_2fa import AuthContract2FA
15
- from py_near.mpc.auth.base import AuthContract
16
- from py_near.mpc.auth.default_auth import DefaultAuthContract
17
- from py_near.mpc.auth.keys_auth import KeysAuthContract
18
- from py_near.mpc.models import WalletAccessModel, WalletModel, CurveType
19
-
20
- _WALLET_REGISTER = "mpc.hot.tg"
21
-
22
- AUTH_CLASS = {
23
- "default.auth.hot.tg": DefaultAuthContract,
24
- "keys.auth.hot.tg": KeysAuthContract,
25
- "2fa.auth.hot.tg": AuthContract2FA,
26
- }
27
-
28
-
29
- class MPCWallet:
30
- derive: bytes
31
- near_account: Account
32
- hot_rpc: str
33
-
34
- def __init__(
35
- self,
36
- near_account: Account,
37
- default_root_pk: Optional[bytes] = None,
38
- hot_rpc: str = "https://rpc1.hotdao.ai",
39
- derive: Optional[bytes] = None,
40
- ):
41
- """
42
- :param near_account: Near account
43
- :param derive: Derived key for wallet
44
- :param hot_rpc: Hot RPC url
45
- :param default_root_pk: Default auth key for HOT Protocol, if provided, derive = sha256(default_root_pk.public_key).digest()
46
- """
47
- self.near_account = near_account
48
- if len(default_root_pk) != 32:
49
- raise ValueError("Default root pk should be 32 bytes")
50
- if default_root_pk:
51
- self.default_root_pk = default_root_pk
52
- private_key_obj = self.derive_private_key(0)
53
- derive = sha256(private_key_obj.verify_key.encode()).digest()
54
- elif derive is None:
55
- logger.warning(
56
- "Its recomended to use default_root_pk to generate derives, v0 wallets support will be deprecated"
57
- )
58
- raise ValueError("Derive is required")
59
- self.derive = derive
60
- self.hot_rpc = hot_rpc
61
- self._client = httpx.AsyncClient()
62
-
63
- def derive_private_key(self, gen=0):
64
- private_key = self.default_root_pk
65
- for _ in range(gen):
66
- private_key = sha256(private_key).digest()
67
- return signing.SigningKey(
68
- private_key,
69
- encoder=encoding.RawEncoder,
70
- )
71
-
72
- @property
73
- def wallet_id(self):
74
- return base58.b58encode(sha256(self.derive).digest()).decode("utf-8")
75
-
76
- async def get_wallet(self):
77
- wallet = await self.near_account.view_function(
78
- _WALLET_REGISTER, "get_wallet", args={"wallet_id": self.wallet_id}
79
- )
80
- if wallet.result:
81
- return WalletModel.build(wallet.result)
82
-
83
- async def create_wallet_with_keys_auth(self, public_key: bytes, key_gen=1):
84
- """
85
- Create wallet with keys.auth.hot.tg auth method.
86
- :param public_key: Public key for auth future signs on keys.auth.hot.tg
87
- """
88
- wallet = await self.get_wallet()
89
- if wallet and wallet.access_list[0].account_id != "default.auth.hot.tg":
90
- raise ValueError(
91
- "MPCWallet already exists with different auth method, please use another wallet_id"
92
- )
93
- auth_to_add_msg = json.dumps(
94
- dict(public_keys=[base58.b58encode(public_key).decode()], rules=[])
95
- ).replace(" ", "")
96
- return await self.create_wallet(
97
- "keys.auth.hot.tg", None, auth_to_add_msg, key_gen
98
- )
99
-
100
- async def create_wallet(
101
- self, auth_account_id: str, metadata: str = "", auth_to_add_msg="", key_gen=1
102
- ):
103
- """
104
- Create wallet with keys.auth.hot.tg auth method.
105
- :param public_key: Public key for auth future signs on keys.auth.hot.tg
106
- """
107
- wallet = await self.get_wallet()
108
- if wallet and wallet.access_list[0].account_id != "default.auth.hot.tg":
109
- raise ValueError(
110
- "MPCWallet already exists with different auth method, please use another wallet_id"
111
- )
112
- root_pk = self.derive_private_key(0)
113
- proof_hash = sha256(
114
- f"CREATE_WALLET:{self.wallet_id}:{auth_account_id}:{metadata}:{auth_to_add_msg}".encode(
115
- "utf-8"
116
- )
117
- ).digest()
118
- signature = base58.b58encode(root_pk.sign(proof_hash).signature).decode("utf-8")
119
-
120
- s = await self._client.post(
121
- f"{self.hot_rpc}/create_wallet",
122
- json=dict(
123
- wallet_id=self.wallet_id,
124
- key_gen=key_gen,
125
- signature=signature,
126
- wallet_derive_public_key=base58.b58encode(
127
- root_pk.verify_key.encode()
128
- ).decode(),
129
- auth={
130
- "auth_account_id": auth_account_id,
131
- "msg": auth_to_add_msg,
132
- "metadata": metadata or None,
133
- },
134
- ),
135
- timeout=30,
136
- )
137
- return s.json()
138
-
139
- async def get_ecdsa_public_key(self) -> bytes:
140
- resp = (
141
- await self._client.post(
142
- f"{self.hot_rpc}/public_key",
143
- json=dict(wallet_derive=base58.b58encode(self.derive).decode()),
144
- timeout=10,
145
- follow_redirects=True,
146
- )
147
- ).json()
148
- return bytes.fromhex(resp["ecdsa"])
149
-
150
- @property
151
- def public_key(self):
152
- # TODO Calculate with Rust Code
153
- raise NotImplementedError("Public key calculation is not implemented yet")
154
-
155
- async def sign_message(
156
- self,
157
- msg_hash: bytes,
158
- message_body: Optional[bytes] = None,
159
- curve_type: CurveType = CurveType.SECP256K1,
160
- auth_methods: List[AuthContract] = None,
161
- ):
162
- if not self.default_root_pk:
163
- raise ValueError("Default auth key is required")
164
- wallet = await self.get_wallet()
165
- user_payloads = []
166
- if len(auth_methods) != len(wallet.access_list):
167
- raise ValueError("Auth methods count should be equal to wallet access list")
168
-
169
- for auth_contract, auth_method in zip(auth_methods, wallet.access_list):
170
- user_payloads.append(auth_contract.generate_user_payload(msg_hash))
171
-
172
- proof = {
173
- "auth_id": 0,
174
- "curve_type": curve_type,
175
- "user_payloads": user_payloads,
176
- "message_body": message_body.hex() if message_body else "",
177
- }
178
-
179
- resp = await self._client.post(
180
- f"{self.hot_rpc}/sign_raw",
181
- json=dict(
182
- uid=self.derive.hex(),
183
- message=msg_hash.hex(),
184
- proof=proof,
185
- key_type=curve_type,
186
- ),
187
- timeout=10,
188
- follow_redirects=True,
189
- )
190
- resp = resp.json()
191
- if "Ecdsa" not in resp:
192
- raise ValueError(f"Invalid response from server: {resp}")
193
- resp = resp["Ecdsa"]
194
- r = int(resp["big_r"][2:], 16)
195
- s = int(resp["signature"], 16)
196
-
197
- pk = await self.get_ecdsa_public_key()
198
- for v in (0, 1):
199
- sig = Signature(vrs=(v, r, s))
200
- recovered = sig.recover_public_key_from_msg_hash(msg_hash)
201
- if recovered.to_compressed_bytes() == pk:
202
- return (
203
- r.to_bytes(32, "big") + s.to_bytes(32, "big") + bytes([v])
204
- ).hex()
205
-
206
- raise BadSignature("Cannot recover public key from signature")
207
-
208
- async def add_new_access_rule(
209
- self, access: WalletAccessModel, auth_contracts: List[AuthContract]
210
- ):
211
- wallet = await self.get_wallet()
212
- access_json = access.model_dump_json()
213
- message_body = f"ADD_AUTH_METHOD:{access_json}:{self.wallet_id}".encode("utf-8")
214
- msg_hash = sha256(message_body).digest()
215
- user_payloads = []
216
- for auth_contract, auth_method in zip(auth_contracts, wallet.access_list):
217
- auth_class = AUTH_CLASS[auth_method.account_id]
218
- if not isinstance(auth_contract, auth_class):
219
- raise ValueError(
220
- f"Auth method {auth_method.account_id} is not supported for this auth class"
221
- )
222
- user_payloads.append(auth_contract.generate_user_payload(msg_hash))
223
- return await self.near_account.function_call(
224
- _WALLET_REGISTER,
225
- "add_access_rule",
226
- {
227
- "wallet_id": self.wallet_id,
228
- "rule": access.model_dump(),
229
- "user_payloads": user_payloads,
230
- },
231
- included=True,
232
- )
233
-
234
- async def remove_access_rule(
235
- self, access_id: int, auth_contracts: List[AuthContract]
236
- ):
237
- wallet = await self.get_wallet()
238
- message_body = f"REMOVE_AUTH_METHOD:{access_id}:{self.wallet_id}".encode(
239
- "utf-8"
240
- )
241
- msg_hash = sha256(message_body).digest()
242
- user_payloads = []
243
- if len(auth_contracts) != len(wallet.access_list):
244
- raise ValueError("Auth methods count should be equal to wallet access list")
245
- for auth_contract, auth_method in zip(auth_contracts, wallet.access_list):
246
- auth_class = AUTH_CLASS[auth_method.account_id]
247
- if not isinstance(auth_contract, auth_class):
248
- raise ValueError(
249
- f"Auth method {auth_method.account_id} is not supported for this auth class"
250
- )
251
- user_payloads.append(auth_contract.generate_user_payload(msg_hash))
252
- return await self.near_account.function_call(
253
- _WALLET_REGISTER,
254
- "remove_access_rule",
255
- {
256
- "wallet_id": self.wallet_id,
257
- "rule_id": access_id,
258
- "user_payloads": user_payloads,
259
- },
260
- included=True,
261
- )