-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[SDK] Initial commit copying from Aptos-core
- Loading branch information
1 parent
bcd6ac3
commit f256f1a
Showing
48 changed files
with
9,308 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
[flake8] | ||
max-line-length = 88 | ||
select = C,E,F,W,B,B9 | ||
ignore = E203, E501, W503 | ||
exclude = __init__.py |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# Aptos Python SDK Changelog | ||
|
||
All notable changes to the Aptos Python SDK will be captured in this file. This changelog is written by hand for now. | ||
|
||
## 0.8.0 | ||
- Add support for SingleKeyAuthenicatoin component of AIP-55 | ||
- Add support for Secp256k1 Ecdsa of AIP-49 | ||
- Add support for Sponsored transactions of AIP-39 and AIP-53 | ||
- Improved support for MultiEd25519 | ||
|
||
## 0.7.0 | ||
- **[Breaking Change]**: The `from_str` function on `AccountAddress` has been updated to conform to the strict parsing described by [AIP-40](https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-40.md). For the relaxed parsing behavior of this function prior to this change, use `AccountAddress.from_str_relaxed`. | ||
- **[Breaking Change]**: Rewrote the large package publisher to support large modules too | ||
- **[Breaking Change]**: Delete sync client | ||
- **[Breaking Change]**: Removed the `hex` function from `AccountAddress`. Instead of `addr.hex()` use `str(addr)`. | ||
- **[Breaking Change]**: The string representation of `AccountAddress` now conforms to [AIP-40](https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-40.md). | ||
- **[Breaking Change]**: `AccountAddress.from_hex` and `PrivateKey.from_hex` have been renamed to `from_str`. | ||
- Port remaining sync examples to async (hello-blockchain, multisig, your-coin) | ||
- Updated token client to use events to acquire minted tokens | ||
- Update many dependencies and set Python 3.8.1 as the minimum requirement | ||
- Add support for an experimental chunked uploader | ||
- Add experimental support for the Aptos CLI enabling local end-to-end testing, package building, and package integration tests | ||
|
||
## 0.6.4 | ||
- Change sync client library from httpX to requests due to latency concerns. | ||
|
||
## 0.6.2 | ||
- Added custom header "x-aptos-client" to both sync/async RestClient | ||
|
||
## 0.6.1 | ||
- Updated package manifest. | ||
|
||
## 0.6.0 | ||
- Add token client. | ||
- Add support for generating account addresses. | ||
- Add support for http2 | ||
- Add async client | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
# Contributing Guide | ||
## Publishing | ||
To publish the SDK, follow these steps. | ||
|
||
First, make sure you have updated the changelog and bumped the SDK version if necessary. | ||
|
||
Configure Poetry with the PyPi credentials: | ||
|
||
``` | ||
poetry config pypi-token.pypi <token> | ||
``` | ||
|
||
You can get the token from our credential management system, search for PyPi. | ||
|
||
Build and publish: | ||
``` | ||
poetry publish --build | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
# Copyright © Aptos Foundation | ||
# SPDX-License-Identifier: Apache-2.0 | ||
|
||
test: | ||
poetry run python -m unittest discover -s aptos_sdk/ -p '*.py' -t .. | ||
|
||
test-coverage: | ||
poetry run python -m coverage run -m unittest discover -s aptos_sdk/ -p '*.py' -t .. | ||
poetry run python -m coverage report | ||
|
||
fmt: | ||
find ./examples ./aptos_sdk . -type f -name "*.py" | xargs poetry run autoflake -i -r --remove-all-unused-imports --remove-unused-variables --ignore-init-module-imports | ||
poetry run isort aptos_sdk examples | ||
poetry run black aptos_sdk examples | ||
|
||
lint: | ||
poetry run mypy aptos_sdk examples | ||
poetry run flake8 aptos_sdk examples | ||
|
||
examples: | ||
poetry run python -m examples.aptos_token | ||
poetry run python -m examples.read_aggregator | ||
poetry run python -m examples.simple_nft | ||
poetry run python -m examples.simple_aptos_token | ||
poetry run python -m examples.simulate_transfer_coin | ||
poetry run python -m examples.transfer_coin | ||
poetry run python -m examples.transfer_two_by_two | ||
|
||
examples_cli: | ||
poetry run python -m unittest -b examples.integration_test | ||
|
||
.PHONY: examples fmt lint test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,89 @@ | ||
# aptos-python-sdk | ||
Aptos Python SDK | ||
# Aptos Python SDK | ||
[![Discord][discord-image]][discord-url] | ||
[![PyPI Package Version][pypi-image-version]][pypi-url] | ||
[![PyPI Package Downloads][pypi-image-downloads]][pypi-url] | ||
|
||
This provides basic functionalities to interact with [Aptos](https:/github.com/aptos-labs/aptos-core/). Get started [here](https://aptos.dev/guides/system-integrators-guide/#getting-started). | ||
|
||
Currently this is still in development and may not be suitable for production purposes. | ||
|
||
Note: The sync client is deprecated, please only start new projects using the async client. Feature contributions to the sync client will be rejected. | ||
|
||
## Requirements | ||
This SDK uses [Poetry](https://python-poetry.org/docs/#installation) for packaging and dependency management: | ||
|
||
``` | ||
curl -sSL https://install.python-poetry.org | python3 - | ||
poetry install | ||
``` | ||
|
||
## Unit testing | ||
```bash | ||
make test | ||
``` | ||
|
||
## E2E testing and Using the Aptos CLI | ||
|
||
* Download the [Aptos CLI](https://aptos.dev/tools/aptos-cli/install-cli/). | ||
* Set the environment variable `APTOS_CLI_PATH` to the full path of the CLI. | ||
* `make examples_cli` | ||
|
||
We of course allow you to do this a bit more manually by: | ||
|
||
First, run a local testnet (run this from the root of aptos-core): | ||
|
||
```bash | ||
cargo run -p aptos -- node run-local-testnet --force-restart --assume-yes | ||
``` | ||
|
||
Next, tell the end-to-end tests to talk to this locally running testnet: | ||
|
||
```bash | ||
export APTOS_NODE_URL="http://127.0.0.1:8080/v1" | ||
export APTOS_FAUCET_URL="http://127.0.0.1:8081" | ||
``` | ||
|
||
Finally run the tests: | ||
|
||
```bash | ||
make examples | ||
``` | ||
|
||
Note: These end-to-end tests are tested against a node built from the same commit as part of CI, not devnet. For examples tested against devnet, see `developer-docs-site/static/examples/python/` from the root of the repo. | ||
|
||
## Autoformatting | ||
```bash | ||
make fmt | ||
``` | ||
|
||
## Package Publishing | ||
|
||
* Download the [Aptos CLI](https://aptos.dev/tools/aptos-cli/install-cli/). | ||
* Set the environment variable `APTOS_CLI_PATH` to the full path of the CLI. | ||
* `poetry run python -m aptos_sdk.cli` and set the appropriate command-line parameters | ||
|
||
## Generating types | ||
The Python `openapi-python-client` tool cannot parse references. Therefore there are three options: | ||
|
||
- Use swagger-cli to dereference, gain a type explosion, and still have missing types | ||
- Live without missing types | ||
- Write a pure python implementation with no autogenerated code | ||
|
||
Currently the team is moving forward with pure python, but leaves the following notes for the curious: | ||
|
||
```bash | ||
npm install -g @apidevtools/swagger-cli | ||
swagger-cli bundle --dereference ../../../api/doc/v0/openapi.yaml -t yaml > openapi.yaml | ||
python3 -m openapi_python_client generate --path openapi.yaml | ||
mv aptos-dev-api-specification-client/aptos_dev_api_specification_client/ aptos_sdk/openapi | ||
``` | ||
|
||
## Semantic versioning | ||
This project follows [semver](https://semver.org/) as closely as possible | ||
|
||
[repo]: https://github.com/aptos-labs/aptos-core | ||
[pypi-image-version]: https://img.shields.io/pypi/v/aptos-sdk.svg | ||
[pypi-image-downloads]: https://img.shields.io/pypi/dm/aptos-sdk.svg | ||
[pypi-url]: https://pypi.org/project/aptos-sdk | ||
[discord-image]: https://img.shields.io/discord/945856774056083548?label=Discord&logo=discord&style=flat~~~~ | ||
[discord-url]: https://discord.gg/aptosnetwork |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
# Copyright © Aptos Foundation | ||
# SPDX-License-Identifier: Apache-2.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
# Copyright © Aptos Foundation | ||
# SPDX-License-Identifier: Apache-2.0 | ||
|
||
from __future__ import annotations | ||
|
||
import json | ||
import tempfile | ||
import unittest | ||
|
||
from . import asymmetric_crypto, asymmetric_crypto_wrapper, ed25519, secp256k1_ecdsa | ||
from .account_address import AccountAddress | ||
from .authenticator import AccountAuthenticator | ||
from .bcs import Serializer | ||
from .transactions import RawTransactionInternal | ||
|
||
|
||
class Account: | ||
"""Represents an account as well as the private, public key-pair for the Aptos blockchain.""" | ||
|
||
account_address: AccountAddress | ||
private_key: asymmetric_crypto.PrivateKey | ||
|
||
def __init__( | ||
self, account_address: AccountAddress, private_key: asymmetric_crypto.PrivateKey | ||
): | ||
self.account_address = account_address | ||
self.private_key = private_key | ||
|
||
def __eq__(self, other: object) -> bool: | ||
if not isinstance(other, Account): | ||
return NotImplemented | ||
return ( | ||
self.account_address == other.account_address | ||
and self.private_key == other.private_key | ||
) | ||
|
||
@staticmethod | ||
def generate() -> Account: | ||
private_key = ed25519.PrivateKey.random() | ||
account_address = AccountAddress.from_key(private_key.public_key()) | ||
return Account(account_address, private_key) | ||
|
||
@staticmethod | ||
def generate_secp256k1_ecdsa() -> Account: | ||
private_key = secp256k1_ecdsa.PrivateKey.random() | ||
public_key = asymmetric_crypto_wrapper.PublicKey(private_key.public_key()) | ||
account_address = AccountAddress.from_key(public_key) | ||
return Account(account_address, private_key) | ||
|
||
@staticmethod | ||
def load_key(key: str) -> Account: | ||
private_key = ed25519.PrivateKey.from_str(key) | ||
account_address = AccountAddress.from_key(private_key.public_key()) | ||
return Account(account_address, private_key) | ||
|
||
@staticmethod | ||
def load(path: str) -> Account: | ||
with open(path) as file: | ||
data = json.load(file) | ||
return Account( | ||
AccountAddress.from_str_relaxed(data["account_address"]), | ||
ed25519.PrivateKey.from_str(data["private_key"]), | ||
) | ||
|
||
def store(self, path: str): | ||
data = { | ||
"account_address": str(self.account_address), | ||
"private_key": str(self.private_key), | ||
} | ||
with open(path, "w") as file: | ||
json.dump(data, file) | ||
|
||
def address(self) -> AccountAddress: | ||
"""Returns the address associated with the given account""" | ||
|
||
return self.account_address | ||
|
||
def auth_key(self) -> str: | ||
"""Returns the auth_key for the associated account""" | ||
return str(AccountAddress.from_key(self.private_key.public_key())) | ||
|
||
def sign(self, data: bytes) -> asymmetric_crypto.Signature: | ||
return self.private_key.sign(data) | ||
|
||
def sign_simulated_transaction( | ||
self, transaction: RawTransactionInternal | ||
) -> AccountAuthenticator: | ||
return transaction.sign_simulated(self.private_key.public_key()) | ||
|
||
def sign_transaction( | ||
self, transaction: RawTransactionInternal | ||
) -> AccountAuthenticator: | ||
return transaction.sign(self.private_key) | ||
|
||
def public_key(self) -> asymmetric_crypto.PublicKey: | ||
"""Returns the public key for the associated account""" | ||
|
||
return self.private_key.public_key() | ||
|
||
|
||
class RotationProofChallenge: | ||
type_info_account_address: AccountAddress = AccountAddress.from_str("0x1") | ||
type_info_module_name: str = "account" | ||
type_info_struct_name: str = "RotationProofChallenge" | ||
sequence_number: int | ||
originator: AccountAddress | ||
current_auth_key: AccountAddress | ||
new_public_key: asymmetric_crypto.PublicKey | ||
|
||
def __init__( | ||
self, | ||
sequence_number: int, | ||
originator: AccountAddress, | ||
current_auth_key: AccountAddress, | ||
new_public_key: asymmetric_crypto.PublicKey, | ||
): | ||
self.sequence_number = sequence_number | ||
self.originator = originator | ||
self.current_auth_key = current_auth_key | ||
self.new_public_key = new_public_key | ||
|
||
def serialize(self, serializer: Serializer): | ||
self.type_info_account_address.serialize(serializer) | ||
serializer.str(self.type_info_module_name) | ||
serializer.str(self.type_info_struct_name) | ||
serializer.u64(self.sequence_number) | ||
self.originator.serialize(serializer) | ||
self.current_auth_key.serialize(serializer) | ||
serializer.struct(self.new_public_key) | ||
|
||
|
||
class Test(unittest.TestCase): | ||
def test_load_and_store(self): | ||
(file, path) = tempfile.mkstemp() | ||
start = Account.generate() | ||
start.store(path) | ||
load = Account.load(path) | ||
|
||
self.assertEqual(start, load) | ||
# Auth key and Account address should be the same at start | ||
self.assertEqual(str(start.address()), start.auth_key()) | ||
|
||
def test_key(self): | ||
message = b"test message" | ||
account = Account.generate() | ||
signature = account.sign(message) | ||
self.assertTrue(account.public_key().verify(message, signature)) | ||
|
||
def test_rotation_proof_challenge(self): | ||
# Create originating account from private key. | ||
originating_account = Account.load_key( | ||
"005120c5882b0d492b3d2dc60a8a4510ec2051825413878453137305ba2d644b" | ||
) | ||
# Create target account from private key. | ||
target_account = Account.load_key( | ||
"19d409c191b1787d5b832d780316b83f6ee219677fafbd4c0f69fee12fdcdcee" | ||
) | ||
# Construct rotation proof challenge. | ||
rotation_proof_challenge = RotationProofChallenge( | ||
sequence_number=1234, | ||
originator=originating_account.address(), | ||
current_auth_key=originating_account.address(), | ||
new_public_key=target_account.public_key(), | ||
) | ||
# Serialize transaction. | ||
serializer = Serializer() | ||
rotation_proof_challenge.serialize(serializer) | ||
rotation_proof_challenge_bcs = serializer.output().hex() | ||
# Compare against expected bytes. | ||
expected_bytes = ( | ||
"0000000000000000000000000000000000000000000000000000000000000001" | ||
"076163636f756e7416526f746174696f6e50726f6f664368616c6c656e6765d2" | ||
"0400000000000015b67a673979c7c5dfc8d9c9f94d02da35062a19dd9d218087" | ||
"bd9076589219c615b67a673979c7c5dfc8d9c9f94d02da35062a19dd9d218087" | ||
"bd9076589219c620a1f942a3c46e2a4cd9552c0f95d529f8e3b60bcd44408637" | ||
"9ace35e4458b9f22" | ||
) | ||
self.assertEqual(rotation_proof_challenge_bcs, expected_bytes) |
Oops, something went wrong.