Skip to main content

Algopay — Open-source Algorand Payroll & Payouts Toolkit

PyPI version Python versions Code style: black Linting: Ruff License: MIT

Algopay is a free, open-source Python library for running automated payroll and general payouts on Algorand. Schedule recurring jobs, pay many employees across departments, generate a CSV ledger, compile simple escrow contracts, send email notifications, and drop down to low-level transaction helpers when you need to.

  • Free & open source (pip-installable from Git)
  • General purpose (payroll, bounties, grants, tips, streaming-style pay)
  • Strong defaults (CSV audit log, per-run payroll_id, background jobs)
  • Clean repo (pre-commit: Ruff + Black, pytest, CI workflows, Dependabot)

Project Resources


Table of Contents


Why Algopay?

What makes this different

  • Open and Affordable — Open-source, free, and pip-installable.
  • Pragmatic API — A single Payroll class covers 90% of payout needs: add employees, run once, or run on a timer.
  • Auditable by default — Every payment appends a row to a CSV ledger with department, job_id, payroll_id, balances, and status.
  • Parallel departments — Run multiple departments concurrently with different mnemonics and intervals, writing to the same ledger.
  • Extensible — Pluggable notifier interface (start with console + SMTP email). Extra transactions helpers for custom flows.
  • Clean & professional — Pre-commit (Ruff + Black), pytest suite, GitHub Actions CI, Dependabot.

Installation

# Recommended: install from GitHub
pip install "git+https://github.com/KelvinLinBU/Algopay.git"

# Download from pip
pip install algopay

Python 3.10+ recommended.

For LocalNet, use AlgoKit’s sandbox (Docker). For TestNet/MainNet, you may use public endpoints (e.g., Algonode) or your own node.


Quick Start

from dotenv import load_dotenv
import os
from algo_pay.payroll import Payroll

load_dotenv()

payroll = Payroll(
    employer_mnemonic=os.getenv("DEPT_A_MNEMONIC"),
    department=os.getenv("DEPT_A_NAME", "Engineering"),
    network=os.getenv("NETWORK", "localnet"),
)

# Hourly rates are in ALGOs/hour
payroll.add_employee(os.getenv("EMPLOYEE_1"), hourly_rate=100.0, name=os.getenv("EMPLOYEE_1_NAME"))
payroll.add_employee(os.getenv("EMPLOYEE_2"), hourly_rate=50.0,  name=os.getenv("EMPLOYEE_2_NAME"))

# Pay 5 hours worth
txids = payroll.run_payroll(hours=5, note="Weekly payroll", job_id="ManualRun")
print("Paid, txids:", txids)

A CSV ledger is written to PAYROLL_HISTORY_FILE (see format below).


Configuration (.env)

Create a .env in your project root:

# ========================
# Employer Accounts
# ========================

# Department A (Engineering)
DEPT_A_NAME="Engineering"
DEPT_A_MNEMONIC="chat denial daring require ticket purse team snake victory olympic around news sausage method lake sunny plunge beef rude flip own tiger wild absent strategy"
DEPT_A_ADDRESS="WPNITU45MLDGDKJ3Z7UDBV466IZ7QTIOR2XKJZ6SG2LOH6QRZXXOTEB6KU"

# Department B (Marketing)
DEPT_B_NAME="Marketing"
DEPT_B_MNEMONIC="sad mango ignore picture burst canoe tail scout hire coil mango mercy usual invite congress song price rifle layer dove violin genuine forum about traffic"
DEPT_B_ADDRESS="3M4B53T5GW4YM7S55ON4NNNYJWAHATE2SNKSGCB4K6K6VEUW23KU3AB6BU"

# Department C (Finance)
DEPT_C_NAME="Finance"
DEPT_C_MNEMONIC="follow learn school various cancel aspect salon win buffalo glare repair rival easy video iron fence theory sniff decorate typical flush sudden peanut absorb clap"
DEPT_C_ADDRESS="5L756GRBFFKQ7UPBXSSQEDWLYCE3YLW75XT2SBRM2HNHFANESKJI46L2KM"

# ========================
# Payroll Settings
# ========================
NETWORK="localnet"                      # localnet | testnet | mainnet
PAYROLL_HISTORY_FILE="payroll_history.csv"
PAYROLL_INTERVAL=30                     # seconds (for demos)

# ========================
# Employees
# ========================
EMPLOYEE_1_NAME="Alice"
EMPLOYEE_1="66MDNQQLL2A3LXHSEZWJ7PZGIWRP3NBNBPO62K3BCSP2VMFNQABCJFQQHQ"

EMPLOYEE_2_NAME="Bob"
EMPLOYEE_2="527M4BKEMJHTEQGQ52CGNI3E74RSJRZIHUJOVL42IAP72PARS6UA3TBENE"

# ========================
# Optional: Email Notifier (SMTP)
# ========================
SMTP_SENDER="your_email@example.com"
SMTP_PASSWORD="your_app_password"      # app password (see Security Notes)
SMTP_SERVER="smtp.mail.yahoo.com"      # e.g., smtp.gmail.com, smtp.mail.yahoo.com
SMTP_PORT=465                          # 465 (SSL) or 587 (STARTTLS)

Never commit real mnemonics. Use LocalNet for demos and a secrets manager for real deployments.


Examples

One-off Payroll

examples/log_demo.py runs a single payroll batch, prints balances and writes the ledger.

Background Scheduler

Run a repeating job in a daemon thread:

# examples/scheduler_demo.py
from dotenv import load_dotenv
import os, time
from algo_pay.payroll import Payroll

load_dotenv()

payroll = Payroll(os.getenv("DEPT_A_MNEMONIC"), department="Engineering", network="localnet")
payroll.add_employee(os.getenv("EMPLOYEE_1"), 60,  name=os.getenv("EMPLOYEE_1_NAME"))
payroll.add_employee(os.getenv("EMPLOYEE_2"), 200, name=os.getenv("EMPLOYEE_2_NAME"))

payroll.start_payroll_job(interval_seconds=30, hours=0.01, note="Scheduled Payroll", job_id="EngJob")
try:
    time.sleep(120)  # let it run
finally:
    payroll.stop_payroll_job()

Parallel Multi-Department Scheduler

Start three departments in parallel at 5s / 10s / 15s:

# examples/parallel.py
from algo_pay.payroll import Payroll
from dotenv import load_dotenv
import os, time

load_dotenv()
NETWORK = os.getenv("NETWORK", "localnet")
HISTORY_FILE = os.getenv("PAYROLL_HISTORY_FILE", "payroll_history.csv")

departments = [
    {"name": os.getenv("DEPT_A_NAME"), "mnemonic": os.getenv("DEPT_A_MNEMONIC"), "interval": 5},
    {"name": os.getenv("DEPT_B_NAME"), "mnemonic": os.getenv("DEPT_B_MNEMONIC"), "interval": 10},
    {"name": os.getenv("DEPT_C_NAME"), "mnemonic": os.getenv("DEPT_C_MNEMONIC"), "interval": 15},
]

employees = [
    {"name": os.getenv("EMPLOYEE_1_NAME"), "address": os.getenv("EMPLOYEE_1"), "rate": 60},
    {"name": os.getenv("EMPLOYEE_2_NAME"), "address": os.getenv("EMPLOYEE_2"), "rate": 200},
]

print("=== Multi-Department Parallel Payroll Scheduler ===")
print(f"Running on {NETWORK}\n")
running = []

for dept in departments:
    print(f"Setting up {dept['name']}…")
    p = Payroll(dept["mnemonic"], department=dept["name"], network=NETWORK, history_file=HISTORY_FILE)
    for e in employees:
        p.add_employee(e["address"], e["rate"], name=e["name"])
    p.start_payroll_job(interval_seconds=dept["interval"], hours=0.01, note=f"{dept['name']} Scheduled")
    running.append(p)

try:
    time.sleep(60)
finally:
    for p in running:
        p.stop_payroll_job()

Email Notification on Completion

Send an email after a batch run:

# examples/notify_demo.py
import os
from dotenv import load_dotenv
from algo_pay.payroll import Payroll
from algo_pay.notifier import EmailNotifier

load_dotenv()

payroll = Payroll(
    os.getenv("DEPT_A_MNEMONIC"),
    department=os.getenv("DEPT_A_NAME", "Engineering"),
    network=os.getenv("NETWORK", "localnet"),
    history_file=os.getenv("PAYROLL_HISTORY_FILE", "payroll_history.csv"),
    notifier=EmailNotifier(
        smtp_server=os.getenv("SMTP_SERVER", "smtp.mail.yahoo.com"),
        smtp_port=int(os.getenv("SMTP_PORT", "465")),
        sender_email=os.getenv("SMTP_SENDER"),
        sender_password=os.getenv("SMTP_PASSWORD"),
        recipient_email="kelvin_lin_2012@yahoo.com",  # default recipient
    ),
)

payroll.add_employee(os.getenv("EMPLOYEE_1"), 100.0, os.getenv("EMPLOYEE_1_NAME", "Alice"))
payroll.add_employee(os.getenv("EMPLOYEE_2"),  50.0, os.getenv("EMPLOYEE_2_NAME", "Bob"))

print("Running payroll with email notification…")
payroll.run_payroll(hours=5, note="Weekly payroll", job_id="NotifyDemo")

You can also call the notifier yourself and override the recipient:

payroll.notifier.notify(payload_dict, recipient_override="someone@example.com")

Generate & Compile Escrow Contracts

Build trivial “pay to exact amount & receiver” PyTeal escrows from a CSV, copy them into the sandbox, and compile:

# CSV must contain: employee_address,fixed_payout_microalgos
python contracts/generate_escrow.py example_employee_data/3_example_employees.csv
# => writes contracts/escrow_<prefix>.teal and <input>_compiled.csv with escrow addresses

The tests stub PyTeal for speed; when you run the script, it compiles against your Dockerized LocalNet (algokit_sandbox_algod) via goal.


API Reference

Payroll

class Payroll:
    def __init__(
        self,
        employer_mnemonic: str,
        department: str,
        network: str = "localnet",          # localnet | testnet | mainnet
        history_file: str = "payroll_history.csv",
        notifier: Optional[Notifier] = None # defaults to no notifications
    )

    def add_employee(self, address: str, hourly_rate: float, name: str | None = None) -> None
    def remove_employee(self, address: str) -> None

    def get_balance(self, address: str | None = None) -> float
    def get_asset_balance(self, address: str, asset_id: int) -> float

    def send_payment(self, to: str, amount: float, note: str = "") -> tuple[str, float, float, str]
        # amount is in ALGOs; returns (txid|"FAILED", employer_balance_before, employer_balance_after, "SUCCESS"/"FAILED")

    def run_payroll(self, hours: float, note: str = "Payroll Run", job_id: str = "DefaultJob") -> list[str]

    def start_payroll_job(self, interval_seconds: int, hours: float, note: str, job_id: str | None = None) -> None
    def stop_payroll_job(self) -> None
  • Networks

    • localnet → http://localhost:4001 (token "a"*64)
    • testnet → https://testnet-api.algonode.cloud
    • mainnet → https://mainnet-api.algonode.cloud
  • Logging Every individual employee payment is appended to history_file with a unique payroll_id per batch.

  • Notifications If you pass a Notifier, run_payroll auto-sends a “job completed” payload (job_id, payroll_id, department, employees, txids, status).

Notifier

class Notifier:
    def notify(self, payload: dict[str, Any]) -> None: ...

class ConsoleNotifier(Notifier):
    def notify(self, payload: dict[str, Any]) -> None  # prints to stdout

class EmailNotifier(Notifier):
    def __init__(self, smtp_server: str, smtp_port: int,
                 sender_email: str, sender_password: str,
                 recipient_email: str):
        ...

    def notify(self, payload: dict[str, Any], recipient_override: str | None = None) -> None

For Yahoo/Gmail SMTP you typically need 2FA + an app password (not your normal login). SSL (465) or STARTTLS (587) are supported.

Transactions Helper

Low-level utilities for custom flows: algo_pay/transactions.py

from algo_pay import transactions

client = transactions.get_client("localnet" | "testnet" | "mainnet")

txn = transactions.build_payment_txn(client, sender, receiver, amount_microalgos: int, note: str | None = None)
asa = transactions.build_asset_transfer_txn(client, sender, receiver, asset_id: int, amount: int, note: str | None = None)

gid, txns = transactions.group_and_assign_id([txn1, txn2, ...])

signed = transactions.sign_transaction(txn, private_key)
txid = transactions.broadcast_transaction(client, signed)

# Convenience: amounts in ALGOs (float)
txid = transactions.execute_payment(client, sender, receiver, amount_algos: float, private_key, note=None)

# Batch convenience (sequential, not atomic group)
txids = transactions.batch_execute_payments(client, sender, [(receiver, algos), ...], private_key, note=None)

Convention: builder functions accept microAlgos (ints), while the high-level convenience execute_payment and the Payroll class accept ALGOs (floats).


CSV Ledger Schema

By default payroll_history.csv (configurable) uses:

Column Type Notes
timestamp ISO8601 UTC time the row was written
department str Department label passed to Payroll
job_id str Job identifier (manual or auto)
payroll_id str Unique batch identifier per run_payroll
employer str Employer address
employee_name str Friendly name (or address if not provided)
employee_address str Employee account
amount_ALGO float Amount per employee in ALGOs
txid str Transaction id (or "FAILED")
employer_balance_before float ALGOs before the payment
employer_balance_after float ALGOs after the payment
status str "SUCCESS" / "FAILED"

Multiple departments and jobs can safely append to the same ledger file.


Testing & Quality

  • Run tests
    pytest -v
    
  • Lint & format (pre-commit)
    pre-commit run --all-files
    # or auto-install into git hooks:
    pre-commit install
    
  • CI & security
    • GitHub Actions run tests & linters on pushes/PRs.
    • Dependabot keeps dependencies fresh.
    • Repo is formatted with Black, linted with Ruff.

License

MIT — do whatever you want, but no warranty. See LICENSE for details.


If you build something cool with Algopay, PRs and issues are welcome!

Metadata

Release files for algopay 0.1.7

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for algopay 0.1.7
File Size Uploaded
algopay-0.1.7.tar.gz 15.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for algopay 0.1.7
File Interpreter ABI Platform
algopay-0.1.7-py3-none-any.whl Python 3 none any Details

Total release size: 29.3 kB

Release files / algopay-0.1.7.tar.gz

Download URL algopay-0.1.7.tar.gz
Size 15.2 kB
Tags Source
SHA-256 checksum
How to use checksums
08fcb268ec7e48ae79dbeb1792349d24ec40537b86e24c65b8d739b81f0a7729
BLAKE2b-256 checksum
How to use checksums
be61a3b2ffdf49551e98fe3d141ffe68ae789b90cad7a5ebae7f577be5f63375
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.7

Release files / algopay-0.1.7-py3-none-any.whl

Download URL algopay-0.1.7-py3-none-any.whl
Size 14.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
337fa9d1c5edd75fc3f3f0f59d79831335a162793fd57b9c6804370ae61eda12
BLAKE2b-256 checksum
How to use checksums
0359eb414a5896d1e177c33a93538d3be2ed535cbf63f42958dbf96da84f639d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.7

Release history Release notifications | RSS feed

This release

0.1.7 This release

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page