Skip to content

Commit

Permalink
Merge pull request #2 from ackness/v0.0.2
Browse files Browse the repository at this point in the history
[v0.0.2] add more functions, changes some function name
  • Loading branch information
ackness authored Oct 29, 2022
2 parents ab02697 + 4b3172d commit 5486cd6
Show file tree
Hide file tree
Showing 11 changed files with 259 additions and 59 deletions.
72 changes: 61 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,56 +1,106 @@
# APTOS Client for Python
# APTC: APTOS Client for Python

![Version](https://img.shields.io/badge/aptc-v0.0.1-green)
![Version](https://img.shields.io/badge/aptc-v0.0.2-green)
![GitHub Org's stars](https://img.shields.io/github/stars/ackness/aptc?style=social)
![GitHub forks](https://img.shields.io/github/forks/ackness/aptc?style=social)
![Pypi](https://img.shields.io/pypi/dm/aptc)

---

A simple client for access the APTOS chain.
[WIP] An easier RESTful client for APTOS chain than [official python client](https://github.com/aptos-labs/aptos-core/blob/main/ecosystem/python/sdk/README.md).

---

## Installation

```bash
pip install aptc

# update
pip install -U aptc
```

## Usage

### Get information from blockchain.
### Create a client

```python
from aptc import APTClient, HttpxProvider, APTOS_NODE_URL_LIST
from aptc import new_client, APTOS_NODE_URL_LIST, APTClient, HttpxProvider

APT_NODE_URL = APTOS_NODE_URL_LIST[0]

# mainnet
client = new_client(node_url=APT_NODE_URL)
# or
client = APTClient(HttpxProvider(APT_NODE_URL))

# faucet client
client = new_client(faucet=True)
# claim apt from faucet
client.deposit('your address')

```

### Faucet client

The devnet faucet may sometimes not work. Some APIs are not supported by faucet.

```python
from aptc import new_client, Account

account = Account.generate()

print('account address:', account.address())
print('account private key:', account.private_key)

faucet_client = new_client(faucet=True)
txn_hash = faucet_client.deposit(account.address())
print(txn_hash)
```


### Get information from blockchain

more: [examples/example1.py](https://github.com/ackness/aptc/blob/main/examples/example1.py)


```python
from aptc import new_client, APTClient, HttpxProvider, APTOS_NODE_URL_LIST

client = new_client()

client.get_ledger_info()
client.check_health()

example_address = '0xc739507214d0e1bf9795485299d709e00024e92f7c0d055a4c2c39717882bdfd'
example_address = "0xc739507214d0e1bf9795485299d709e00024e92f7c0d055a4c2c39717882bdfd"
client.get_account(example_address)
client.get_account_balance(example_address)
client.get_account_resources(example_address)
client.get_account_transactions(example_address)

# for some nft mint, get resources is useful to get nft info
# here are one of bluemove nft mint info
some_address = "your address"
# some nft mint factory
some_resource_type = "0xf9bf19f5077c196e5468510e140d1e3cbfa0681f67fe245566ceab2399a6388d::factory::MintedByUser"
client.get_account_resource(some_address, some_resource_type)
```

### Send Transaction

Detail information about transaction, please refer to [examples/example2.py](https://github.com/ackness/aptc/blob/main/examples/example2.py)

```python
import os
import time
from aptc import APTClient, HttpxProvider, APTOS_NODE_URL_LIST, Account, APT
from aptc import Account, APT, new_client

APT_NODE_URL = APTOS_NODE_URL_LIST[0]
client = APTClient(HttpxProvider(APT_NODE_URL))

client = new_client()

# submit transaction
# load your private key
account = Account.load_key("")
# load your private key, environment variable
account = Account.load_key(os.environ['private_key'])
account_address = account.address()

# build a transaction payload
Expand Down
12 changes: 9 additions & 3 deletions aptc/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from .configs import DEV_APTOS_NODE_URL_LIST, DEV_FAUCET_URL_LIST, APTOS_NODE_URL_LIST
from .libs.const import *
from .libs import HttpxProvider, HttpxAsyncProvider, APTClient
from .libs import *
from .libs import APTClient, HttpxProvider
from .sdk_impl import *

__all__ = [
Expand All @@ -11,5 +11,11 @@
"APT_COIN_TYPE", "APT_COIN_STORE", "COIN_STORE_TYPE_TAG", "OCTA", "APT",
# SDK
'Account', 'AccountAddress', 'Serializer', 'Deserializer',
'PrivateKey', 'PublicKey', 'Signature'
'PrivateKey', 'PublicKey', 'Signature',
'new_client', 'apt_client', 'default_http_provider', 'default_node_url'
]

default_node_url = APTOS_NODE_URL_LIST[0]
default_http_provider = HttpxProvider(APTOS_NODE_URL_LIST[0])
apt_client = new_client(default_node_url)

7 changes: 6 additions & 1 deletion aptc/libs/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
from .client import APTClient
from .providers import HttpxProvider, HttpxAsyncProvider
from .const import *
from .utils import *

__all__ = [
"HttpxProvider", "HttpxAsyncProvider", "APTClient",
"APT_COIN_TYPE", "APT_COIN_STORE", "COIN_STORE_TYPE_TAG", "OCTA", "APT",
# const
"APT_COIN_TYPE", "APT_COIN_STORE", "COIN_STORE_TYPE_TAG",
"OCTA", "APT",
# utils
"new_client", "build_transfer_payload"
]
145 changes: 118 additions & 27 deletions aptc/libs/client.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import time
from typing import Dict

from httpx import Response
Expand All @@ -8,93 +9,145 @@
from .providers import BaseProvider


class APTClient:
class BaseClient:
def __init__(self, provider):
self.provider = provider


class APTClient(BaseClient):
BCS_SUBMIT_HEADERS = {'Content-Type': 'application/x.aptos.signed_transaction+bcs'}

def __init__(self, provider: BaseProvider):
super(APTClient, self).__init__(provider)
self.provider = provider
self.base_url = self.provider.base_url

# account
def get_account(self, address: Address):
def get_account(self, address: Address) -> dict:
return self.provider.get(APTAccountAPI.GET_ACCOUNT.format(address=address))

def get_account_resource(self, address: Address, resource_type: str):
account = get_account

def get_account_resource(self, address: Address, resource_type: str) -> dict:
return self.provider.get(
APTAccountAPI.GET_ACCOUNT_RESOURCE.format(address=address, resource_type=resource_type))

def get_account_resources(self, address: Address):
account_resource = get_account_resource

def get_account_resources(self, address: Address) -> dict:
return self.provider.get(APTAccountAPI.GET_ACCOUNT_RESOURCES.format(address=address))

def get_account_modules(self, address: Address):
account_resources = get_account_resources

def get_account_modules(self, address: Address) -> dict:
return self.provider.get(APTAccountAPI.GET_ACCOUNT_MODULES.format(address=address))

def get_account_module(self, address: Address, module_name: str):
account_modules = get_account_modules

def get_account_module(self, address: Address, module_name: str) -> dict:
return self.provider.get(APTAccountAPI.GET_ACCOUNT_MODULE.format(address=address, module_name=module_name))

account_module = get_account_module

# useful account related method
def get_account_balance(self, address: Address):
def get_account_balance(self, address: Address) -> int:
# return self.get_account_resource(address, APT_COIN_STORE)['data']['coin']['value']
return self.provider.get(APTAccountAPI.GET_ACCOUNT_BALANCE.format(address=address))['data']['coin']['value']
return int(
self.provider.get(APTAccountAPI.GET_ACCOUNT_BALANCE.format(address=address))['data']['coin']['value'])

account_balance = get_account_balance

def get_account_sequence_number(self, address: Address) -> int:
return int(self.get_account(address)['sequence_number'])

def get_account_sequence_number(self, address: Address):
return self.get_account(address)['sequence_number']
account_sequence_number = get_account_sequence_number
sequence_number = get_account_sequence_number

def get_account_nonce(self, address: Address):
return self.get_account_sequence_number(address)
def get_account_nonce(self, address: Address) -> int:
return int(self.get_account_sequence_number(address))

account_nonce = get_account_nonce

# block
def get_block_by_height(self, block_height: IntNumber):
def get_block_by_height(self, block_height: IntNumber) -> dict:
return self.provider.get(APTBlockAPI.GET_BLOCK_BY_HEIGHT.format(block_height=str(block_height)))

def get_block_by_version(self, version: IntNumber):
block_by_height = get_block_by_height

def get_block_by_version(self, version: IntNumber) -> dict:
return self.provider.get(APTBlockAPI.GET_BLOCK_BY_VERSION.format(version=str(version)))

block_by_version = get_block_by_version

# event
def get_event_by_creation_number(self, address: Address, creation_number: int):
def get_event_by_creation_number(self, address: Address, creation_number: int) -> dict:
return self.provider.get(
APTEventAPI.GET_EVENT_BY_CREATION_NUMBER.format(address=address, creation_number=str(creation_number)))

def get_event_by_event_handle(self, address: Address, event_handle: str):
event_by_creation_number = get_event_by_creation_number

def get_event_by_event_handle(self, address: Address, event_handle: str) -> dict:
return self.provider.get(
APTEventAPI.GET_EVENT_BY_EVENT_HANDLE.format(address=address, event_handle=event_handle))

event_by_event_handle = get_event_by_event_handle

# general
def show_openapi_explorer(self):
def show_openapi_explorer(self) -> dict:
url = self.provider.patch_url([self.provider.base_url, APTGeneralAPI.SHOW_OPENAPI_EXPLORER])
return self.provider.client.get(url)

def check_node_health(self):
def check_node_health(self) -> dict:
return self.provider.get(APTGeneralAPI.CHECK_NODE_HEALTH)

check_health = check_node_health

def get_ledger_info(self):
# some node may not support this api
return self.provider.get(APTGeneralAPI.GET_LEDGER_INFO)

ledger_info = get_ledger_info

# tables
def get_table_item(self, table_handle: str):
def get_table_item(self, table_handle: str) -> dict:
return self.provider.post(APTTablesAPI.GET_TABLE_ITEM.format(table_handle=table_handle))

table_item = get_table_item

# transactions
def get_transactions(self):
def get_transactions(self) -> dict:
return self.provider.get(APTTransactionsAPI.GET_TRANSACTIONS)

def get_transaction_by_hash(self, txn_hash: TXHash):
transactions = get_transactions

def get_transaction_by_hash(self, txn_hash: TXHash) -> dict:
return self.provider.get(APTTransactionsAPI.GET_TRANSACTION_BY_HASH.format(txn_hash=txn_hash))

get_txn_by_hash = get_transaction_by_hash

def get_transaction_by_version(self, txn_version: IntNumber):
def get_transaction_by_version(self, txn_version: IntNumber) -> dict:
return self.provider.get(APTTransactionsAPI.GET_TRANSACTION_BY_VERSION.format(txn_version=str(txn_version)))

def get_account_transactions(self, address):
transaction_by_version = get_transaction_by_version

def get_account_transactions(self, address) -> dict:
return self.provider.get(APTTransactionsAPI.GET_ACCOUNT_TRANSACTIONS.format(address=address))

account_transactions = get_account_transactions

def submit_transaction(self, txn_dict: Dict):
return self.provider.post(APTTransactionsAPI.SUBMIT_TRANSACTION, params_dict=txn_dict)
return self.provider.post(
APTTransactionsAPI.SUBMIT_TRANSACTION, params_dict=txn_dict,
headers=self.provider.DEFAULT_HEADERS
)

submit = submit_transaction

def submit_batch_transactions(self, batch_txn_dict: Dict):
return self.provider.post(APTTransactionsAPI.SUBMIT_BATCH_TRANSACTIONS, params_dict=batch_txn_dict)
return self.provider.post(
APTTransactionsAPI.SUBMIT_BATCH_TRANSACTIONS, params_dict=batch_txn_dict,
headers=self.provider.DEFAULT_HEADERS
)

submit_batch = submit_batch_transactions

Expand All @@ -108,15 +161,18 @@ def simulate_transaction(
f'?estimate_gas_unit_price={estimate_gas_unit_price}'
f'&estimate_max_gas_amount={estimate_max_gas_amount}'
f'&estimate_prioritized_gas_unit_price={estimate_prioritized_gas_unit_price}',
txn_dict
txn_dict,
)
return self._handle_simulate_error(j)

simulate = simulate_transaction

def encode_submission(self, txn_dict: Dict):
j = self.provider.post(APTTransactionsAPI.ENCODE_SUBMISSION, params_dict=txn_dict)
return bytes.fromhex(j[2:])
if "message" in j:
raise RPCRequestError(j["message"])
else:
return bytes.fromhex(j[2:])

encode = encode_submission

Expand All @@ -131,3 +187,38 @@ def _handle_simulate_error(j: Response):
return j
else:
raise RPCRequestError(f"RPC Error: {j['vm_status']}")

def transaction_pending(self, txn_hash: str) -> bool:
response = self.provider.client.get(f"{self.base_url}/transactions/by_hash/{txn_hash}")
# print(response)
if response.status_code == 404:
return True
if response.status_code >= 400:
raise RPCRequestError(response.text, response.status_code)
return response.json()["type"] == "pending_transaction"

def wait_for_transaction(self, txn_hash, timeout=10, iteration=1):
"""Waits up to 20 seconds for a transaction to move past pending state."""

count = 0
while self.transaction_pending(txn_hash):
assert count < timeout, f"transaction {txn_hash} timed out"
time.sleep(iteration)
count += iteration
response = self.provider.get(f"{self.base_url}/transactions/by_hash/{txn_hash}")
assert (
"success" in response.json() and response.json()["success"]
), f"{response.text} - {txn_hash}"


class APTDevClient(APTClient):
def __init__(self, provider: BaseProvider):
super(APTDevClient, self).__init__(provider)
self.provider = provider

def deposit(self, address: Address, amount: int = 10_000_000) -> str:
response = self.provider.client.post(f"{self.base_url}/mint?amount={amount}&address={address}")
if response.status_code >= 400:
raise RPCRequestError(response.text, response.status_code)
# for testnet it not return proper response, but still success
return response.json()[0]
Loading

0 comments on commit 5486cd6

Please sign in to comment.