Package referral implements a referral system on Gno. It allows authorized contracts to register, update, or remove referral relationships. A referral link is defined as a mapping from one address (the "user") to another address (the "referrer").
## Overview
The referral package is composed of the following components:
- **errors.gno**: Defines error types for various failure scenarios such as invalid address, unauthorized caller, self-referral, rate limit exceeded, and referral not found.
- **utils.gno**: Contains the isValidCaller function that checks if the caller has an authorized role (governance, router, position, staker, or launchpad contracts).
- **type.gno**: Defines the ReferralKeeper interface and event type constants for register, update, and remove operations.
- **keeper.gno**: Implements the ReferralKeeper interface using an tree for storage. Includes rate limiting (24-hour cooldown) to prevent abuse of referral operations.
- **global_keeper.gno**: Exposes the public API functions that external contracts can use to interact with the referral system.
## Public API
The package exposes the following public functions:
- GetReferral(addr string) string: Returns the referrer address for the given user address. Returns empty string if not found.
- HasReferral(addr string) bool: Returns true if the user has a registered referrer.
- IsEmpty() bool: Returns true if no referral relationships exist.
- GetLastOpTimestamp(addr string) (int64, error): Returns the last referral operation timestamp for the given user address.
- TryRegister(cur realm, addr address, referral string) string: Attempts to register a new referral and returns the effective referrer. Empty input returns the user's stored referral without attempting registration.
## Workflow
Typical usage of this contract follows these steps:
- An authorized contract (router, staker, etc.) calls TryRegister to resolve the effective referrer and optionally create a referral relationship.
- The keeper validates the caller's permissions via isValidCaller.
- Address validation ensures both addresses are valid and not self-referencing.
- Rate limiting checks prevent operations more than once per 24 hours.
- The referral is stored in the tree and an event is emitted when registration occurs.
## Authorized Callers
Only contracts with the following roles can modify referral data:
- ROLE_GOVERNANCE: Governance contracts
- ROLE_GOV_STAKER: Governance staker contracts
- ROLE_ROUTER: Router contracts
- ROLE_POSITION: Position manager contracts
- ROLE_STAKER: Staker contracts
- ROLE_LAUNCHPAD: Launchpad contracts
## Rate Limiting
To prevent abuse, the system enforces a 24-hour cooldown period between operations for each address. This means:
- A new referral can only be registered once per 24 hours per address
- Updates and removals are also subject to the same rate limit
- Attempting operations within the cooldown period returns ErrTooManyRequests
## Events
The package emits the following events:
- RegisterReferral: Emitted when a new referral is created
- ReferralRegistrationFailed: Emitted when non-empty referral registration fails
## Error Handling
The package defines several error types:
- `ErrInvalidAddress`: Returned when an address format is invalid
- `ErrSelfReferral`: Returned when attempting to set self as referrer
- `ErrUnauthorized`: Returned when the caller lacks permission
- `ErrTooManyRequests`: Returned when rate limit is exceeded (24-hour cooldown)
- `ErrNotFound`: Returned when attempting to get a non-existent referral
- `ErrInvalidTime`: Returned when the stored timestamp format is invalid
## Example: Integration with Router Contract
The router contract can register referrals during swap operations:
```go
Example
1import (
2 "gno.land/r/gnoswap/referral"
3)
4
5func SwapWithReferral(cur realm, referralCode string, ...) {
6 // Get the caller address
7 caller := cur.Previous().Address()
8
9 actualReferrer := referral.TryRegister(cross(cur), caller, referralCode)
10
11 // Continue with swap logic...
12}
```
## Example: Checking Referral for Rewards
Other contracts can check referral relationships for reward distribution:
```go
Example
1import (
2 "gno.land/r/gnoswap/referral"
3)
4
5func DistributeRewards(user address, amount uint64) {
6 // Check if user has a referrer
7 if referral.HasReferral(user.String()) {
8 referrerAddr := referral.GetReferral(user.String())
9 // Calculate and distribute referral bonus
10 referrerBonus := amount * referralRate / 100
11 sendReward(address(referrerAddr), referrerBonus)
12 }
13}
```
## Limitations and Constraints
- A user can have only one referrer at a time
- Self-referral is not allowed
- Operations are rate-limited to once per 24 hours per address
- Only authorized contracts can register/update/remove referrals
- Zero address as referrer triggers removal of the referral
## Notes
- The contract uses RBAC (Role-Based Access Control) for authorization
- Rate limiting state persists across transactions
- Events are emitted for all state changes for off-chain tracking