Implementing Celo-Based Subscription Services with Python

Introduction

This tutorial gives a thorough walkthrough of how to use Python to construct a Celo-based subscription service. The modern corporate landscape now relies heavily on subscription-based business models. They work in a variety of fields, including news, retail, and the software and entertainment industries. In order to increase transparency, security, and efficiency, there has been an increase in interest in merging subscription models with decentralized systems since the advent of blockchain technology.

In this course, we’ll explore Python programming techniques for integrating the Celo blockchain with subscription services. Anyone with a mobile phone can access financial tools thanks to Celo, a blockchain network designed exclusively for mobile devices. With a decentralized governance system, it offers transactions that are quick and secure. Due to its qualities, it’s a great option for subscription services that demand high security and accessibility.

This tutorial, which focuses on practical instruction, addresses the following important topics:

  • Development of Smart Contracts: We will begin by using Solidity to build a solid smart contract. This agreement will deal with subscriptions, guaranteeing safe and open transactions.
  • Celo blockchain deployment: Following the creation of the smart contract, the Celo blockchain will host it. The specifics of the Celo network will be covered.
  • Integration with Python: Python will be used to communicate with the deployed smart contract after that. The setup of the Python environment and the use of the web3.py module to conduct transactions and access the blockchain are covered in this section.
  • Testing and Validation: To conclude, we will test our solution and validate transactions made through Celo’s block explorer.

Developers, business owners, and blockchain enthusiasts interested in utilizing blockchain technology for subscription services will find this tutorial to be of great use. The combination of Python’s ease of use and capability with Celo presents a fascinating possibility for creative and secure subscription services.

This tutorial will equip you with the skills and resources required to use Python to design a Celo-based subscription business, regardless of whether your goal is to create a brand-new subscription service or incorporate blockchain into an already-existing one.

Prerequisites

You should have the following prerequisites in order to follow this tutorial:

  • Python programming language fundamentals
  • Basic familiarity with the Celo Blockchain Platform
  • Basic understanding of the creation of smart contracts
  • Basic familiarity with Web3 Library

Requirements

  • Your machine must have Python 3.7 or greater installed.
  • A code editor or IDE (such as PyCharm or Visual Studio Code)
  • A wallet that works with Celo, such as MetaMask or Valora
  • A test account on the Celo network with some test cash (available through the Celo Faucet)

Step 1: Setting up a development environment

First, we need to set up a development environment for our Celo-based subscription service.

On your terminal, use the following commands to create a new folder for your project:

mkdir subscription-service
cd subscription-service

In your new directory, Let’s now establish and activate a virtual Python environment.

python3 -m venv venv
source venv/bin/activate

Next, Install the following libraries using pip, the Python package manager:

pip install py-solcx web3 python-dotenv

Step 2: Write the subscription service smart contract

Next, in the root directory of our project, let’s create a new file called SubscriptionService.sol which we will write out our smart contract and paste the following code:

SubscriptionService.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SubscriptionService {
    address public owner;
    uint256 public subscriptionPrice;

    struct Subscriber {
        bool isActive;
        uint256 expirationTime;
    }

    mapping(address => Subscriber) public subscribers;

    event Subscribed(address indexed subscriber, uint256 expirationTime);

    constructor(uint256 _subscriptionPrice) {
        owner = msg.sender;
        subscriptionPrice = _subscriptionPrice;
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "You are not the owner");
        _;
    }

    modifier onlyActiveSubscribers() {
        require(
            subscribers[msg.sender].isActive &&
                subscribers[msg.sender].expirationTime > block.timestamp,
            "You must be an active subscriber"
        );
        _;
    }

    function subscribe() external payable {
        require(
            msg.value == subscriptionPrice,
            "Must send exact subscription price"
        );

        subscribers[msg.sender].isActive = true;
        subscribers[msg.sender].expirationTime = block.timestamp + 30 days;

        emit Subscribed(msg.sender, subscribers[msg.sender].expirationTime);
    }

    function withdrawFunds(uint256 _amount) external onlyOwner {
        payable(owner).transfer(_amount);
    }
}

Now, let’s go through each code block step by step:

address public owner;
uint256 public subscriptionPrice;
mapping(address => Subscriber) public subscribers;

struct Subscriber {
    bool isActive;
    uint256 expirationTime;
}

mapping(address => Subscriber) public subscribers;

event Subscribed(address indexed subscriber, uint256 expirationTime);

Three Solidity components for a subscription service smart contract are defined in this snippet of code. The address of the contract owner is stored in the state variable owner, while the subscription price is kept in subscriptionPrice. The Subscriber struct is a special data type that contains the isActive flag for a subscriber’s status and the expirationTime flag for the subscription’s expiration date. The subscribers mapping effectively keeps track of all subscribers and their subscription states by connecting Ethereum addresses to their matching Subscriber structs. Moreover, the event Subscribed is defined, which is issued whenever a new subscription is made and records the subscriber’s address and expiration time.

constructor(uint256 _subscriptionPrice) {
    owner = msg.sender;
    subscriptionPrice = _subscriptionPrice;
}

A constructor method for a Solidity smart contract is shown above. It is a unique operation that is carried out just once, during the deployment of the contract. The service’s subscription fee is set using the only argument provided by this constructor, _subscriptionPrice. The address deploying the contract msg.sender is set as the owner state variable inside the constructor, and the subscriptionPrice state variable is initialized with the value supplied as _subscriptionPrice. This constructor essentially establishes the contract’s initial state by providing the owner’s address and the subscription cost.

modifier onlyOwner() {
    require(msg.sender == owner, "You are not the owner");
    _;
}

modifier onlyActiveSubscribers() {
    require(
        subscribers[msg.sender].isActive &&
            subscribers[msg.sender].expirationTime > block.timestamp,
        "You must be an active subscriber"
    );
    _;
}

The two modifiers above allow users to:

  • onlyOwner: Assures that a function can only be executed by the contract owner.
  • onlyActiveSubscribers: Assures that only subscribers who are currently active can use a function.
function subscribe() external payable {
    require(
        msg.value == subscriptionPrice,
        "Must send exact subscription price"
    );

    subscribers[msg.sender].isActive = true;
    subscribers[msg.sender].expirationTime = block.timestamp + 30 days;

    emit Subscribed(msg.sender, subscribers[msg.sender].expirationTime);
}

Users can send ether to the service’s subscribe function to sign up for it. This function is designated as external, which limits its ability to be called to sources outside the contract, and payment, which permits it to accept Ether. The precise cost of the subscription must be supplied. The sender is then designated as an active subscriber, and the expiration date is set to the timestamp plus 30 days. The Subscribed event is then released by it.

function withdrawFunds(uint256 _amount) external onlyOwner {
    payable(owner).transfer(_amount);
}

The contract owner can remove money (Ether) from the contract using the withdrawFunds function. It asks for the amount in wei that the owner wants to withdraw as an argument, which is called _amount. This function is also designated as external and uses the onlyOwner modifier, making it only accessible to the contract owner. It sends the specified amount of Ether to the owner’s address using the transfer function.

Step 3: Compile and Deploy the smart contract

Next, create a deploy.py file in your project root directory to compile and deploy the subscription service smart contract we have created and paste the following code:

deploy.py

import json
import os
from web3 import Web3
from dotenv import load_dotenv
from solcx import compile_standard, install_solc

load_dotenv()

# Install specific Solidity compiler version
install_solc("0.8.0")

# Set up web3 connection
provider_url = os.environ.get("CELO_PROVIDER_URL")
w3 = Web3(Web3.HTTPProvider(provider_url))
assert w3.is_connected(), "Not connected to a Celo node"

# Set deployer account and private key
deployer = os.environ.get("CELO_DEPLOYER_ADDRESS")
private_key = os.getenv("CELO_DEPLOYER_PRIVATE_KEY")

with open("SubscriptionService.sol", "r") as file:
    contract_source_code = file.read()

# Compile the contract
compiled_sol = compile_standard({
    "language": "Solidity",
    "sources": {
        "SubscriptionService.sol": {
            "content": contract_source_code
        }
    },
    "settings": {
        "outputSelection": {
            "*": {
                "*": ["metadata", "evm.bytecode", "evm.sourceMap"]
            }
        }
    }
})

# Extract the contract data
contract_data = compiled_sol['contracts']['SubscriptionService.sol']['SubscriptionService']
bytecode = contract_data['evm']['bytecode']['object']
abi = json.loads(contract_data['metadata'])['output']['abi']

# Deploy the contract
nonce = w3.eth.get_transaction_count(deployer)
transaction = {
    'nonce': nonce,
    'gas': 2000000,
    'gasPrice': w3.eth.gas_price,
    'data': bytecode,
}
signed_txn = w3.eth.account.sign_transaction(transaction, private_key)
transaction_hash = w3.eth.send_raw_transaction(signed_txn.rawTransaction)
transaction_receipt = w3.eth.wait_for_transaction_receipt(transaction_hash)

# Get the contract address
contract_address = transaction_receipt['contractAddress']
print(f"Subscription Service Contract deployed at address: {contract_address}")

The creation and deployment of Solidity smart contracts onto the Celo blockchain are both automated by this Python script. Initial imports of the script include json, os, web3' dotenv, and solcx, among other necessary libraries. The Solidity compiler is then installed after loading environment variables.

In the following step, the script uses the Web3 library to connect to the Celo blockchain using a provider URL. It sets the deployer’s account and private key from the environment variables after confirming the connection.

It then reads the SubscriptionService.sol file’s smart contract source code and builds it using the Solidity compiler. The script extracts the essentials for deployment bytecode and ABI (Application Binary Interface) from the compiled contract.

Note: create a .env file in your project root directory and store sensitive keys or details like the deployer and private_key variables.

Run the following command on your terminal to compile and deploy our contract:

python deploy.py

Step 4: Interact with the smart contract

Lastly, let’s create functions that would interact with our smart contract and then we can view the transactions on Celo Explorer. create a new file called client.py and paste the following code:

client.py

import os
from web3 import Web3, HTTPProvider
from web3.middleware import geth_poa_middleware
from dotenv import load_dotenv
import time

import deploy

# Load environment variables
load_dotenv()

# Set up web3 connection
provider_url = os.getenv("CELO_PROVIDER_URL")
w3 = Web3(HTTPProvider(provider_url))
assert w3.is_connected(), "Not connected to a Celo node"

# Add PoA middleware to web3.py instance
w3.middleware_onion.inject(geth_poa_middleware, layer=0)

# Get contract ABI and address
abi = deploy.abi
contract_address = deploy.contract_address
contract = w3.eth.contract(address=contract_address, abi=abi)

# Initialize account
account_address = os.getenv("CELO_DEPLOYER_ADDRESS")
private_key = os.getenv("CELO_DEPLOYER_PRIVATE_KEY")

# Function to subscribe to the service
def subscribe(subscriber_address, private_key, subscription_price):
    
    # Fetch the current gas price from the network
    current_gas_price = w3.eth.gas_price
    
    # Prepare the transaction
    transaction = contract.functions.subscribe().build_transaction({
        'chainId': 44787,
        'gas': 2000000,
        'gasPrice': current_gas_price,
        'nonce': w3.eth.get_transaction_count(subscriber_address),
        'value': subscription_price,
    })

    # Sign the transaction
    signed_txn = w3.eth.account.sign_transaction(transaction, private_key)

    # Send the transaction
    return w3.eth.send_raw_transaction(signed_txn.rawTransaction)

# Function to withdraw funds (only callable by owner)
def withdraw_funds(owner_address, private_key, amount):
    # Fetch the current gas price from the network
    current_gas_price = w3.eth.gas_price
    
    # Prepare the transaction
    transaction = contract.functions.withdrawFunds(amount).build_transaction({
        'chainId': 44787,
        'gas': 2000000,
        'gasPrice': current_gas_price,
        'nonce': w3.eth.get_transaction_count(owner_address),
    })
    
    # Sign the transaction
    signed_txn = w3.eth.account.sign_transaction(transaction, private_key)

    # Send the transaction
    return w3.eth.send_raw_transaction(signed_txn.rawTransaction)

# Example Usage:
subscriber_address = "" # SUBSCRIBER_ADDRESS
private_key = os.getenv("SUBSCRIBER_PRIVATE_KEY")
subscription_price = w3.to_wei('0.0001', 'ether')  # In wei

# Subscribing
transaction_hash = subscribe(subscriber_address, private_key, subscription_price)
transaction_receipt = w3.eth.wait_for_transaction_receipt(transaction_hash)
print("Subscription successful, Transaction hash: ", transaction_hash.hex())

# Withdrawing funds as the contract owner (example)
owner_address =  account_address # "OWNER_ADDRESS"
owner_private_key = private_key # "OWNER_PRIVATE_KEY"
withdraw_amount = w3.to_wei('0.00001', 'ether')

transaction_hash = withdraw_funds(owner_address, owner_private_key, withdraw_amount)
transaction_receipt = w3.eth.wait_for_transaction_receipt(transaction_hash)
print("Withdrawal successful, Transaction hash: ", transaction_hash.hex())

Now let’s go through the code above:

  • Setup: The script imports the required libraries and modules (such as web3, os, dotenv, and deploy), and then uses load_dotenv() to load environment variables.
  • Web3 Connection: Using the provider URL gleaned from the environment variables, it connects to a Celo node. Additionally, it states that the connection is effective. In order to make sure that the web3 instance is compatible with Celo’s consensus method, Proof-of-Authority (PoA) middleware is implemented.
  • Contract Interaction Setup: It initializes a contract instance for interaction after retrieving the contract’s ABI and address from the previously deployed contract (likely in the deploy module).
  • Initialize Account: It pulls the private key and account address from the environment variables. Transactions will be signed using this account.
  • The function known as “subscribe” enables an address to sign up for the contract’s service. It creates a transaction and calls the subscribe function of the contract before signing it with the supplied private key and submitting it to the network.
  • withdraw_funds Function: With this function, the contract’s owner can take money out of the agreement. It builds, signs, and sends a transaction in a manner akin to the subscribe function. But this one calls the withdrawFunds function of the contract.
  • Example Usage: The subscribe and withdraw_funds functions are used in this section near the bottom of the script. It uses defined variables to invoke functions, such as subscriber_address, private_key, and subscription_price. The transaction hashes are printed when it waits for transaction receipts to show that transactions have been mined.

Note: Please, make sure to replace placeholders like YOUR_CELO_PROVIDER_URL, YOUR_CONTRACT_ADDRESS, SUBSCRIBER_ADDRESS, SUBSCRIBER_PRIVATE_KEY, etc. with actual values.

Run the following command to see the output of the code:

python client.py

You can view the transaction on Celo Explorer by pasting the transaction hash:

Conclusion

This article has demonstrated how to use a Solidity smart contract to build a subscription service on the Celo blockchain and communicate with it using Python. We’ve discussed the fundamentals of creating smart contracts, the specifics of the Celo blockchain, and how to deploy and work with smart contracts using Python. This combination makes use of the efficiency, security, and transparency offered by blockchain technology to produce more dependable and easily available subscription services.

Next Steps

  • Explore the Solidity development process in greater detail to learn how to optimize smart contracts for gas utilization and protect them from common vulnerabilities.
  • Discover the environment of Celo. Become familiar with the ecosystem of Celo. Try out native Celo assets like the cUSD to see how you may incorporate them into your subscription business.
  • Implement Frontend to enable consumers to quickly subscribe to your service without directly engaging with the smart contract, and create a user-friendly frontend utilizing web technologies (HTML, CSS, and JS).
  • Develop your Python knowledge by learning more about how to manage events, automate processes, and construct more complicated interactions using smart contracts for easier management of the subscription service.

About the Author

I am a Software engineer with an interest in Blockchain technology. I love picking up new technologies and sharing my knowledge as I learn as a way of giving back to the tech community. You can find one on GitHub and Twitter.

References

  1. Celo Developers Documentation
  2. Solidity Documentation
  3. Web3.py Documentation
  4. Celo Faucet
  5. Github repo
2 Likes

Approved for you to get started. You can manage the tutorial here by changing the category to Proposals > In Progress then Proposals > Review as you complete the tutorial. Thanks!

Hi @divinef837 I’ll be reviewing your piece in 1 to 2 days

1 Like

@Phenzic this article was approved while celo sage was on Trello, here is a screenshot in case you are wondering why there are no votes on the piece