py-near 1.1.50__tar.gz → 1.1.51__tar.gz
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-1.1.50 → py_near-1.1.51}/PKG-INFO +9 -5
- {py_near-1.1.50 → py_near-1.1.51}/pyproject.toml +22 -10
- {py_near-1.1.50 → py_near-1.1.51}/src/py_near/account.py +10 -11
- py_near-1.1.51/src/py_near/mpc/__init__.py +0 -0
- py_near-1.1.51/src/py_near/mpc/__pycache__/__init__.cpython-311.pyc +0 -0
- py_near-1.1.51/src/py_near/mpc/__pycache__/models.cpython-311.pyc +0 -0
- py_near-1.1.51/src/py_near/mpc/__pycache__/wallet.cpython-311.pyc +0 -0
- py_near-1.1.51/src/py_near/mpc/auth/__init__.py +0 -0
- py_near-1.1.51/src/py_near/mpc/auth/__pycache__/__init__.cpython-311.pyc +0 -0
- py_near-1.1.51/src/py_near/mpc/auth/__pycache__/auth_2fa.cpython-311.pyc +0 -0
- py_near-1.1.51/src/py_near/mpc/auth/__pycache__/base.cpython-311.pyc +0 -0
- py_near-1.1.51/src/py_near/mpc/auth/__pycache__/default_auth.cpython-311.pyc +0 -0
- py_near-1.1.51/src/py_near/mpc/auth/__pycache__/keys_auth.cpython-311.pyc +0 -0
- py_near-1.1.51/src/py_near/mpc/auth/auth_2fa.py +97 -0
- py_near-1.1.51/src/py_near/mpc/auth/base.py +23 -0
- py_near-1.1.51/src/py_near/mpc/auth/core.py +0 -0
- py_near-1.1.51/src/py_near/mpc/auth/default_auth.py +29 -0
- py_near-1.1.51/src/py_near/mpc/auth/keys_auth.py +83 -0
- py_near-1.1.51/src/py_near/mpc/models.py +32 -0
- py_near-1.1.51/src/py_near/mpc/wallet.py +237 -0
- {py_near-1.1.50 → py_near-1.1.51}/src/py_near/providers.py +6 -3
- {py_near-1.1.50 → py_near-1.1.51}/src/py_near/transactions.py +5 -6
- {py_near-1.1.50 → py_near-1.1.51}/LICENSE +0 -0
- {py_near-1.1.50 → py_near-1.1.51}/README.md +0 -0
- {py_near-1.1.50 → py_near-1.1.51}/src/py_near/__init__.py +0 -0
- {py_near-1.1.50 → py_near-1.1.51}/src/py_near/constants.py +0 -0
- {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/__init__.py +0 -0
- {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/__pycache__/__init__.cpython-311.pyc +0 -0
- {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/__pycache__/core.cpython-311.pyc +0 -0
- {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/__pycache__/fts.cpython-311.pyc +0 -0
- {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/core.py +0 -0
- {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/ft/__init__.py +0 -0
- {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/ft/__pycache__/__init__.cpython-311.pyc +0 -0
- {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/ft/__pycache__/async_client.cpython-311.pyc +0 -0
- {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/ft/__pycache__/exceptions.cpython-311.pyc +0 -0
- {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/ft/__pycache__/models.cpython-311.pyc +0 -0
- {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/ft/async_client.py +0 -0
- {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/ft/exceptions.py +0 -0
- {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/ft/models.py +0 -0
- {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/fts.py +0 -0
- {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/keypom/__init__.py +0 -0
- {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/keypom/async_client.py +0 -0
- {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/keypom/exceptions.py +0 -0
- {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/keypom/models.py +0 -0
- {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/staking/__init__.py +0 -0
- {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/staking/__pycache__/__init__.cpython-311.pyc +0 -0
- {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/staking/__pycache__/async_client.cpython-311.pyc +0 -0
- {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/staking/__pycache__/exceptions.cpython-311.pyc +0 -0
- {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/staking/__pycache__/models.cpython-311.pyc +0 -0
- {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/staking/async_client.py +0 -0
- {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/staking/exceptions.py +0 -0
- {py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/staking/models.py +0 -0
- {py_near-1.1.50 → py_near-1.1.51}/src/py_near/exceptions/__init__.py +0 -0
- {py_near-1.1.50 → py_near-1.1.51}/src/py_near/exceptions/__pycache__/__init__.cpython-311.pyc +0 -0
- {py_near-1.1.50 → py_near-1.1.51}/src/py_near/exceptions/__pycache__/exceptions.cpython-311.pyc +0 -0
- {py_near-1.1.50 → py_near-1.1.51}/src/py_near/exceptions/__pycache__/provider.cpython-311.pyc +0 -0
- {py_near-1.1.50 → py_near-1.1.51}/src/py_near/exceptions/exceptions.py +0 -0
- {py_near-1.1.50 → py_near-1.1.51}/src/py_near/exceptions/provider.py +0 -0
- {py_near-1.1.50 → py_near-1.1.51}/src/py_near/models.py +0 -0
- {py_near-1.1.50 → py_near-1.1.51}/src/py_near/utils.py +0 -0
@@ -1,20 +1,24 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: py-near
|
3
|
-
Version: 1.1.
|
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
|
+
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
|
-
|
15
|
-
Requires-Dist:
|
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,33 +1,38 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "py-near"
|
3
|
-
version = "1.1.
|
3
|
+
version = "1.1.51"
|
4
4
|
description="Pretty simple and fully asynchronous framework for working with NEAR blockchain"
|
5
5
|
authors = ["pvolnov <petr@herewallet.app>"]
|
6
6
|
readme = "README.md"
|
7
7
|
|
8
8
|
[tool.poetry.dependencies]
|
9
|
-
|
10
|
-
|
9
|
+
base58 = "^2.1.1"
|
10
|
+
python = ">=3.8,<3.12"
|
11
11
|
httpx = "0.26.0"
|
12
12
|
py-near-primitives = "0.2.3"
|
13
|
+
frozenlist = "^1.4.1"
|
14
|
+
pynacl = "^1.5.0"
|
15
|
+
loguru = "^0.7.2"
|
16
|
+
pydantic = "^2.6.2"
|
17
|
+
eth-utils = "^5.2.0"
|
18
|
+
eth-keys = "^0.6.1"
|
13
19
|
|
14
|
-
[tool.poetry.dev
|
20
|
+
[tool.poetry.group.dev.dependencies]
|
15
21
|
black = "^21.12b0"
|
16
22
|
mypy = "^0.930"
|
17
|
-
pytest = "
|
23
|
+
pytest = ">=7.0,<8.0"
|
18
24
|
flake8 = "^4.0.1"
|
19
25
|
Sphinx = "^4.3.2"
|
20
26
|
sphinx-rtd-theme = "^1.0.0"
|
21
|
-
|
27
|
+
pytest-asyncio = "^0.23.5"
|
22
28
|
|
23
29
|
[project]
|
24
30
|
name = "py-near"
|
25
31
|
version = "1.1.50"
|
26
|
-
description = "Pretty simple and fully asynchronous framework for working with NEAR
|
32
|
+
description = "Pretty simple and fully asynchronous framework for working with NEAR blockchain"
|
27
33
|
authors = [ {name = "pvolnov", email = "petr@herewallet.app"} ]
|
28
|
-
requires-python = ">=3.7"
|
29
34
|
license = {file = "LICENSE"}
|
30
|
-
|
35
|
+
|
31
36
|
keywords = ["python", "near", "async"]
|
32
37
|
classifiers = [
|
33
38
|
"Programming Language :: Python :: 3",
|
@@ -53,4 +58,11 @@ documentation = "https://py-near.readthedocs.io/en/latest"
|
|
53
58
|
repository = "https://github.com/pvolnov/py-near"
|
54
59
|
changelog = "https://github.com/pvolnov/py-near/blob/main/CHANGELOG.md"
|
55
60
|
funding = "https://opencollective.com/py-near"
|
56
|
-
twitter = "https://twitter.com/p_volnov"
|
61
|
+
twitter = "https://twitter.com/p_volnov"
|
62
|
+
|
63
|
+
[tool.pytest.ini_options]
|
64
|
+
addopts = "--capture=no --tb=native"
|
65
|
+
testpaths = [
|
66
|
+
"tests",
|
67
|
+
]
|
68
|
+
asyncio_mode = "auto"
|
@@ -13,7 +13,7 @@ else:
|
|
13
13
|
from collections import MutableSet
|
14
14
|
|
15
15
|
import base58
|
16
|
-
import
|
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 =
|
89
|
-
public_key =
|
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 =
|
210
|
-
public_key = private_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.
|
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 =
|
453
|
-
verifying_key = private_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 =
|
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
|
|
File without changes
|
Binary file
|
Binary file
|
Binary file
|
File without changes
|
Binary file
|
Binary file
|
Binary file
|
@@ -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
|
@@ -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
|
+
)
|
@@ -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
|
+
)
|
@@ -220,8 +220,11 @@ class JsonProvider(object):
|
|
220
220
|
break
|
221
221
|
if not body:
|
222
222
|
return error
|
223
|
-
|
224
|
-
|
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 = {}
|
225
228
|
error = ERROR_CODE_TO_EXCEPTION[key](
|
226
229
|
body, error_json=content["error"]
|
227
230
|
)
|
@@ -443,4 +446,4 @@ class JsonProvider(object):
|
|
443
446
|
return await self.json_rpc("light_client_proof", params)
|
444
447
|
|
445
448
|
async def get_next_light_client_block(self, last_block_hash):
|
446
|
-
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])
|
@@ -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 =
|
36
|
+
private_key = signing.SigningKey(pk[:32], encoder=encoding.RawEncoder)
|
38
37
|
|
39
38
|
transaction = Transaction(
|
40
39
|
account_id,
|
41
|
-
private_key.
|
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 =
|
63
|
+
private_key = signing.SigningKey(pk[:32], encoder=encoding.RawEncoder)
|
65
64
|
|
66
65
|
transaction = Transaction(
|
67
66
|
account_id,
|
68
|
-
private_key.
|
67
|
+
private_key.verify_key.encode(),
|
69
68
|
nonce,
|
70
69
|
receiver_id,
|
71
70
|
block_hash,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/ft/__pycache__/async_client.cpython-311.pyc
RENAMED
File without changes
|
{py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/ft/__pycache__/exceptions.cpython-311.pyc
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/staking/__pycache__/__init__.cpython-311.pyc
RENAMED
File without changes
|
{py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/staking/__pycache__/async_client.cpython-311.pyc
RENAMED
File without changes
|
{py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/staking/__pycache__/exceptions.cpython-311.pyc
RENAMED
File without changes
|
{py_near-1.1.50 → py_near-1.1.51}/src/py_near/dapps/staking/__pycache__/models.cpython-311.pyc
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{py_near-1.1.50 → py_near-1.1.51}/src/py_near/exceptions/__pycache__/__init__.cpython-311.pyc
RENAMED
File without changes
|
{py_near-1.1.50 → py_near-1.1.51}/src/py_near/exceptions/__pycache__/exceptions.cpython-311.pyc
RENAMED
File without changes
|
{py_near-1.1.50 → py_near-1.1.51}/src/py_near/exceptions/__pycache__/provider.cpython-311.pyc
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|