Skip to content

Commit

Permalink
Feat/staking (#6)
Browse files Browse the repository at this point in the history
* feat: staking storage support

* feat: staking storage support

* feat: staking storage support

* feat: staking storage support

* feat: docs
  • Loading branch information
pvolnov authored Dec 15, 2022
1 parent 2b1a694 commit 399ee7e
Show file tree
Hide file tree
Showing 16 changed files with 570 additions and 26 deletions.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
venv
async_near.egg-info
build
dist
.idea
tests
tests
py_near.egg-info
169 changes: 169 additions & 0 deletions docs/clients/staking.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@

Staking
======================

.. note::
Class to stake NEAR on liquid staking. You can stake NEAR and withdraw at any time without fees

Read more about staking: https://docs.herewallet.app/technology-description/readme


Quick start
-----------

.. code:: python
from pynear.account import Account
from pynear.dapps.fts import FTS
import asyncio
ACCOUNT_ID = "mydev.near"
PRIVATE_KEY = "ed25519:..."
async def main():
acc = Account(ACCOUNT_ID, PRIVATE_KEY)
await acc.startup()
transaction = await acc.staking.stake(10000)
print(tr.transaction.hash)
transaction = await acc.staking.receive_dividends()
print(tr.logs)
transaction = await acc.staking.unstake(10000)
print(tr.transaction.hash)
asyncio.run(main())
Documentation
-------------

.. class:: Staking(DappClient)

Client to `storage.herewallet.near contract`
With this contract you can stake NEAR without blocking and receive passive income ~9% APY

.. code:: python
acc = Account(...)
staking = acc.staking
.. class:: StakingData(DappClient)

.. py:attribute:: apy_value
:type: int

current APY value * 100 (908=9.08%)

.. py:attribute:: last_accrual_ts
:type: int

Last UTC timestamp of accrued recalc

.. py:attribute:: accrued
:type: int

Total accrued in yoctoNEAR, which can be receiver by `receive_dividends()` call


.. function:: transfer(account_id: str, amount: int, memo: str = "", force_register: bool = False)

Transfer hNEAR to account

:param receiver_id: receiver account id
:param amount: amount in yoctoNEAR
:param memo: comment
:param nowait if True, method will return before transaction is confirmed
:return: transaction hash ot TransactionResult

.. code:: python
await acc.staking.transfer("azbang.near", 10000)
.. function:: transfer_call(account_id: str, amount: int, memo: str = "", force_register: bool = False)

Transfer hNEAR to account and call on_transfer_call() on receiver smart contract

:param receiver_id: receiver account id
:param amount: amount in yoctoNEAR
:param memo: comment
:param nowait if True, method will return before transaction is confirmed
:return: transaction hash ot TransactionResult

.. code:: python
await acc.staking.transfer_call("azbang.near", 10000)
.. function:: get_staking_amount(account_id: str)

Get staking balance of account.

:param account_id: account id
:param nowait if True, method will return before transaction is confirmed
:return: int balance in yoctoNEAR

.. code:: python
amount = await acc.staking.get_staking_amount("azbang.near")
print(amount)
.. function:: get_user(account_id: str)

Get user staking parameters

:param account_id: account id
:return: StakingData

.. code:: python
data = await acc.staking.get_user("azbang.near")
print(data.apy_value / 100)
.. function:: stake(amount: int, nowait: bool = False)

Deposit staking for account

:param amount: in amount of yoctoNEAR
:param nowait: if True, method will return before transaction is confirmed
:return: transaction hash or TransactionResult

.. code:: python
res = await acc.staking.stake(1_000_000_000_000_000)
print(res.transaction.hash)
.. function:: unstake(amount: int, nowait: bool = False)

Withdraw from staking

:param amount: in amount of yoctoNEAR
:param nowait: if True, method will return before transaction is confirmed
:return: transaction hash or TransactionResult

.. code:: python
res = await acc.staking.unstake(1_000_000_000_000_000)
print(res.transaction.hash)
.. function:: receive_dividends(nowait: bool = False)

Receive dividends. user.accrued yoctoNEAR amount will transfer to staking balance

:param nowait: if True, method will return before transaction is confirmed
:return: transaction hash ot TransactionResult

.. code:: python
res = await acc.staking.receive_dividends()
print(res.transaction.hash)
5 changes: 4 additions & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@ Welcome to py-near's documentation!

.. toctree::
:glob:
:titlesonly:
:maxdepth: 2

quickstart.rst
account.rst

clients/phone.rst
clients/fungible-token.rst
clients/staking.rst

.. include:: quickstart.rst
.. include:: account.rst
.. include:: clients/phone.rst
.. include:: clients/fungible-token.rst
.. include:: clients/staking.rst

6 changes: 4 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
[tool.poetry]
name = "py-near"
version = "0.1.4"
version = "1.0.5"
description="Pretty simple and fully asynchronous framework for working with NEAR blockchain"
authors = ["pvolnov <[email protected]>"]
readme = "README.md"

[tool.poetry.dependencies]
python = "^3.7"
Expand All @@ -21,7 +22,7 @@ base58 = "^2.1.1"

[project]
name = "py-near"
version = "0.1.4"
version = "1.0.5"
description = "Pretty simple and fully asynchronous framework for working with NEAR blockchaink"
authors = [ {name = "pvolnov", email = "[email protected]"} ]
requires-python = ">=3.7"
Expand All @@ -33,6 +34,7 @@ classifiers = [
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]
readme = "README.md"

[tool.setuptools.packages.find]
namespaces = true
Expand Down
63 changes: 43 additions & 20 deletions src/pynear/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from pynear import constants
from pynear.dapps.ft.async_client import FT
from pynear.dapps.phone.async_client import Phone
from pynear.dapps.staking.async_client import Staking
from pynear.exceptions.exceptions import (
AccountAlreadyExistsError,
AccountDoesNotExistError,
Expand Down Expand Up @@ -64,24 +65,29 @@ class Account(object):
"""

_access_key: dict
_lock: asyncio.Lock
_lock: asyncio.Lock = None
_latest_block_hash: str
_latest_block_hash_ts: float = 0
chain_id: str = "mainnet"

def __init__(
self, account_id, private_key, rpc_addr=constants.RPC_MAINNET
self,
account_id: str = None,
private_key: str = None,
rpc_addr="https://rpc.mainnet.near.org",
):
if isinstance(private_key, str):
private_key = base58.b58decode(private_key.replace("ed25519:", ""))
self._provider = JsonProvider(rpc_addr)
if private_key is None:
return

if isinstance(private_key, str):
private_key = base58.b58decode(private_key.replace("ed25519:", ""))
key = ED25519SecretKey(private_key)
self._signer = InMemorySigner(
AccountId(account_id),
ED25519SecretKey(private_key).public_key(),
ED25519SecretKey(private_key),
key.public_key(),
key,
)
self._account_id = account_id

async def startup(self):
"""
Expand Down Expand Up @@ -114,6 +120,10 @@ async def _sign_and_submit_tx(
confirm and return TransactionResult
:return: transaction hash or TransactionResult
"""
if self._signer is None:
raise ValueError("You must provide a private key or seed to call methods")
if self._lock is None:
await self.startup()
async with self._lock:
access_key = await self.get_access_key()
await self._update_last_block_hash()
Expand All @@ -139,8 +149,8 @@ async def _sign_and_submit_tx(
return TransactionResult(**result)

@property
def account_id(self) -> AccountId:
return self._account_id
def account_id(self) -> str:
return str(self._signer.account_id)

@property
def signer(self) -> InMemorySigner:
Expand All @@ -155,11 +165,16 @@ async def get_access_key(self) -> AccountAccessKey:
Get access key for current account
:return: AccountAccessKey
"""
return AccountAccessKey(
**await self._provider.get_access_key(
self._account_id, str(self._signer.public_key)
if self._signer is None:
raise ValueError(
"Signer is not initialized, use Account(account_id, private_key)"
)
resp = await self._provider.get_access_key(
self.account_id, str(self._signer.public_key)
)
if "error" in resp:
raise ValueError(resp["error"])
return AccountAccessKey(**resp)

async def get_access_key_list(self, account_id: str = None) -> List[PublicKey]:
"""
Expand All @@ -168,7 +183,7 @@ async def get_access_key_list(self, account_id: str = None) -> List[PublicKey]:
:return: list of PublicKey
"""
if account_id is None:
account_id = self._account_id
account_id = self.account_id
resp = await self._provider.get_access_key_list(account_id)
result = []
if "keys" in resp and isinstance(resp["keys"], list):
Expand All @@ -178,7 +193,7 @@ async def get_access_key_list(self, account_id: str = None) -> List[PublicKey]:

async def fetch_state(self) -> dict:
"""Fetch state for given account."""
return await self._provider.get_account(self._account_id)
return await self._provider.get_account(self.account_id)

async def send_money(self, account_id: str, amount: int, nowait: bool = False) -> TransactionResult:
"""
Expand Down Expand Up @@ -265,7 +280,7 @@ async def add_public_key(
public_key, allowance, receiver_id, method_names
),
]
return await self._sign_and_submit_tx(self._account_id, actions, nowait)
return await self._sign_and_submit_tx(self.account_id, actions, nowait)

async def add_full_access_public_key(
self, public_key: Union[str, bytes], nowait=False
Expand All @@ -279,7 +294,7 @@ async def add_full_access_public_key(
actions = [
transactions.create_full_access_key_action(public_key),
]
return await self._sign_and_submit_tx(self._account_id, actions, nowait)
return await self._sign_and_submit_tx(self.account_id, actions, nowait)

async def delete_public_key(self, public_key: Union[str, bytes], nowait=False):
"""
Expand All @@ -291,7 +306,7 @@ async def delete_public_key(self, public_key: Union[str, bytes], nowait=False):
actions = [
transactions.create_delete_access_key_action(public_key),
]
return await self._sign_and_submit_tx(self._account_id, actions, nowait)
return await self._sign_and_submit_tx(self.account_id, actions, nowait)

async def deploy_contract(self, contract_code: bytes, nowait=False):
"""
Expand All @@ -301,7 +316,7 @@ async def deploy_contract(self, contract_code: bytes, nowait=False):
:return: transaction hash or TransactionResult
"""
return await self._sign_and_submit_tx(
self._account_id,
self.account_id,
[transactions.create_deploy_contract_action(contract_code)],
nowait,
)
Expand All @@ -315,7 +330,7 @@ async def stake(self, public_key: str, amount: str, nowait=False):
:return: transaction hash or TransactionResult
"""
return await self._sign_and_submit_tx(
self._account_id,
self.account_id,
[transactions.create_staking_action(public_key, amount)],
nowait,
)
Expand Down Expand Up @@ -345,7 +360,7 @@ async def get_balance(self, account_id: str = None) -> int:
:return: balance of account in yoctoNEAR
"""
if account_id is None:
account_id = self._account_id
account_id = self.account_id
return int((await self._provider.get_account(account_id))["amount"])

@property
Expand All @@ -363,3 +378,11 @@ def ft(self):
:return: FT(self)
"""
return FT(self)

@property
def staking(self):
"""
Get client for staking
:return: Staking(self)
"""
return Staking(self)
Loading

0 comments on commit 399ee7e

Please sign in to comment.