Skip to content

Commit f90fabb

Browse files
authored
chore: more demonstrative examples (#14)
2 parents 7c9da9b + bda2c42 commit f90fabb

File tree

7 files changed

+395
-0
lines changed

7 files changed

+395
-0
lines changed

examples/governance/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Subscriber Example: Governance
2+
3+
This example demonstrates how to use the `AlgorandSubscriber` to parse governance commitment transactions. Every 10 seconds, the subscriber will print out all of the governance commitments made since the last sync. The subscriber in this example uses `"sync_behaviour": "catchup-with-indexer"` to catch up because we are expecting to have a large amount of transactions with a common note prefix. This is an example of where the indexer's server-side filtering is useful. It should be noted that the exact same behavior can be achieved without indexer using algod, but indexer allows for a quicker catchup with fewer API calls. This example also only polls the chain every 10 seconds, since it is primarily useful for historical data and we don't care about live data.
4+
5+
## Governance Prefix
6+
7+
This example uses the `af/gov1` governance prefix to find governance transactions. For more information on Algorand Governance transactions, see the [Govenor's Guide](https://forum.algorand.org/t/governors-guide-2021-2024/12013).
8+
9+
## Running the example
10+
11+
To run the example, execute the following commands:
12+
13+
### Install dependencies
14+
15+
```bash
16+
poetry install
17+
```
18+
19+
### Run the script
20+
21+
```bash
22+
poetry run python governance.py
23+
```

examples/governance/governance.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import base64
2+
import json
3+
import random
4+
5+
from algokit_utils.beta.algorand_client import AlgorandClient
6+
from algokit_utils.beta.composer import PayParams
7+
8+
from algokit_subscriber.subscriber import AlgorandSubscriber
9+
from algokit_subscriber.types.subscription import SubscribedTransaction
10+
11+
algorand = AlgorandClient.default_local_net()
12+
13+
14+
dispenser = algorand.account.localnet_dispenser()
15+
sender = algorand.account.random()
16+
17+
# Fund the sender
18+
algorand.send.payment(
19+
PayParams(sender=dispenser.address, receiver=sender.address, amount=1_000_000)
20+
)
21+
22+
# Send a governance commitment message
23+
algorand.send.payment(
24+
PayParams(
25+
sender=sender.address,
26+
receiver=sender.address,
27+
amount=0,
28+
# Commit a random amount of ALGO
29+
note=f'af/gov1:j{{"com":{random.randint(1_000_000, 100_000_000)}}}'.encode(),
30+
)
31+
)
32+
33+
# Send an unrelated message
34+
algorand.send.payment(
35+
PayParams(
36+
sender=sender.address,
37+
receiver=sender.address,
38+
amount=0,
39+
note=b"Some random txn",
40+
)
41+
)
42+
43+
# Every subscriber instance uses a watermark to track what block it processed last.
44+
# In this example we are using a variable to track the watermark
45+
46+
watermark = 0
47+
48+
49+
# To implement a watermark in the subscriber, we must define a get and set function
50+
def get_watermark() -> int:
51+
"""
52+
Get the current watermark value
53+
"""
54+
return watermark
55+
56+
57+
def set_watermark(new_watermark: int) -> None:
58+
"""
59+
Set our watermark variable to the new watermark from the subscriber
60+
"""
61+
global watermark # noqa: PLW0603
62+
watermark = new_watermark
63+
64+
65+
subscriber = AlgorandSubscriber(
66+
algod_client=algorand.client.algod,
67+
indexer_client=algorand.client.indexer,
68+
config={
69+
"filters": [
70+
{
71+
"name": "Governance",
72+
"filter": {
73+
"type": "pay",
74+
"note_prefix": "af/gov1:j",
75+
},
76+
},
77+
],
78+
# Instead of always waiting for the next block, just poll for new blocks every 10 seconds
79+
"wait_for_block_when_at_tip": False,
80+
"frequency_in_seconds": 10,
81+
# The watermark persistence functions are used to get and set the watermark
82+
"watermark_persistence": {"get": get_watermark, "set": set_watermark},
83+
# Indexer has the ability to filter transactions server-side, resulting in less API calls
84+
# This is only useful if we have a very specific query, such as a note prefix
85+
"sync_behaviour": "catchup-with-indexer",
86+
},
87+
)
88+
89+
90+
def print_transfer(transaction: SubscribedTransaction, _: str) -> None:
91+
"""
92+
This is an EventListener callback. We use the .on function below to attach this callback to specific events.
93+
94+
Every EventListener callback will receive two arguments:
95+
* The transaction data
96+
* The filter name (from the 'filters' list) that the transaction matched
97+
"""
98+
json_data = (
99+
base64.b64decode(transaction["note"])
100+
.decode()
101+
.split(":j")[1]
102+
.replace("”", '"')
103+
.replace("“", '"')
104+
)
105+
106+
amount = json.loads(json_data)["com"] * 1e-6
107+
108+
print(
109+
f"Transaction {transaction['sender']} committed {amount} ALGO on round {transaction['confirmed-round']} in transaction {transaction['id']}"
110+
)
111+
112+
113+
# Attach the callback to the events we are interested in
114+
subscriber.on("Governance", print_transfer)
115+
116+
# Start the subscriber
117+
subscriber.start()

examples/live_monitoring/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Subscriber Example: Live Monitoring
2+
3+
This example demonstrates how to use the `AlgorandSubscriber` to get live transactions from the Algorand blockchain.
4+
5+
Each round, the subscriber will print out all of the USDC and ALGO transactions that have ocurred.
6+
7+
This is an example of using the subscriber for live monitoring where you don't care about historical data. This behavior is primarily driven by the `"sync_behaviour": "skip-sync-newest"` configuration which skips syncing older blocks. Since we don't care about historical data, the watermark of the last round processed is not persisted and only a non-archival algod is required for the subscriber to function. This makes this setup lightweight with low infrastructure requirements.
8+
9+
## Running the example
10+
11+
To run the example, execute the following commands:
12+
13+
### Install dependencies
14+
15+
```bash
16+
poetry install
17+
```
18+
19+
### Run the script
20+
21+
```bash
22+
poetry run python live_monitoring.py
23+
```
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
from typing import TYPE_CHECKING
2+
3+
from algokit_utils.beta.algorand_client import AlgorandClient
4+
5+
from algokit_subscriber.subscriber import AlgorandSubscriber
6+
from algokit_subscriber.types.subscription import SubscribedTransaction
7+
8+
if TYPE_CHECKING:
9+
from algokit_subscriber.types.indexer import (
10+
AssetTransferTransactionResult,
11+
PaymentTransactionResult,
12+
)
13+
14+
algorand = AlgorandClient.main_net()
15+
16+
# Every subscriber instance uses a watermark to track what block it processed last.
17+
# In this example we are using a variable to track the watermark
18+
19+
watermark = 0
20+
21+
22+
# To implement a watermark in the subscriber, we must define a get and set function
23+
def get_watermark() -> int:
24+
"""
25+
Get the current watermark value
26+
"""
27+
return watermark
28+
29+
30+
def set_watermark(new_watermark: int) -> None:
31+
"""
32+
Set our watermark variable to the new watermark from the subscriber
33+
"""
34+
global watermark # noqa: PLW0603
35+
watermark = new_watermark
36+
37+
38+
subscriber = AlgorandSubscriber(
39+
algod_client=algorand.client.algod,
40+
config={
41+
"filters": [
42+
{
43+
"name": "USDC",
44+
# Only match non-zero USDC transfers
45+
"filter": {
46+
"type": "axfer",
47+
"asset_id": 31566704, # mainnet usdc
48+
"min_amount": 1,
49+
},
50+
},
51+
{
52+
"name": "ALGO",
53+
# Only match non-zero ALGO transfers
54+
"filter": {
55+
"type": "pay",
56+
"min_amount": 1,
57+
},
58+
},
59+
],
60+
# Once we are caught up, always wait until the next block is available and process it immediately once available
61+
"wait_for_block_when_at_tip": True,
62+
# The watermark persistence functions are used to get and set the watermark
63+
"watermark_persistence": {"get": get_watermark, "set": set_watermark},
64+
# Skip the sync process and immediately get the latest block in the network
65+
"sync_behaviour": "skip-sync-newest",
66+
# Max rounds to sync defines how many rounds to lookback when first starting the subscriber
67+
# If syncing via a non-archival node, this could be up to 1000 rounds back
68+
# In this example we want to immediately start processing the latest block without looking back
69+
"max_rounds_to_sync": 1,
70+
},
71+
)
72+
73+
74+
def print_transfer(transaction: SubscribedTransaction, filter_name: str) -> None:
75+
"""
76+
This is an EventListener callback. We use the .on function below to attach this callback to specific events.
77+
78+
Every EventListener callback will receive two arguments:
79+
* The transaction data
80+
* The filter name (from the 'filters' list) that the transaction matched
81+
"""
82+
details: PaymentTransactionResult | AssetTransferTransactionResult
83+
if filter_name == "USDC":
84+
details = transaction["asset-transfer-transaction"]
85+
elif filter_name == "ALGO":
86+
details = transaction["payment-transaction"]
87+
88+
print(
89+
f"{transaction['sender']} sent {details['receiver']} {details['amount'] * 1e-6} {filter_name} in transaction {transaction['id']}"
90+
)
91+
92+
93+
# Attach the callback to the events we are interested in
94+
subscriber.on("ALGO", print_transfer)
95+
subscriber.on("USDC", print_transfer)
96+
97+
# Start the subscriber
98+
subscriber.start()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
transactions.csv
2+
watermark
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Subscriber Example: Transaction Record
2+
3+
This example demonstrates how to use the `AlgorandSubscriber` to record all of the balance changes for a given account. This example uses the filesystem to persist the watermark and record transactions in a CSV file. This example makes use of the `"balance_changes"` filter option which will include ANY transaction that affects the balance of the given account. This example uses `"sync_behaviour": "sync-oldest"` to ensure that we get all historical data from an archival node. An indexer could be used for catchup, but due to the complex nature of the query it would not save any API calls like it would with a more simple query (such as the one in the [governance example](../governance/README.md)).
4+
5+
## Created Files
6+
7+
`watermark` will be created with the last processed round and updated with each new round processed.
8+
9+
`transactions.csv` will be created with the header `round,sender,receiver,amount` and will append a new row for each transaction processed.
10+
11+
## Running the example
12+
13+
To run the example, execute the following commands:
14+
15+
### Install dependencies
16+
17+
```bash
18+
poetry install
19+
```
20+
21+
### Run the script
22+
23+
```bash
24+
poetry run python governance.py
25+
```

0 commit comments

Comments
 (0)