-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
docs: initial documentation for beta release #128
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
814dd4f
docs: adding rewritten app client amount and account sections; respec…
aorumbayev b8d70d2
docs: initial rewrites for appmanager; clients and debugger docs
aorumbayev 30663b6
docs: adding migration guide, refining remaining capabilities; adding…
aorumbayev 2f63102
Merge remote-tracking branch 'origin/prerelease/ts-feature-parity' in…
aorumbayev 772f005
chore: addressing pr comments
aorumbayev 9d2e5f1
chore: update docs/source/capabilities/transfer.md
aorumbayev File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 |
---|---|---|
@@ -1,31 +1,195 @@ | ||
# Account management | ||
|
||
Account management is one of the core capabilities provided by AlgoKit Utils. It allows you to create mnemonic, idempotent KMD and environment variable injected accounts | ||
that can be used to sign transactions as well as representing a sender address at the same time. | ||
|
||
(account)= | ||
## `Account` | ||
|
||
Encapsulates a private key with convenience properties for `address`, `signer` and `public_key`. | ||
|
||
There are various methods of obtaining an `Account` instance | ||
|
||
* `get_account`: Returns an `Account` instance with the private key loaded by convention based on the given name identifier: | ||
* from an environment variable containing a mnemonic `{NAME}_MNEMONIC` OR | ||
* loading the account from KMD ny name if it exists (LocalNet only) OR | ||
* creating the account in KMD with associated name (LocalNet only) | ||
|
||
This allows you to have powerful code that will automatically create and fund an account by name locally and when deployed against | ||
TestNet/MainNet will automatically resolve from environment variables | ||
|
||
* `Account.new_account`: Returns a new `Account` using `algosdk.account.generate_account()` | ||
* `Account(private_key)`: Load an existing account from a private key | ||
* `Account(private_key, address)`: Load an existing account from a private key and address, useful for re-keyed accounts | ||
* `get_account_from_mnemonic`: Load an existing account from a mnemonic | ||
* `get_dispenser_account`: Gets a dispenser account that is funded by either: | ||
* Using the LocalNet default account (LocalNet only) OR | ||
* Loading an account from `DISPENSER_MNEMONIC` | ||
|
||
If working with a LocalNet instance, there are some additional functions that rely on a KMD service being exposed: | ||
* `create_kmd_wallet_account`, `get_kmd_wallet_account` or `get_or_create_kmd_wallet_account`: These functions allow retrieving a KMD wallet account by name, | ||
* `get_localnet_default_account`: Gets default localnet account that is funded with algos | ||
Account management is one of the core capabilities provided by AlgoKit Utils. It allows you to create mnemonic, rekeyed, multisig, transaction signer, idempotent KMD and environment variable injected accounts that can be used to sign transactions as well as representing a sender address at the same time. This significantly simplifies management of transaction signing. | ||
|
||
## `AccountManager` | ||
|
||
The `AccountManager` is a class that is used to get, create, and fund accounts and perform account-related actions such as funding. The `AccountManager` also keeps track of signers for each address so when using transaction composition to send transactions, a signer function does not need to manually be specified for each transaction - instead it can be inferred from the sender address automatically! | ||
|
||
To get an instance of `AccountManager`, you can either use the `AlgorandClient` via `algorand.account` or instantiate it directly: | ||
|
||
```python | ||
from algokit_utils import AccountManager | ||
|
||
account_manager = AccountManager(client_manager) | ||
``` | ||
|
||
## `Account` and Transaction Signing | ||
|
||
The core type that holds information about a signer/sender pair for a transaction in Python is the `Account` class, which represents both the signing capability and sender address in one object. This is different from the TypeScript implementation which uses `TransactionSignerAccount` interface that combines an `algosdk.TransactionSigner` with a sender address. | ||
|
||
The Python `Account` class provides: | ||
|
||
- `address` - The encoded string address | ||
- `private_key` - The private key for signing | ||
- `signer` - An `AccountTransactionSigner` that can sign transactions | ||
- `public_key` - The public key associated with this account | ||
|
||
## Registering a signer | ||
|
||
The `AccountManager` keeps track of which signer is associated with a given sender address. This is used by the transaction composition functionality to automatically sign transactions by that sender. Any of the [methods](#accounts) within `AccountManager` that return an account will automatically register the signer with the sender. | ||
|
||
There are two methods that can be used for this: | ||
|
||
```python | ||
# Register an account object that has both signer and sender | ||
account_manager.set_signer_from_account(account) | ||
|
||
# Register just a signer for a given sender address | ||
account_manager.set_signer("SENDER_ADDRESS", transaction_signer) | ||
``` | ||
|
||
## Default signer | ||
|
||
If you want to have a default signer that is used to sign transactions without a registered signer (rather than throwing an exception) then you can register a default signer: | ||
|
||
```python | ||
account_manager.set_default_signer(my_default_signer) | ||
``` | ||
|
||
## Get a signer | ||
|
||
The library will automatically retrieve a signer when signing a transaction, but if you need to get a `TransactionSigner` externally to do something more custom then you can retrieve the signer for a given sender address: | ||
|
||
```python | ||
signer = account_manager.get_signer("SENDER_ADDRESS") | ||
``` | ||
|
||
If there is no signer registered for that sender address it will either return the default signer (if registered) or raise an exception. | ||
|
||
## Accounts | ||
|
||
In order to get/register accounts for signing operations you can use the following methods on `AccountManager`: | ||
|
||
- `from_environment(name: str, fund_with: AlgoAmount | None = None) -> Account` - Registers and returns an account with private key loaded by convention based on the given name identifier - either by idempotently creating the account in KMD or from environment variable via `{NAME}_MNEMONIC` and (optionally) `{NAME}_SENDER` (if account is rekeyed) | ||
- This allows you to have powerful code that will automatically create and fund an account by name locally and when deployed against TestNet/MainNet will automatically resolve from environment variables, without having to have different code | ||
- Note: `fund_with` allows you to control how many Algo are seeded into an account created in KMD | ||
- `from_mnemonic(mnemonic_secret: str) -> Account` - Registers and returns an account with secret key loaded by taking the mnemonic secret | ||
- `multisig(version: int, threshold: int, addrs: list[str], signing_accounts: list[Account]) -> MultisigAccount` - Registers and returns a multisig account with one or more signing keys loaded | ||
- `rekeyed(sender: Account | str, account: Account) -> Account` - Registers and returns an account representing the given rekeyed sender/signer combination | ||
- `random() -> Account` - Returns a new, cryptographically randomly generated account with private key loaded | ||
- `from_kmd(name: str, predicate: Callable[[dict[str, Any]], bool] | None = None, sender: str | None = None) -> Account` - Returns an account with private key loaded from the given KMD wallet | ||
- `logic_sig(program: bytes, args: list[bytes] | None = None) -> LogicSigAccount` - Returns an account that represents a logic signature | ||
|
||
### Underlying account classes | ||
|
||
While `Account` is the main class used to represent an account that can sign, there are underlying account classes that can underpin the signer: | ||
|
||
- `Account` - The main account class that combines address and private key | ||
- `LogicSigAccount` - An in-built algosdk `LogicSigAccount` object for logic signature accounts | ||
- `MultisigAccount` - An abstraction around multisig accounts that supports multisig accounts with one or more signers present | ||
|
||
### Dispenser | ||
|
||
- `dispenser_from_environment() -> Account` - Returns an account (with private key loaded) that can act as a dispenser from environment variables, or against default LocalNet if no environment variables present | ||
- `localnet_dispenser() -> Account` - Returns an account with private key loaded that can act as a dispenser for the default LocalNet dispenser account | ||
|
||
## Rekey account | ||
|
||
One of the unique features of Algorand is the ability to change the private key that can authorise transactions for an account. This is called [rekeying](https://developer.algorand.org/docs/get-details/accounts/rekey/). | ||
|
||
```{warning} | ||
Rekeying should be done with caution as a rekey transaction can result in permanent loss of control of an account. | ||
``` | ||
|
||
You can issue a transaction to rekey an account by using the `rekey_account` method: | ||
|
||
```python | ||
account_manager.rekey_account( | ||
account="ACCOUNTADDRESS", # str | Account | ||
rekey_to="NEWADDRESS", # str | Account | ||
# Optional parameters | ||
signer=None, # TransactionSigner | ||
note=None, # bytes | ||
lease=None, # bytes | ||
static_fee=None, # AlgoAmount | ||
extra_fee=None, # AlgoAmount | ||
max_fee=None, # AlgoAmount | ||
validity_window=None, # int | ||
first_valid_round=None, # int | ||
last_valid_round=None, # int | ||
suppress_log=None # bool | ||
) | ||
``` | ||
|
||
You can also pass in `rekey_to` as a common transaction parameter to any transaction. | ||
|
||
### Examples | ||
|
||
```python | ||
# Basic example (with string addresses) | ||
account_manager.rekey_account(account="ACCOUNTADDRESS", rekey_to="NEWADDRESS") | ||
|
||
# Basic example (with signer accounts) | ||
account_manager.rekey_account(account=account1, rekey_to=new_signer_account) | ||
|
||
# Advanced example | ||
account_manager.rekey_account( | ||
account="ACCOUNTADDRESS", | ||
rekey_to="NEWADDRESS", | ||
lease="lease", | ||
note="note", | ||
first_valid_round=1000, | ||
validity_window=10, | ||
extra_fee=1000, # microAlgos | ||
static_fee=1000, # microAlgos | ||
max_fee=3000, # microAlgos | ||
max_rounds_to_wait_for_confirmation=5, | ||
suppress_log=True, | ||
) | ||
|
||
# Using a rekeyed account | ||
# Note: if a signing account is passed into account_manager.rekey_account then you don't need to call rekeyed_account to register the new signer | ||
rekeyed_account = account_manager.rekeyed(account, new_account) | ||
# rekeyed_account can be used to sign transactions on behalf of account... | ||
``` | ||
|
||
# KMD account management | ||
|
||
When running LocalNet, you have an instance of the [Key Management Daemon](https://github.com/algorand/go-algorand/blob/master/daemon/kmd/README.md), which is useful for: | ||
|
||
- Accessing the private key of the default accounts that are pre-seeded with Algo so that other accounts can be funded and it's possible to use LocalNet | ||
- Idempotently creating new accounts against a name that will stay intact while the LocalNet instance is running without you needing to store private keys anywhere (i.e. completely automated) | ||
|
||
The KMD SDK is fairly low level so to make use of it there is a fair bit of boilerplate code that's needed. This code has been abstracted away into the `KmdAccountManager` class. | ||
|
||
To get an instance of the `KmdAccountManager` class you can access it from `AccountManager` via `account_manager.kmd` or instantiate it directly (passing in a `ClientManager`): | ||
|
||
```python | ||
from algokit_utils import KmdAccountManager | ||
|
||
# Algod client only | ||
kmd_account_manager = KmdAccountManager(client_manager) | ||
``` | ||
|
||
The methods that are available are: | ||
|
||
- `get_wallet_account(wallet_name: str, predicate: Callable[[dict[str, Any]], bool] | None = None, sender: str | None = None) -> Account` - Returns an Algorand signing account with private key loaded from the given KMD wallet (identified by name). | ||
- `get_or_create_wallet_account(name: str, fund_with: AlgoAmount | None = None) -> Account` - Gets an account with private key loaded from a KMD wallet of the given name, or alternatively creates one with funds in it via a KMD wallet of the given name. | ||
- `get_localnet_dispenser_account() -> Account` - Returns an Algorand account with private key loaded for the default LocalNet dispenser account (that can be used to fund other accounts) | ||
|
||
```python | ||
# Get a wallet account that seeded the LocalNet network | ||
default_dispenser_account = kmd_account_manager.get_wallet_account( | ||
"unencrypted-default-wallet", | ||
lambda a: a.status != "Offline" and a.amount > 1_000_000_000, | ||
) | ||
# Same as above, but dedicated method call for convenience | ||
localnet_dispenser_account = kmd_account_manager.get_localnet_dispenser_account() | ||
# Idempotently get (if exists) or create (if it doesn't exist yet) an account by name using KMD | ||
# if creating it then fund it with 2 ALGO from the default dispenser account | ||
new_account = kmd_account_manager.get_or_create_wallet_account("account1", AlgoAmount.from_algo(2)) | ||
# This will return the same account as above since the name matches | ||
existing_account = kmd_account_manager.get_or_create_wallet_account("account1") | ||
``` | ||
|
||
Some of this functionality is directly exposed from `AccountManager`, which has the added benefit of registering the account as a signer so they can be automatically used to sign transactions: | ||
|
||
```python | ||
# Get and register LocalNet dispenser | ||
localnet_dispenser = account_manager.localnet_dispenser() | ||
# Get and register a dispenser by environment variable, or if not set then LocalNet dispenser via KMD | ||
dispenser = account_manager.dispenser_from_environment() | ||
# Get / create and register account from KMD idempotently by name | ||
account1 = account_manager.from_kmd("account1", AlgoAmount.from_algo(2)) | ||
``` |
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,85 @@ | ||
# Algorand client | ||
|
||
`AlgorandClient` is a client class that brokers easy access to Algorand functionality. It's the default entrypoint into AlgoKit Utils functionality. | ||
|
||
The main entrypoint to the bulk of the functionality in AlgoKit Utils is the `AlgorandClient` class. You can get started by using one of the static initialization methods to create an Algorand client: | ||
|
||
```python | ||
# Point to the network configured through environment variables or | ||
# if no environment variables it will point to the default LocalNet configuration | ||
algorand = AlgorandClient.from_environment() | ||
# Point to default LocalNet configuration | ||
algorand = AlgorandClient.default_localnet() | ||
# Point to TestNet using AlgoNode free tier | ||
algorand = AlgorandClient.testnet() | ||
# Point to MainNet using AlgoNode free tier | ||
algorand = AlgorandClient.mainnet() | ||
# Point to a pre-created algod client(s) | ||
algorand = AlgorandClient.from_clients( | ||
AlgoSdkClients( | ||
aorumbayev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
algod=..., | ||
indexer=..., | ||
kmd=..., | ||
) | ||
) | ||
# Point to custom configuration | ||
algorand = AlgorandClient.from_config( | ||
aorumbayev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
AlgoClientConfigs( | ||
algod_config=AlgoClientConfig( | ||
server="http://localhost:4001", token="my-token", port=4001 | ||
), | ||
indexer_config=None, | ||
kmd_config=None, | ||
) | ||
) | ||
``` | ||
|
||
## Accessing SDK clients | ||
|
||
Once you have an `AlgorandClient` instance, you can access the SDK clients for the various Algorand APIs via the `algorand.client` property. | ||
|
||
```python | ||
algorand = AlgorandClient.default_localnet() | ||
|
||
algod_client = algorand.client.algod | ||
indexer_client = algorand.client.indexer | ||
kmd_client = algorand.client.kmd | ||
``` | ||
|
||
## Accessing manager class instances | ||
|
||
The `AlgorandClient` has several manager class instances that help you quickly access advanced functionality: | ||
|
||
- `AccountManager` via `algorand.account`, with chainable convenience methods: | ||
- `algorand.set_default_signer(signer)` | ||
- `algorand.set_signer(sender, signer)` | ||
- `AssetManager` via `algorand.asset` | ||
- `ClientManager` via `algorand.client` | ||
- `AppManager` via `algorand.app` | ||
- `AppDeployer` via `algorand.app_deployer` | ||
|
||
## Creating and issuing transactions | ||
|
||
`AlgorandClient` exposes methods to create, execute, and compose groups of transactions via the `TransactionComposer`. | ||
|
||
### Transaction configuration | ||
|
||
AlgorandClient caches network provided transaction values automatically to reduce network traffic. You can configure this behavior: | ||
|
||
- `algorand.set_default_validity_window(validity_window)` - Set the default validity window (number of rounds the transaction will be valid). Defaults to 10. | ||
- `algorand.set_suggested_params(suggested_params, until?)` - Set the suggested network parameters to use (optionally until the given time) | ||
- `algorand.set_suggested_params_timeout(timeout)` - Set the timeout for caching suggested network parameters (default 3 seconds) | ||
- `algorand.get_suggested_params()` - Get current suggested network parameters | ||
|
||
### Creating transaction groups | ||
|
||
You can compose a group of transactions using the `new_group()` method which returns a `TransactionComposer` instance: | ||
|
||
```python | ||
result = ( | ||
algorand.new_group() | ||
.add_payment(sender="SENDERADDRESS", receiver="RECEIVERADDRESS", amount=1_000) | ||
.add_asset_opt_in(sender="SENDERADDRESS", asset_id=12345) | ||
.send() | ||
) | ||
``` |
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,69 @@ | ||
# Algo amount handling | ||
|
||
Algo amount handling is one of the core capabilities provided by AlgoKit Utils. It allows you to reliably and tersely specify amounts of microAlgo and Algo and safely convert between them. | ||
|
||
Any AlgoKit Utils function that needs an Algo amount will take an `AlgoAmount` object, which ensures that there is never any confusion about what value is being passed around. Whenever an AlgoKit Utils function calls into an underlying algosdk function, or if you need to take an `AlgoAmount` and pass it into an underlying algosdk function you can safely and explicitly convert to microAlgo or Algo. | ||
|
||
To see some usage examples check out the automated tests in the repository. Alternatively, you can refer to the reference documentation for `AlgoAmount`. | ||
|
||
## `AlgoAmount` | ||
|
||
The `AlgoAmount` class provides a safe wrapper around an underlying amount of microAlgo where any value entering or exiting the `AlgoAmount` class must be explicitly stated to be in microAlgo or Algo. This makes it much safer to handle Algo amounts rather than passing them around as raw numbers where it's easy to make a (potentially costly!) mistake and not perform a conversion when one is needed (or perform one when it shouldn't be!). | ||
|
||
To import the AlgoAmount class you can access it via: | ||
|
||
```python | ||
from algokit_utils.models import AlgoAmount | ||
``` | ||
|
||
### Creating an `AlgoAmount` | ||
|
||
There are several ways to create an `AlgoAmount`: | ||
|
||
- Algo | ||
- Constructor: `AlgoAmount({"algo": 10})` | ||
- Static helper: `AlgoAmount.from_algo(10)` | ||
- Static helper (plural): `AlgoAmount.from_algos(10)` | ||
- microAlgo | ||
- Constructor: `AlgoAmount({"microAlgo": 10_000})` | ||
- Static helper: `AlgoAmount.from_micro_algo(10_000)` | ||
- Static helper (plural): `AlgoAmount.from_micro_algos(10_000)` | ||
|
||
### Extracting a value from `AlgoAmount` | ||
|
||
The `AlgoAmount` class has properties to return Algo and microAlgo: | ||
|
||
- `amount.algo` or `amount.algos` - Returns the value in Algo | ||
- `amount.micro_algo` or `amount.micro_algos` - Returns the value in microAlgo | ||
|
||
`AlgoAmount` will coerce to an integer automatically (in microAlgo) when using `int(amount)`, which allows you to use `AlgoAmount` objects in comparison operations such as `<` and `>=` etc. | ||
|
||
You can also call `str(amount)` or use an `AlgoAmount` directly in string interpolation to convert it to a nice user-facing formatted amount expressed in microAlgo. | ||
|
||
### Additional Features | ||
|
||
The `AlgoAmount` class also supports: | ||
|
||
- Arithmetic operations (`+`, `-`) with other `AlgoAmount` objects or integers | ||
- Comparison operations (`<`, `<=`, `>`, `>=`, `==`, `!=`) | ||
- In-place arithmetic (`+=`, `-=`) | ||
|
||
Example usage: | ||
|
||
```python | ||
from algokit_utils.models import AlgoAmount | ||
|
||
# Create amounts | ||
amount1 = AlgoAmount.from_algo(1.5) # 1.5 Algos | ||
amount2 = AlgoAmount.from_micro_algos(500_000) # 0.5 Algos | ||
|
||
# Arithmetic | ||
total = amount1 + amount2 # 2 Algos | ||
difference = amount1 - amount2 # 1 Algo | ||
|
||
# Comparisons | ||
is_greater = amount1 > amount2 # True | ||
|
||
# String representation | ||
print(amount1) # "1,500,000 µALGO" | ||
``` |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering if we should mirror the TS utils here? Might be worth a chat.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed, will introduce a further overhaul to have TransactionSignerAccount introduced as a protocol, would require a bigger chunk of changes so will address in a separate PR as part of last batch of improvements in preparation for pypi release. Will keep the issue unresolved for now