py-near 1.1.49__py3-none-any.whl → 1.1.51__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.
py_near/account.py CHANGED
@@ -13,7 +13,7 @@ else:
13
13
  from collections import MutableSet
14
14
 
15
15
  import base58
16
- import ed25519
16
+ from nacl import signing, encoding
17
17
  from loguru import logger
18
18
  from py_near_primitives import DelegateAction
19
19
 
@@ -85,10 +85,8 @@ class Account(object):
85
85
  except UnicodeEncodeError:
86
86
  logger.error(f"Can't decode private key {pk[:10]}")
87
87
  continue
88
- private_key = ed25519.SigningKey(pk)
89
- public_key = base58.b58encode(
90
- private_key.get_verifying_key().to_bytes()
91
- ).decode("utf-8")
88
+ private_key = signing.SigningKey(pk[:32], encoder=encoding.RawEncoder)
89
+ public_key = private_key.verify_key
92
90
  self._signer_by_pk[public_key] = pk
93
91
  self._free_signers.put_nowait(pk)
94
92
  self._signers.append(pk)
@@ -206,11 +204,12 @@ class Account(object):
206
204
  if pk is None:
207
205
  pk = self._signers[0]
208
206
 
209
- private_key = ed25519.SigningKey(pk)
210
- public_key = private_key.get_verifying_key()
207
+ private_key = signing.SigningKey(pk[:32], encoder=encoding.RawEncoder)
208
+ public_key = private_key.verify_key
209
+ public_key.to_curve25519_public_key()
211
210
 
212
211
  resp = await self._provider.get_access_key(
213
- self.account_id, base58.b58encode(public_key.to_bytes()).decode("utf8")
212
+ self.account_id, base58.b58encode(public_key.encode()).decode("utf8")
214
213
  )
215
214
  if "error" in resp:
216
215
  raise ValueError(resp["error"])
@@ -449,8 +448,8 @@ class Account(object):
449
448
  access_key = await self.get_access_key(pk)
450
449
  await self._update_last_block_hash()
451
450
 
452
- private_key = ed25519.SigningKey(pk)
453
- verifying_key = private_key.get_verifying_key()
451
+ private_key = signing.SigningKey(pk[:32], encoder=encoding.RawEncoder)
452
+ verifying_key = private_key.verify_key
454
453
  return DelegateActionModel(
455
454
  sender_id=self.account_id,
456
455
  receiver_id=receiver_id,
@@ -479,7 +478,7 @@ class Account(object):
479
478
  if public_key not in self._signer_by_pk:
480
479
  raise ValueError(f"Public key {public_key} not found in signer list")
481
480
 
482
- private_key = ed25519.SigningKey(self._signer_by_pk[public_key])
481
+ private_key = signing.SigningKey(self._signer_by_pk[public_key], encoder=encoding.RawEncoder)
483
482
  sign = private_key.sign(nep461_hash)
484
483
  return base58.b58encode(sign).decode("utf-8")
485
484
 
py_near/models.py CHANGED
@@ -48,8 +48,10 @@ class ReceiptOutcome:
48
48
  tokens_burnt: str
49
49
  executor_id: str
50
50
  gas_burnt: int
51
+ receipt_id: str
51
52
 
52
53
  def __init__(self, data):
54
+ self.receipt_id = data['id']
53
55
  self.logs = data["outcome"]["logs"]
54
56
  self.metadata = data["outcome"]["metadata"]
55
57
  self.receipt_ids = data["outcome"]["receipt_ids"]
File without changes
File without changes
@@ -0,0 +1,97 @@
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
+ )
@@ -0,0 +1,23 @@
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")
File without changes
@@ -0,0 +1,29 @@
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
+
@@ -0,0 +1,83 @@
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 ADDED
@@ -0,0 +1,32 @@
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 ADDED
@@ -0,0 +1,237 @@
1
+ from hashlib import sha256
2
+ from typing import List, Optional
3
+
4
+ import base58
5
+ import httpx
6
+ from eth_keys.datatypes import Signature
7
+ from eth_keys.exceptions import BadSignature
8
+ from eth_utils import keccak
9
+ from loguru import logger
10
+ from nacl import encoding, signing
11
+
12
+ from py_near.account import Account
13
+ from py_near.mpc.auth.auth_2fa import AuthContract2FA
14
+ from py_near.mpc.auth.base import AuthContract
15
+ from py_near.mpc.auth.default_auth import DefaultAuthContract
16
+ from py_near.mpc.auth.keys_auth import KeysAuthContract
17
+ from py_near.mpc.models import WalletAccessModel, WalletModel, CurveType
18
+
19
+ _WALLET_REGISTER = "mpc.hot.tg"
20
+
21
+ AUTH_CLASS = {
22
+ "default.auth.hot.tg": DefaultAuthContract,
23
+ "keys.auth.hot.tg": KeysAuthContract,
24
+ "2fa.auth.hot.tg": AuthContract2FA,
25
+ }
26
+
27
+
28
+ class MPCWallet:
29
+ derive: bytes
30
+ near_account: Account
31
+ hot_rpc: str
32
+
33
+ def __init__(
34
+ self,
35
+ near_account: Account,
36
+ default_root_pk: Optional[bytes] = None,
37
+ hot_rpc: str = "https://rpc1.hotdao.ai",
38
+ derive: Optional[bytes] = None,
39
+ ):
40
+ """
41
+ :param near_account: Near account
42
+ :param derive: Derived key for wallet
43
+ :param hot_rpc: Hot RPC url
44
+ :param default_root_pk: Default auth key for HOT Protocol, if provided, derive = sha256(default_root_pk.public_key).digest()
45
+ """
46
+ self.near_account = near_account
47
+ if len(default_root_pk) != 32:
48
+ raise ValueError("Default root pk should be 32 bytes")
49
+ if default_root_pk:
50
+ self.default_root_pk = default_root_pk
51
+ private_key_obj = self.derive_private_key(0)
52
+ derive = sha256(private_key_obj.verify_key.encode()).digest()
53
+ elif derive is None:
54
+ logger.warning(
55
+ "Its recomended to use default_root_pk to generate derives, v0 wallets support will be deprecated"
56
+ )
57
+ raise ValueError("Derive is required")
58
+ self.derive = derive
59
+ self.hot_rpc = hot_rpc
60
+ self._client = httpx.AsyncClient()
61
+
62
+ def derive_private_key(self, gen=0):
63
+ private_key = self.default_root_pk
64
+ for _ in range(gen):
65
+ private_key = sha256(private_key).digest()
66
+ return signing.SigningKey(
67
+ private_key,
68
+ encoder=encoding.RawEncoder,
69
+ )
70
+
71
+ @property
72
+ def wallet_id(self):
73
+ return base58.b58encode(sha256(self.derive).digest()).decode("utf-8")
74
+
75
+ async def get_wallet(self):
76
+ wallet = await self.near_account.view_function(
77
+ _WALLET_REGISTER, "get_wallet", args={"wallet_id": self.wallet_id}
78
+ )
79
+ if wallet.result:
80
+ return WalletModel.build(wallet.result)
81
+
82
+ async def create_wallet_with_keys_auth(self, public_key: bytes, key_gen=1):
83
+ """
84
+ Create wallet with keys.auth.hot.tg auth method.
85
+ :param public_key: Public key for auth future signs on keys.auth.hot.tg
86
+ """
87
+ wallet = await self.get_wallet()
88
+ if wallet and wallet.access_list[0].account_id != "default.auth.hot.tg":
89
+ raise ValueError(
90
+ "MPCWallet already exists with different auth method, please use another wallet_id"
91
+ )
92
+ root_pk = self.derive_private_key(0)
93
+ proof_hash = sha256(f"CREATE_WALLET:{self.wallet_id}".encode("utf-8")).digest()
94
+ signature = base58.b58encode(root_pk.sign(proof_hash).signature).decode("utf-8")
95
+
96
+ s = await self._client.post(
97
+ f"{self.hot_rpc}/create_wallet",
98
+ json=dict(
99
+ public_key=base58.b58encode(public_key).decode(),
100
+ wallet_id=self.wallet_id,
101
+ key_gen=key_gen,
102
+ signature=signature,
103
+ wallet_derive_public_key=base58.b58encode(
104
+ root_pk.verify_key.encode()
105
+ ).decode(),
106
+ ),
107
+ timeout=30,
108
+ )
109
+ return s.json()
110
+
111
+ async def get_ecdsa_public_key(self) -> bytes:
112
+ resp = (
113
+ await self._client.post(
114
+ f"{self.hot_rpc}/public_key",
115
+ json=dict(wallet_derive=base58.b58encode(self.derive).decode()),
116
+ timeout=10,
117
+ follow_redirects=True,
118
+ )
119
+ ).json()
120
+ return bytes.fromhex(resp["ecdsa"])
121
+
122
+ @property
123
+ def public_key(self):
124
+ # TODO Calculate with Rust Code
125
+ raise NotImplementedError("Public key calculation is not implemented yet")
126
+
127
+ async def sign_message(
128
+ self,
129
+ message_body: bytes,
130
+ curve_type: CurveType = CurveType.SECP256K1,
131
+ auth_methods: List[AuthContract] = None,
132
+ ):
133
+ if not self.default_root_pk:
134
+ raise ValueError("Default auth key is required")
135
+ wallet = await self.get_wallet()
136
+ user_payloads = []
137
+ msg_hash = keccak(message_body)
138
+ if len(auth_methods) != len(wallet.access_list):
139
+ raise ValueError("Auth methods count should be equal to wallet access list")
140
+
141
+ for auth_contract, auth_method in zip(auth_methods, wallet.access_list):
142
+ auth_class = AUTH_CLASS[auth_method.account_id]
143
+ if not isinstance(auth_contract, auth_class):
144
+ raise ValueError(
145
+ f"Auth method {auth_method.account_id} is not supported for this auth class"
146
+ )
147
+ user_payloads.append(auth_contract.generate_user_payload(msg_hash))
148
+
149
+ proof = {
150
+ "auth_id": 0,
151
+ "curve_type": curve_type,
152
+ "user_payloads": user_payloads,
153
+ "message_body": message_body.hex(),
154
+ }
155
+
156
+ resp = (
157
+ await self._client.post(
158
+ f"{self.hot_rpc}/sign_raw",
159
+ json=dict(
160
+ uid=self.derive.hex(),
161
+ message=msg_hash.hex(),
162
+ proof=proof,
163
+ key_type=curve_type,
164
+ ),
165
+ timeout=10,
166
+ follow_redirects=True,
167
+ )
168
+ ).json()
169
+ resp = resp["Ecdsa"]
170
+ r = int(resp["big_r"][2:], 16)
171
+ s = int(resp["signature"], 16)
172
+
173
+ pk = await self.get_ecdsa_public_key()
174
+ for v in (0, 1):
175
+ sig = Signature(vrs=(v, r, s))
176
+ recovered = sig.recover_public_key_from_msg_hash(msg_hash)
177
+ if recovered.to_compressed_bytes() == pk:
178
+ return (
179
+ r.to_bytes(32, "big") + s.to_bytes(32, "big") + bytes([v])
180
+ ).hex()
181
+
182
+ raise BadSignature("Cannot recover public key from signature")
183
+
184
+ async def add_new_access_rule(
185
+ self, access: WalletAccessModel, auth_contracts: List[AuthContract]
186
+ ):
187
+ wallet = await self.get_wallet()
188
+ access_json = access.model_dump_json()
189
+ message_body = f"ADD_AUTH_METHOD:{access_json}:{self.wallet_id}".encode("utf-8")
190
+ msg_hash = sha256(message_body).digest()
191
+ user_payloads = []
192
+ for auth_contract, auth_method in zip(auth_contracts, wallet.access_list):
193
+ auth_class = AUTH_CLASS[auth_method.account_id]
194
+ if not isinstance(auth_contract, auth_class):
195
+ raise ValueError(
196
+ f"Auth method {auth_method.account_id} is not supported for this auth class"
197
+ )
198
+ user_payloads.append(auth_contract.generate_user_payload(msg_hash))
199
+ return await self.near_account.function_call(
200
+ _WALLET_REGISTER,
201
+ "add_access_rule",
202
+ {
203
+ "wallet_id": self.wallet_id,
204
+ "rule": access.model_dump(),
205
+ "user_payloads": user_payloads,
206
+ },
207
+ included=True,
208
+ )
209
+
210
+ async def remove_access_rule(
211
+ self, access_id: int, auth_contracts: List[AuthContract]
212
+ ):
213
+ wallet = await self.get_wallet()
214
+ message_body = f"REMOVE_AUTH_METHOD:{access_id}:{self.wallet_id}".encode(
215
+ "utf-8"
216
+ )
217
+ msg_hash = sha256(message_body).digest()
218
+ user_payloads = []
219
+ if len(auth_contracts) != len(wallet.access_list):
220
+ raise ValueError("Auth methods count should be equal to wallet access list")
221
+ for auth_contract, auth_method in zip(auth_contracts, wallet.access_list):
222
+ auth_class = AUTH_CLASS[auth_method.account_id]
223
+ if not isinstance(auth_contract, auth_class):
224
+ raise ValueError(
225
+ f"Auth method {auth_method.account_id} is not supported for this auth class"
226
+ )
227
+ user_payloads.append(auth_contract.generate_user_payload(msg_hash))
228
+ return await self.near_account.function_call(
229
+ _WALLET_REGISTER,
230
+ "remove_access_rule",
231
+ {
232
+ "wallet_id": self.wallet_id,
233
+ "rule_id": access_id,
234
+ "user_payloads": user_payloads,
235
+ },
236
+ included=True,
237
+ )
py_near/providers.py CHANGED
@@ -6,6 +6,7 @@ from collections import Counter
6
6
  from typing import Optional
7
7
 
8
8
  import httpx
9
+ from httpx import Limits
9
10
  from loguru import logger
10
11
 
11
12
  from py_near.constants import TIMEOUT_WAIT_RPC
@@ -60,29 +61,13 @@ class JsonProvider(object):
60
61
  self._last_rpc_addr_check = 0
61
62
  self.allow_broadcast = allow_broadcast
62
63
  self._timeout = timeout
63
- self._client: httpx.AsyncClient = httpx.AsyncClient()
64
+ self._client = httpx.AsyncClient(
65
+ limits=Limits(max_connections=1000, max_keepalive_connections=200)
66
+ )
64
67
 
65
68
  async def shutdown(self):
66
69
  pass
67
70
 
68
- async def check_available_rpcs(self):
69
- if (
70
- self._last_rpc_addr_check < datetime.datetime.now().timestamp() - 30
71
- or not self._available_rpcs
72
- ):
73
- self._last_rpc_addr_check = datetime.datetime.now().timestamp()
74
- asyncio.create_task(self._check_available_rpcs())
75
-
76
- for _ in range(5):
77
- if self._available_rpcs:
78
- break
79
- await self._check_available_rpcs()
80
- await asyncio.sleep(3)
81
-
82
- if not self._available_rpcs:
83
- self._available_rpcs = self._rpc_addresses.copy()
84
- logger.error("All RPCs are async, reset to default list")
85
-
86
71
  async def _check_available_rpcs(self):
87
72
  available_rpcs = []
88
73
  for rpc_addr in self._rpc_addresses:
@@ -132,7 +117,7 @@ class JsonProvider(object):
132
117
  except Exception as e:
133
118
  if rpc_addr in self._available_rpcs:
134
119
  logger.error(f"Remove rpc: {e}")
135
- logger.error(f"Rpc check error: {e}")
120
+ logger.exception(e)
136
121
  self._available_rpcs = available_rpcs
137
122
 
138
123
  @staticmethod
@@ -144,7 +129,6 @@ class JsonProvider(object):
144
129
  async def call_rpc_request(
145
130
  self, method, params, broadcast=False, threshold: int = 0
146
131
  ):
147
- await self.check_available_rpcs()
148
132
  j = {"method": method, "params": params, "id": "dontcare", "jsonrpc": "2.0"}
149
133
 
150
134
  async def f(rpc_call_addr):
@@ -236,8 +220,11 @@ class JsonProvider(object):
236
220
  break
237
221
  if not body:
238
222
  return error
239
- key, body = list(body.items())[0]
240
- if key in ERROR_CODE_TO_EXCEPTION:
223
+ if len(body) == 1 and list(body.keys())[0] in ERROR_CODE_TO_EXCEPTION:
224
+ key, body = list(body.items())[0]
225
+ if isinstance(body, str) and body in ERROR_CODE_TO_EXCEPTION:
226
+ key = body
227
+ body = {}
241
228
  error = ERROR_CODE_TO_EXCEPTION[key](
242
229
  body, error_json=content["error"]
243
230
  )
@@ -325,7 +312,6 @@ class JsonProvider(object):
325
312
  return await self.wait_for_trx(trx_hash, receiver_id)
326
313
 
327
314
  async def get_status(self):
328
- await self.check_available_rpcs()
329
315
  for rpc_addr in self._available_rpcs.copy():
330
316
  try:
331
317
  data = {
@@ -340,9 +326,7 @@ class JsonProvider(object):
340
326
  if "@" in rpc_addr:
341
327
  auth_key = rpc_addr.split("//")[1].split("@")[0]
342
328
  rpc_addr = rpc_addr.replace(auth_key + "@", "")
343
- headers = {
344
- "Authorization": f"Bearer {auth_key}"
345
- }
329
+ headers = {"Authorization": f"Bearer {auth_key}"}
346
330
  r = await self._client.post(rpc_addr, json=data, headers=headers)
347
331
  if r.status_code == 200:
348
332
  return json.loads(r.text)["result"]
@@ -462,4 +446,4 @@ class JsonProvider(object):
462
446
  return await self.json_rpc("light_client_proof", params)
463
447
 
464
448
  async def get_next_light_client_block(self, last_block_hash):
465
- return await self.json_rpc("next_light_client_block", [last_block_hash])
449
+ return await self.json_rpc("next_light_client_block", [last_block_hash])
py_near/transactions.py CHANGED
@@ -1,8 +1,7 @@
1
1
  import base64
2
2
  from typing import Union, List
3
-
3
+ from nacl import signing, encoding
4
4
  import base58
5
- import ed25519
6
5
  from py_near_primitives import (
7
6
  CreateAccountAction,
8
7
  AddKeyAction,
@@ -34,11 +33,11 @@ def sign_and_serialize_transaction(
34
33
  pk = base58.b58decode(private_key.replace("ed25519:", ""))
35
34
  else:
36
35
  pk = private_key
37
- private_key = ed25519.SigningKey(pk)
36
+ private_key = signing.SigningKey(pk[:32], encoder=encoding.RawEncoder)
38
37
 
39
38
  transaction = Transaction(
40
39
  account_id,
41
- private_key.get_verifying_key().to_bytes(),
40
+ private_key.verify_key.encode(),
42
41
  nonce,
43
42
  receiver_id,
44
43
  block_hash,
@@ -61,11 +60,11 @@ def calc_trx_hash(
61
60
  pk = base58.b58decode(private_key.replace("ed25519:", ""))
62
61
  else:
63
62
  pk = private_key
64
- private_key = ed25519.SigningKey(pk)
63
+ private_key = signing.SigningKey(pk[:32], encoder=encoding.RawEncoder)
65
64
 
66
65
  transaction = Transaction(
67
66
  account_id,
68
- private_key.get_verifying_key().to_bytes(),
67
+ private_key.verify_key.encode(),
69
68
  nonce,
70
69
  receiver_id,
71
70
  block_hash,
@@ -1,20 +1,24 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: py-near
3
- Version: 1.1.49
3
+ Version: 1.1.51
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
7
- Requires-Python: >=3.7,<4.0
7
+ Requires-Python: >=3.8,<3.12
8
8
  Classifier: Programming Language :: Python :: 3
9
- Classifier: Programming Language :: Python :: 3.7
10
9
  Classifier: Programming Language :: Python :: 3.8
11
10
  Classifier: Programming Language :: Python :: 3.9
12
11
  Classifier: Programming Language :: Python :: 3.10
13
12
  Classifier: Programming Language :: Python :: 3.11
14
- Classifier: Programming Language :: Python :: 3.12
15
- Requires-Dist: ed25519 (==1.5)
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
+ Requires-Dist: frozenlist (>=1.4.1,<2.0.0)
16
17
  Requires-Dist: httpx (==0.26.0)
18
+ Requires-Dist: loguru (>=0.7.2,<0.8.0)
17
19
  Requires-Dist: py-near-primitives (==0.2.3)
20
+ Requires-Dist: pydantic (>=2.6.2,<3.0.0)
21
+ Requires-Dist: pynacl (>=1.5.0,<2.0.0)
18
22
  Description-Content-Type: text/markdown
19
23
 
20
24
  # py-near
@@ -1,5 +1,5 @@
1
1
  py_near/__init__.py,sha256=t5fAxjaU8dN8xpQR2vz0ZGhfTkdVy2RCbkhJhZFglk4,50
2
- py_near/account.py,sha256=77jCtVaGXK_ejQ0SyRYRgJ4Ez1DaWByfAX94Uuph-aw,18107
2
+ py_near/account.py,sha256=ICLWOzRE7sfEU_ilLGPd0kyzhsMDmS1CWURFFCbk3Io,18200
3
3
  py_near/constants.py,sha256=inaWIuwmF1EB5JSB0ynnZY5rKY_QsxhF9KuCOhPsM6k,164
4
4
  py_near/dapps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  py_near/dapps/core.py,sha256=LtN9aW2gw2mvEdhzQcQJIidtjv-XL1xjb0LK8DzqtqE,231
@@ -19,11 +19,20 @@ py_near/dapps/staking/models.py,sha256=zC5M_pc1oMqHq4GaYif1uwFbW6acD2BsiA9rbyiaU
19
19
  py_near/exceptions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
20
  py_near/exceptions/exceptions.py,sha256=DEFipaAHm0y7oCuN2QKzHsiQvUTUQVl-Ce36Ag7n7hs,5509
21
21
  py_near/exceptions/provider.py,sha256=K-wexgjPJ8sw42JePwaP7R5dJEIn9DoFJRvVcURsx6s,7718
22
- py_near/models.py,sha256=GZQD1TKGWlwqsJsKRXrVNBjCdAIpk7GQypU-QOtAPFs,11533
23
- py_near/providers.py,sha256=1eg9MFIYIKA09bZyTdSZ2Umrh0kX1SUFEhY_tN8Cqz4,16722
24
- py_near/transactions.py,sha256=QAXegv2JpKISk92NaChtIH6-QPHrcWbrwdKH_lH4TsU,3186
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=Nihr4TR7MQURyI2wvoNIQ77Dx2_mR797RgyN91UHB8Q,8906
32
+ py_near/providers.py,sha256=X_GfddFzmafBatqFJH41Dp_lYvTNcz1Q3Ko2tEnN0VU,16193
33
+ py_near/transactions.py,sha256=QwEP3qOGxb8OB2vZA_rU20jvkPA0FmJowUKDxbuzOuI,3251
25
34
  py_near/utils.py,sha256=FirRH93ydH1cwjn0-sNrZeIn3BRD6QHedrP2VkAdJ6g,126
26
- py_near-1.1.49.dist-info/LICENSE,sha256=I_GOA9xJ35FiL-KnYXZJdATkbO2KcV2dK2enRGVxzKM,1023
27
- py_near-1.1.49.dist-info/METADATA,sha256=ERDv9zuuxRMfj4RzeF4Kvup_2QuTYueowDiP3tmc-EY,4693
28
- py_near-1.1.49.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
29
- py_near-1.1.49.dist-info/RECORD,,
35
+ py_near-1.1.51.dist-info/LICENSE,sha256=I_GOA9xJ35FiL-KnYXZJdATkbO2KcV2dK2enRGVxzKM,1023
36
+ py_near-1.1.51.dist-info/METADATA,sha256=0Tcfm023KquhJIWBY5JWhcBb_cyHQsmmIpv3eqloP6A,4846
37
+ py_near-1.1.51.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
38
+ py_near-1.1.51.dist-info/RECORD,,