Skip to content

Commit

Permalink
[python] updates to sdk
Browse files Browse the repository at this point in the history
* Add submit and wait
* Improve table data retrieval code (aesthetics)
* Add support for view functions via bcs
* Demo transfer of non apt
  • Loading branch information
davidiw committed Mar 13, 2024
1 parent 2141444 commit dc2280c
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 16 deletions.
5 changes: 5 additions & 0 deletions ecosystem/python/sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

All notable changes to the Aptos Python SDK will be captured in this file. This changelog is written by hand for now.

## 0.8.1
- Improve TypeTag parsing for nested types
- Add BCS and String-based (JSON) view functions
- Added thorough documentation

## 0.8.0
- Add support for SingleKeyAuthenicatoin component of AIP-55
- Add support for Secp256k1 Ecdsa of AIP-49
Expand Down
84 changes: 75 additions & 9 deletions ecosystem/python/sdk/aptos_sdk/async_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
TransactionArgument,
TransactionPayload,
)
from .type_tag import StructTag, TypeTag

U64_MAX = 18446744073709551615

Expand Down Expand Up @@ -370,19 +371,14 @@ async def get_table_item(
key: Any,
ledger_version: Optional[int] = None,
) -> Any:
if not ledger_version:
request = f"{self.base_url}/tables/{handle}/item"
else:
request = (
f"{self.base_url}/tables/{handle}/item?ledger_version={ledger_version}"
)
response = await self.client.post(
request,
json={
response = await self._post(
endpoint=f"{self.base_url}/tables/{handle}/item",
data={
"key_type": key_type,
"value_type": value_type,
"key": key,
},
params={"ledger_version": ledger_version},
)
if response.status_code >= 400:
raise ApiError(response.text, response.status_code)
Expand Down Expand Up @@ -492,6 +488,13 @@ async def submit_bcs_transaction(
raise ApiError(response.text, response.status_code)
return response.json()["hash"]

async def submit_and_wait_for_bcs_transaction(
self, signed_transaction: SignedTransaction
) -> Dict[str, Any]:
txn_hash = await self.submit_bcs_transaction(signed_transaction)
await self.wait_for_transaction(txn_hash)
return await self.transaction_by_hash(txn_hash)

async def submit_transaction(self, sender: Account, payload: Dict[str, Any]) -> str:
"""
1) Generates a transaction request
Expand Down Expand Up @@ -761,6 +764,31 @@ async def bcs_transfer(
)
return await self.submit_bcs_transaction(signed_transaction) # <:!:bcs_transfer

async def transfer_coins(
self,
sender: Account,
recipient: AccountAddress,
coin_type: str,
amount: int,
sequence_number: Optional[int] = None,
) -> str:
transaction_arguments = [
TransactionArgument(recipient, Serializer.struct),
TransactionArgument(amount, Serializer.u64),
]

payload = EntryFunction.natural(
"0x1::aptos_account",
"transfer_coins",
[TypeTag(StructTag.from_str(coin_type))],
transaction_arguments,
)

signed_transaction = await self.create_bcs_signed_transaction(
sender, TransactionPayload(payload), sequence_number=sequence_number
)
return await self.submit_bcs_transaction(signed_transaction)

async def transfer_object(
self, owner: Account, object: AccountAddress, to: AccountAddress
) -> str:
Expand Down Expand Up @@ -821,6 +849,44 @@ async def view(

return response.content

async def view_bcs_payload(
self,
module: str,
function: str,
ty_args: List[TypeTag],
args: List[TransactionArgument],
ledger_version: Optional[int] = None,
) -> Dict[str, str]:
"""
Execute a view Move function with the given parameters and return its execution result.
Note, this differs from `view` as in this expects bcs compatible inputs and submits the
view function in bcs format. This is convenient for clients that execute functions in
transactions similar to view functions.
The Aptos nodes prune account state history, via a configurable time window. If the requested ledger version
has been pruned, the server responds with a 410.
:param function: Entry function id is string representation of an entry function defined on-chain.
:param type_arguments: Type arguments of the function.
:param arguments: Arguments of the function.
:param ledger_version: Ledger version to get state of account. If not provided, it will be the latest version.
:returns: Execution result.
"""
request = f"{self.base_url}/view"
if ledger_version:
request = f"{request}?ledger_version={ledger_version}"

view_data = EntryFunction.natural(module, function, ty_args, args)
ser = Serializer()
view_data.serialize(ser)
headers = {"Content-Type": "application/x.aptos.view_function+bcs"}
response = await self.client.post(
request, headers=headers, content=ser.output()
)
if response.status_code >= 400:
raise ApiError(response.text, response.status_code)
return response.json()

async def _post(
self,
endpoint: str,
Expand Down
50 changes: 46 additions & 4 deletions ecosystem/python/sdk/aptos_sdk/type_tag.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
from __future__ import annotations

import typing
from typing import List
import unittest
from typing import List, Tuple

from .account_address import AccountAddress
from .bcs import Deserializer, Serializer
Expand Down Expand Up @@ -308,19 +309,45 @@ def __str__(self) -> str:

@staticmethod
def from_str(type_tag: str) -> StructTag:
return StructTag._from_str_internal(type_tag, 0)[0][0]

@staticmethod
def _from_str_internal(type_tag: str, index: int) -> Tuple[List[StructTag], int]:
name = ""
index = 0
tags = []
inner_tags: List[StructTag] = []

while index < len(type_tag):
letter = type_tag[index]
index += 1

if letter == " ":
continue

if letter == "<":
raise NotImplementedError
(inner_tags, index) = StructTag._from_str_internal(type_tag, index)
elif letter == ",":
split = name.split("::")
tag = StructTag(
AccountAddress.from_str_relaxed(split[0]),
split[1],
split[2],
inner_tags,
)
tags.append(tag)
name = ""
inner_tags = []
elif letter == ">":
break
else:
name += letter

split = name.split("::")
return StructTag(AccountAddress.from_str(split[0]), split[1], split[2], [])
tag = StructTag(
AccountAddress.from_str_relaxed(split[0]), split[1], split[2], inner_tags
)
tags.append(tag)
return (tags, index)

def variant(self):
return TypeTag.STRUCT
Expand All @@ -338,3 +365,18 @@ def serialize(self, serializer: Serializer):
serializer.str(self.module)
serializer.str(self.name)
serializer.sequence(self.type_args, Serializer.struct)


class Test(unittest.TestCase):
def test_nested_structs(self):
l0 = "0x0::l0::L0"
l10 = "0x1::l10::L10"
l20 = "0x2::l20::L20"
l11 = "0x1::l11::L11"
composite = f"{l0}<{l10}<{l20}>, {l11}>"

self.assertEqual(composite, f"{StructTag.from_str(composite)}")


if __name__ == "__main__":
unittest.main()
4 changes: 2 additions & 2 deletions ecosystem/python/sdk/examples/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
import os

# :!:>section_1
NODE_URL = os.getenv("APTOS_NODE_URL", "https://fullnode.devnet.aptoslabs.com/v1")
NODE_URL = os.getenv("APTOS_NODE_URL", "https://fullnode.testnet.aptoslabs.com/v1")
FAUCET_URL = os.getenv(
"APTOS_FAUCET_URL",
"https://faucet.devnet.aptoslabs.com",
"https://faucet.testnet.aptoslabs.com",
) # <:!:section_1
14 changes: 14 additions & 0 deletions ecosystem/python/sdk/examples/your_coin.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,20 @@ async def main(moon_coin_path: str):
balance = await rest_client.get_balance(alice.address(), bob.address())
print(f"Bob's updated MoonCoin balance: {balance}")

try:
maybe_balance = await rest_client.get_balance(alice.address(), alice.address())
except Exception:
maybe_balance = None
print(f"Bob will transfer to Alice, her balance: {maybe_balance}")
txn_hash = await rest_client.transfer_coins(
bob, alice.address(), f"{alice.address()}::moon_coin::MoonCoin", 5
)
await rest_client.wait_for_transaction(txn_hash)
balance = await rest_client.get_balance(alice.address(), alice.address())
print(f"Alice's updated MoonCoin balance: {balance}")
balance = await rest_client.get_balance(alice.address(), bob.address())
print(f"Bob's updated MoonCoin balance: {balance}")


if __name__ == "__main__":
assert (
Expand Down
2 changes: 1 addition & 1 deletion ecosystem/python/sdk/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "aptos-sdk"
version = "0.7.1"
version = "0.8.1"
description = "Aptos SDK"
authors = ["Aptos Labs <[email protected]>"]
license = "Apache-2.0"
Expand Down

0 comments on commit dc2280c

Please sign in to comment.