Skip to content

Latest commit

 

History

History
188 lines (133 loc) · 13.7 KB

account.md

File metadata and controls

188 lines (133 loc) · 13.7 KB

Account management

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 the TransactionComposer 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 use either AlgorandClient via algorand.account or instantiate it directly:

import { AccountManager } from '@algorandfoundation/algokit-utils/types/account-manager'

const accountManager = new AccountManager(clientManager)

TransactionSignerAccount

The core internal type that holds information about a signer/sender pair for a transaction is TransactionSignerAccount, which represents an algosdk.TransactionSigner (signer) along with a sender address (addr) as the encoded string address.

Many methods in AccountManager expose a TransactionSignerAccount. TransactionSignerAccount can be used with AtomicTransactionComposer, TransactionComposer and useWallet.

Registering a signer

The AccountManager keeps track of which signer is associated with a given sender address. This is used by AlgorandClient to automatically sign transactions by that sender. Any of the methods within AccountManager that return an account will automatically register the signer with the sender. If however, you are creating a signer external to the AccountManager, for instance when using the use-wallet library in a dApp, then you need to register the signer with the AccountManager if you want it to be able to automatically sign transactions from that sender.

There are two methods that can be used for this, setSignerFromAccount, which takes any number of account based objects that combine signer and sender (TransactionSignerAccount | algosdk.Account | algosdk.LogicSigAccount | SigningAccount | MultisigAccount), or setSigner which takes the sender address and the TransactionSigner:

algorand.account
  .setSignerFromAccount(algosdk.generateAccount())
  .setSignerFromAccount(new algosdk.LogicSigAccount(program, args))
  .setSignerFromAccount(new SigningAccount(mnemonic, sender))
  .setSignerFromAccount(new MultisigAccount({ version: 1, threshold: 1, addrs: ['ADDRESS1...', 'ADDRESS2...'] }, [account1, account2]))
  .setSignerFromAccount({ addr: 'SENDERADDRESS', signer: transactionSigner })
  .setSigner('SENDERADDRESS', transactionSigner)

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:

algorand.account.setDefaultSigner(myDefaultSigner)

Get a signer

AlgorandClient 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:

const signer = algorand.account.getSigner('SENDER_ADDRESS')

If there is no signer registered for that sender address it will either return the default signer (if registered) or throw an exception.

Accounts

In order to get/register accounts for signing operations you can use the following methods on AccountManager (expressed here as algorand.account to denote the syntax via an AlgorandClient):

Underlying account classes

While TransactionSignerAccount is the main class used to represent an account that can sign, there are underlying account classes that can underpin the signer within the transaction signer account.

  • Account - An in-built algosdk.Account object that has an address and private signing key, this can be created
  • SigningAccount - An abstraction around algosdk.Account that supports rekeyed accounts
  • LogicSigAccount - An in-built algosdk algosdk.LogicSigAccount object
  • MultisigAccount - An abstraction around algosdk.MultisigMetadata, algosdk.makeMultiSigAccountTransactionSigner, algosdk.multisigAddress, algosdk.signMultisigTransaction and algosdk.appendSignMultisigTransaction that supports multisig accounts with one or more signers present

Dispenser

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.

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 algorand.account.rekeyAccount(account, rekeyTo, options) function:

  • account: string | TransactionSignerAccount - The account address or signing account of the account that will be rekeyed
  • rekeyTo: string | TransactionSignerAccount - The account address or signing account of the account that will be used to authorise transactions for the rekeyed account going forward. If a signing account is provided that will now be tracked as the signer for account in the AccountManager instance.
  • An options object, which has:

You can also pass in rekeyTo as a common transaction parameter to any transaction.

Examples

// Basic example (with string addresses)

await algorand.account.rekeyAccount({ account: 'ACCOUNTADDRESS', rekeyTo: 'NEWADDRESS' })

// Basic example (with signer accounts)

await algorand.account.rekeyAccount({ account: account1, rekeyTo: newSignerAccount })

// Advanced example

await algorand.account.rekeyAccount({
  account: 'ACCOUNTADDRESS',
  rekeyTo: 'NEWADDRESS',
  lease: 'lease',
  note: 'note',
  firstValidRound: 1000n,
  validityWindow: 10,
  extraFee: (1000).microAlgo(),
  staticFee: (1000).microAlgo(),
  // Max fee doesn't make sense with extraFee AND staticFee
  //  already specified, but here for completeness
  maxFee: (3000).microAlgo(),
  maxRoundsToWaitForConfirmation: 5,
  suppressLog: true,
})

// Using a rekeyed account

// Note: if a signing account is passed into `algorand.account.rekeyAccount` then you don't need to call `rekeyedAccount` to register the new signer
const rekeyedAccount = algorand.account.rekeyed(account, newAccount)
// rekeyedAccount 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, 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 AlgorandClient via algorand.account.kmd or instantiate it directly (passing in a ClientManager):

import { KmdAccountManager } from '@algorandfoundation/algokit-utils/types/kmd-account-manager'

// Algod client only
const kmdAccountManager = new KmdAccountManager(clientManager)

The methods that are available are:

  • [getWalletAccount(walletName, predicate?, sender?)](../code/classes/types_kmd_account_manager.KmdAccountManager.md#getwalletaccount) - Returns an Algorand signing account with private key loaded from the given KMD wallet (identified by name).
  • [getOrCreateWalletAccount(name, fundWith?)](../code/classes/types_kmd_account_manager.KmdAccountManager.md#getorcreatewalletaccount) - 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.
  • [getLocalNetDispenserAccount()](../code/classes/types_kmd_account_manager.KmdAccountManager.md#getlocalnetdispenseraccount) - Returns an Algorand account with private key loaded for the default LocalNet dispenser account (that can be used to fund other accounts)
// Get a wallet account that seeded the LocalNet network
const defaultDispenserAccount = await kmdAccountManager.getWalletAccount(
  'unencrypted-default-wallet',
  (a) => a.status !== 'Offline' && a.amount > 1_000_000_000,
)
// Same as above, but dedicated method call for convenience
const localNetDispenserAccount = await kmdAccountManager.getLocalNetDispenserAccount()
// 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
const newAccount = await kmdAccountManager.getOrCreateWalletAccount('account1', (2).algo())
// This will return the same account as above since the name matches
const existingAccount = await kmdAccountManager.getOrCreateWalletAccount('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 when using via AlgorandClient:

// Get and register LocalNet dispenser
const localNetDispenser = await algorand.account.localNetDispenser()
// Get and register a dispenser by environment variable, or if not set then LocalNet dispenser via KMD
const dispenser = await algorand.account.dispenserFromEnvironment()
// Get an account from KMD idempotently by name. In this case we'll get the default dispenser account
const account1 = await algorand.account.fromKmd('unencrypted-default-wallet', (a) => a.status !== 'Offline' && a.amount > 1_000_000_000)
// Get / create and register account from KMD idempotently by name
const account1 = await algorand.account.kmd.getOrCreateWalletAccount('account1', (2).algo())