Loyalty Points and Rewards Module

Introduction

Loyalty programs are popular tools for businesses to enhance customer retention and increase engagement. A blockchain-based loyalty points system offers transparency, security, and ease of use for both the issuer and the customer.

Prerequisites

  • Basic understanding of blockchain technology.
  • Familiarity with Go programming language.
  • Ignite CLI installed on your machine.

Steps to Create the Module

Step 1: Scaffold a New Blockchain

Create a new blockchain project using Ignite CLI:

ignite scaffold chain loyaltychain --no-module && cd loyaltychain

Step 2: Scaffold the Loyalty Module

Generate the loyalty module with:

ignite scaffold module loyalty

Step 3: Define Data Structures for Points and Rewards

Scaffold the data structures required for managing loyalty points and rewards. For example, define a Point type to represent loyalty points and a Reward type to represent redeemable items or services:

ignite scaffold map Point owner balance:int --module loyalty
ignite scaffold map Reward item points:int --module loyalty

Step 4: Implement Points Issuance and Redemption Functionality

The IssuePoints function will be responsible for crediting loyalty points to a user's account. This operation will typically be called by the business logic layer after a customer performs an action that earns them points, like making a purchase.

In your x/loyalty/keeper/keeper.go, implement the logic for issuing loyalty points:

package keeper

import (
    // ...
    errorsmod "cosmossdk.io/errors"
    sdk "github.com/cosmos/cosmos-sdk/types"
    sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
    "loyaltychain/x/loyalty/types" 
)

// IssuePoints credits points to the owner's balance.
func (k Keeper) IssuePoints(ctx sdk.Context, owner string, amount int) error {
	if amount <= 0 {
		return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "amount must be positive")
	}

	// Convert the owner string to an sdk.AccAddress
	_, err := sdk.AccAddressFromBech32(owner)
	if err != nil {
		return errorsmod.Wrap(sdkerrors.ErrInvalidAddress, "owner address is invalid")
	}

	// Convert amount to int32
	amountInt32 := int32(amount)

	// Get the current balance or initialize a new one
	pointBalance, found := k.GetPoint(ctx, owner)
	if !found {
		pointBalance = types.Point{
			Owner:   owner,
			Balance: 0,
		}
	}

	// Update the balance
	pointBalance.Balance += amountInt32

	// Set the updated balance back to the store
	k.SetPoint(ctx, pointBalance)

	return nil
}

The RedeemReward function will allow users to exchange their loyalty points for rewards. The function will check if the user has enough points to redeem the requested reward and then deduct the appropriate amount from their balance.

Implement the logic for redeeming rewards with loyalty points:

// RedeemReward exchanges points for a specified reward item.
func (k Keeper) RedeemReward(ctx sdk.Context, owner string, rewardItem string) error {
	// Convert the owner string to an sdk.AccAddress
	_, err := sdk.AccAddressFromBech32(owner)
	if err != nil {
		return errorsmod.Wrap(sdkerrors.ErrInvalidAddress, "owner address is invalid")
	}

	// Check if the reward exists
	reward, found := k.GetReward(ctx, rewardItem)
	if !found {
		return errorsmod.Wrap(types.ErrRewardNotFound, "reward does not exist")
	}

	// Get the owner's point balance
	pointBalance, found := k.GetPoint(ctx, owner)
	if !found || pointBalance.Balance < reward.Points {
		return errorsmod.Wrap(types.ErrInsufficientPoints, "insufficient points to redeem reward")
	}

	// Deduct the points for the reward
	pointBalance.Balance -= reward.Points

	// Set the updated balance back to the store
	k.SetPoint(ctx, pointBalance)

	// Emit an event for the reward redemption
	ctx.EventManager().EmitEvent(
		sdk.NewEvent(string(types.EventTypeRedeemReward),
			sdk.NewAttribute(string(types.AttributeKeyReward), rewardItem),
			sdk.NewAttribute(string(types.AttributeKeyOwner), owner),
			sdk.NewAttribute(string(types.AttributeKeyPoints), fmt.Sprintf("%d", reward.Points)),
		),
	)

	return nil
}

Add the two new error types to the x/loyalty/types/errors.go

	ErrInvalidSigner      = sdkerrors.Register(ModuleName, 1100, "expected gov account as only signer for proposal message")
	ErrSample             = sdkerrors.Register(ModuleName, 1101, "sample error")
	ErrRewardNotFound     = sdkerrors.Register(ModuleName, 1102, "Rewards Not Found")
	ErrInsufficientPoints = sdkerrors.Register(ModuleName, 1103, "Insufficient Points")

Add your EventTypes and Attribute Keys to the x/loyalty/types/types.go

package types

type EventType string
type AttributeKey string

const (
	EventTypeRedeemReward EventType    = "RedeemReward"
	AttributeKeyReward    AttributeKey = "reward"
	AttributeKeyOwner     AttributeKey = "owner"
	AttributeKeyPoints    AttributeKey = "points"
)

Step 5: Add CLI and gRPC Configurations

Ensure your module's CLI commands and gRPC endpoints are properly configured for managing loyalty points and redeeming rewards.

Step 6: Test the Module

After implementing the module, test issuing points and redeeming rewards using CLI commands or gRPC clients.

Conclusion

With these steps, you have built the foundation for a Loyalty Points and Rewards module. This module can be further expanded to include features like tier-based rewards, time-limited offers, and integration with point-of-sale systems.

Select a repo