Strategy Development

Custom Strategies are automatically loaded when runing pab run. PAB loads your strategies from a strategies module at your current working directory.

Any subclass of pab.strategy.BaseStrategy in the strategies module can be used to run a task. For more info on how to configure tasks, see Configuring Tasks.

Basic Strategy

Open strategies/__init__.py and write the following:

 1 from pab.strategy import BaseStrategy
 2 from pab.utils import amountToDecimal
 3
 4 class LogBalanceToFileStrategy(BaseStrategy):
 5     def __init__(self, *args, accix: int = 0):
 6         super().__init__(*args)
 7         self.user = self.accounts[0]
 8
 9     def run(self):
10         balance = self.blockchain.w3.get_balance(self.user)
11         self.logger.info(f"Balance: {amountToDecimal(balance)})

This simple strategy will only log the balance of some account. It uses the BaseStrategy.accounts to retrieve the account at the accix index, and the current blockchain connection from BaseStrategy.blockchain.w3 to get the account balance.

Strategies In-Depth

Accounts

Accounts, also called wallets, are used in blockchains as user controlled addresses that can sign transactions and data. For info on how to configure a accounts in PAB read Loading Accounts.

To use configured accounts in a BaseStrategy subclass, you can access the pab.strategy.BaseStrategy.accounts attribute (e.g. self.accounts[0], self.accounts[1]).

class MyStrategy(BaseStrategy):
    def run(self):
        user = self.accounts[0]

Accounts Attributes

Accounts in self.accounts are instances of LocalAccount.

Loading Order

As you see in Loading Accounts, there are two ways of loading accounts but only one list. The way the list is filled is by first loading accounts from environment variables into their fixed indexes, and then filling the gaps from 0 to N with keyfiles.

This means that if you have the following environment variables:

# .env.prod
PAB_PK1=ACC-A
PAB_PK2=0xACC-B
PAB_PK5=0xACC-C

And you run with two keyfiles like this:

$ pab run -e prod -k ACC-D.keyfile,ACC-E.keyfile

The accounts dictionary for a strategy will look like this:

>>> print(self.accounts)
{
    0: LocalAccount("0xACC-D"),
    1: LocalAccount("0xACC-A"),
    2: LocalAccount("0xACC-B"),
    3: LocalAccount("0xACC-E"),
    5: LocalAccount("0xACC-C")
}

To avoid the confusion that using both methods might cause, we recomend you stick to one method of loading accounts.

Contracts

PAB automatically loads the contracts defined in Registering Contracts. Strategies can fetch them by name using the pab.strategy.BaseStrategy.contracts attribute.

For example:

class MyStrategy(BaseStrategy):
    def run(self):
        contract = self.contacts.get("MY_CONTRACT")

Transactions

Subclasses of BaseStrategy will have a pab.strategy.BaseStrategy.transact() method that you can use to sign and send transactions.

For example:

class MyStrategy(BaseStrategy):
    def run(self):
        user = self.accounts[0]
        contract = self.contacts.get("MY_CONTRACT")
        params = ("param1", 2)
        rcpt = self.transact(user, contract.functions.someFunction, params)

Read-Only Queries

You can make readonly queries directly from the contract, without using self.transact.

class MyStrategy(BaseStrategy):
    def run(self):
        contract = self.contacts.get("MY_CONTRACT")
        params = ("param1", 2)
        some_data = contract.functions.getSomeData(*params).call()

Read-Only queries do not consume gas.

Blockchain and Web3

To access the underlying Web3 connection you can use the pab.blockchain.Blockchain.w3 attribute. You can get the current Blockchain object from your strategie’ s pab.strategy.BaseStrategy.blockchain.