Skip to content

Commit b60be0a

Browse files
Use eth for payments and add discounts
1 parent 3837851 commit b60be0a

8 files changed

Lines changed: 502 additions & 245 deletions

File tree

contracts/talent_plus/README.md

Lines changed: 109 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ This directory contains the core smart contracts for the TalentPlus subscription
44

55
## Overview
66

7-
The TalentPlus system enables trusted signers to purchase subscriptions for users using TALENT tokens, with flexible subscription models and administrative capabilities for custom subscription management. Trusted signers can purchase subscriptions for any wallet address, enabling gift subscriptions and corporate subscription management.
7+
The TalentPlus system enables anyone to purchase subscriptions for users using ETH payments, with flexible subscription models, TALENT token-based discounts (including vault staking), and administrative capabilities for custom subscription management. Users can purchase subscriptions for any wallet address, enabling gift subscriptions and corporate subscription management.
88

99
## Contracts
1010

@@ -15,6 +15,8 @@ The core subscription management contract that handles subscription models and u
1515
#### Key Features
1616

1717
- **Subscription Model Management**: Create, update, and deactivate subscription models
18+
- **TALENT Token Integration**: Check TALENT token balances and vault staking for discount eligibility
19+
- **Dynamic Discount System**: Apply discounts based on combined TALENT holdings (balance + vault staking)
1820
- **User Subscription Management**: Add, upgrade, and extend user subscriptions
1921
- **Custom Expiration Support**: Administrative function to set custom expiration times
2022
- **Access Control**: Owner and trusted signer permissions
@@ -23,20 +25,22 @@ The core subscription management contract that handles subscription models and u
2325
#### Main Functions
2426

2527
**Subscription Model Management:**
26-
- `addSubscriptionModel(string subscriptionSlug, uint256 durationInSeconds, uint256 priceInTalent)`
27-
- `updateSubscriptionModel(string subscriptionSlug, uint256 durationInSeconds, uint256 priceInTalent)`
28+
- `addSubscriptionModel(string subscriptionSlug, uint256 durationInSeconds, uint256 priceInEth, uint256 discountPercentage, uint256 talentRequiredForDiscount)`
29+
- `updateSubscriptionModel(string subscriptionSlug, uint256 durationInSeconds, uint256 priceInEth, uint256 discountPercentage, uint256 talentRequiredForDiscount)`
2830
- `deactivateSubscriptionModel(string subscriptionSlug)`
2931

3032
**User Subscription Management:**
31-
- `addUserSubscription(address wallet, string subscriptionSlug)` - Standard subscription with model-based duration
32-
- `addUserSubscriptionWithExpiration(address wallet, uint256 expirationTime)` - Custom expiration time (sets slug as "custom")
33+
- `addUserSubscription(address wallet, string subscriptionSlug, address payer, uint256 pricePaid)` - Standard subscription with model-based duration. `payer` is who paid, `pricePaid` is the ETH amount paid.
34+
- `addUserSubscriptionWithExpiration(address wallet, uint256 expirationTime)` - Custom expiration time (sets slug as "custom"). For these admin-set subscriptions, `payer` is `msg.sender` and `pricePaid` is `0` in the emitted events.
3335

3436
**Query Functions:**
3537
- `hasActiveSubscription(address wallet)`
3638
- `hasActiveSubscriptionForModel(address wallet, string subscriptionSlug)`
3739
- `getCurrentActiveSubscription(address wallet)`
3840
- `getSubscriptionExpiration(address wallet)`
3941
- `getSubscriptionStartTime(address wallet)`
42+
- `getSubscriptionModel(string subscriptionSlug)` - Returns model details including discount info
43+
- `calculateDiscountedPrice(string subscriptionSlug, address wallet)` - Calculates final price with discount applied based on TALENT balance + vault staking
4044

4145
**Access Control:**
4246
- `addTrustedSigner(address signer)`
@@ -62,54 +66,71 @@ struct UserActiveSubscription {
6266

6367
### 2. TalentPlus.sol
6468

65-
The payment and subscription creation contract that handles TALENT token payments and integrates with TalentPlusSubscription. Only trusted signers can call the subscription functions.
69+
The payment and subscription creation contract that handles ETH payments and integrates with TalentPlusSubscription. Anyone can call the subscription functions.
6670

6771
#### Key Features
6872

69-
- **TALENT Token Integration**: Handles ERC20 token payments for subscriptions
70-
- **Direct Access Control**: Only trusted signers can create subscriptions
71-
- **Dynamic Pricing**: Fetches subscription costs from TalentPlusSubscription contract
72-
- **Gift Subscriptions**: Trusted signers can purchase subscriptions for any wallet address
73+
- **ETH Payment Integration**: Handles native ETH payments for subscriptions
74+
- **TALENT-Based Discounts**: Automatically applies discounts based on TALENT token holdings and vault staking
75+
- **Direct Access**: Anyone can create subscriptions by calling the subscribe function
76+
- **Dynamic Pricing**: Fetches subscription costs and applies discounts from TalentPlusSubscription contract
77+
- **Gift Subscriptions**: Anyone can purchase subscriptions for any wallet address
7378
- **Administrative Control**: Owner-managed contract settings
7479
- **Integration**: Seamless integration with TalentPlusSubscription
7580

7681
#### Main Functions
7782

7883
**Core Subscription:**
79-
- `subscribe(address wallet, string subscriptionSlug)` - Main subscription function (trusted signers can purchase for any wallet)
84+
- `subscribe(address wallet, string subscriptionSlug)` - Main subscription function (anyone can purchase for any wallet, requires ETH payment)
8085

8186
**Administrative:**
8287
- `setEnabled(bool _enabled)` - Enable/disable contract
8388
- `setDisabled()` - Disable contract
84-
- `updateReceiver(address _feeReceiver)` - Update fee receiver address
89+
- `updateReceiver(address _feeReceiver)` - Update ETH fee receiver address
8590
- `updateTalentPlusSubscription(address _talentPlusSubscriptionAddress)` - Update subscription contract address
8691

8792
#### Events
8893

8994
```solidity
90-
event SubscriptionCreated(address indexed payer, address indexed recipient, string subscriptionSlug);
95+
event SubscriptionCreated(address indexed payer, address indexed recipient, string subscriptionSlug, uint256 finalPrice, bool discountApplied);
9196
```
9297

98+
TalentPlusSubscription also emits enriched subscription events capturing the payer and amount paid:
99+
100+
```solidity
101+
event UserSubscriptionAdded(address indexed wallet, string indexed subscriptionSlug, uint256 expirationTime, uint256 startTime, address payer, uint256 pricePaid);
102+
event UserSubscriptionReplaced(address indexed wallet, string indexed oldSlug, string indexed newSlug, uint256 expirationTime, uint256 startTime, address payer, uint256 pricePaid);
103+
event UserSubscriptionExtended(address indexed wallet, string indexed subscriptionSlug, uint256 expirationTime, uint256 startTime, address payer, uint256 pricePaid);
104+
```
105+
106+
Notes:
107+
- For purchases through `TalentPlus.subscribe`, `payer` is the caller and `pricePaid` is the final ETH price after any discount.
108+
- For `addUserSubscriptionWithExpiration`, `payer = msg.sender` and `pricePaid = 0`.
109+
93110
## Integration Flow
94111

95112
### 1. Subscription Purchase Flow
96113

97114
```mermaid
98115
sequenceDiagram
99-
participant TrustedSigner
116+
participant User
100117
participant TalentPlus
101118
participant TalentPlusSubscription
102119
participant TALENT_Token
103120
participant FeeReceiver
104121
105-
TrustedSigner->>TalentPlus: subscribe(wallet, slug)
122+
User->>TalentPlus: subscribe(wallet, slug) + ETH
106123
TalentPlus->>TalentPlusSubscription: getSubscriptionModel(slug)
107-
TalentPlusSubscription-->>TalentPlus: (duration, price, active)
108-
TalentPlus->>TalentPlus: verify msg.sender == trustedSigner
109-
TalentPlus->>TALENT_Token: transferFrom(trustedSigner, feeReceiver, price)
124+
TalentPlusSubscription-->>TalentPlus: (duration, price, discount%, talentRequired, active)
125+
TalentPlus->>TalentPlusSubscription: calculateDiscountedPrice(slug, wallet)
126+
TalentPlusSubscription->>TALENT_Token: balanceOf(wallet)
127+
TALENT_Token-->>TalentPlusSubscription: balance
128+
TalentPlusSubscription-->>TalentPlus: (finalPrice, discountApplied)
129+
TalentPlus->>TalentPlus: verify msg.value >= finalPrice
130+
TalentPlus->>FeeReceiver: transfer ETH
110131
TalentPlus->>TalentPlusSubscription: addUserSubscription(wallet, slug)
111132
TalentPlusSubscription-->>TalentPlus: success
112-
TalentPlus->>TalentPlus: emit SubscriptionCreated(trustedSigner, wallet, slug)
133+
TalentPlus->>TalentPlus: emit SubscriptionCreated(user, wallet, slug, finalPrice, discountApplied)
113134
```
114135

115136
### 2. Custom Subscription Flow
@@ -131,8 +152,8 @@ sequenceDiagram
131152
### 1. Trusted Signer Relationship
132153

133154
- **TalentPlus** contract must be added as a trusted signer in **TalentPlusSubscription**
134-
- Only addresses designated as trusted signers can call `subscribe()` function
135-
- Trusted signers pay for subscriptions and can purchase them for any wallet address
155+
- Anyone can call `subscribe()` function to purchase subscriptions
156+
- Users pay for subscriptions and can purchase them for any wallet address
136157
- This enables gift subscriptions and corporate subscription management
137158

138159
### 2. Dynamic Pricing
@@ -152,11 +173,37 @@ sequenceDiagram
152173
### Basic Subscription Purchase
153174

154175
```solidity
155-
// Trusted signer purchases subscription for a user
156-
talentPlus.subscribe(userWallet, "premium");
176+
// User purchases subscription for themselves (full price)
177+
talentPlus.subscribe(userWallet, "premium", { value: parseEther("100") });
178+
179+
// User purchases subscription as a gift for another user (with discount)
180+
// Recipient has 5000 TALENT tokens, so gets 20% discount on premium subscription
181+
talentPlus.subscribe(recipientWallet, "premium", { value: parseEther("80") }); // 100 - 20% = 80 ETH
182+
```
183+
184+
### Discount System Management (Admin)
185+
186+
```solidity
187+
// Admin creates subscription model with discount
188+
talentPlusSubscription.addSubscriptionModel(
189+
"premium", // subscription slug
190+
90 * 24 * 60 * 60, // 90 days duration
191+
parseEther("100"), // 100 ETH base price
192+
20, // 20% discount
193+
parseEther("5000") // 5000 TALENT tokens required for discount
194+
);
157195
158-
// Trusted signer purchases subscription as a gift for another user
159-
talentPlus.subscribe(recipientWallet, "premium");
196+
// Admin updates discount parameters
197+
talentPlusSubscription.updateSubscriptionModel(
198+
"premium",
199+
90 * 24 * 60 * 60,
200+
parseEther("100"),
201+
25, // Updated to 25% discount
202+
parseEther("10000") // Updated to 10000 TALENT tokens required
203+
);
204+
205+
// Check discounted price for a specific wallet
206+
(uint256 finalPrice, bool discountApplied) = talentPlusSubscription.calculateDiscountedPrice("premium", userWallet);
160207
```
161208

162209
### Custom Subscription Creation (Admin)
@@ -184,12 +231,12 @@ talentPlusSubscription.addSubscriptionModel(
184231

185232
### 1. Access Control
186233
- **Owner**: Can manage contract settings and trusted signers
187-
- **Trusted Signers**: Can create subscriptions for any wallet address (pay for subscriptions)
188-
- **Regular Users**: Cannot directly call subscription functions
234+
- **Anyone**: Can create subscriptions for any wallet address (pay for subscriptions)
235+
- **Regular Users**: Can directly call subscription functions
189236

190237
### 2. Direct Access Control
191-
- All subscriptions require the trusted signer to call the function directly
192-
- Simple `msg.sender == trustedSigner` check instead of complex signature verification
238+
- All subscriptions can be created by anyone calling the function directly
239+
- Simple public function access instead of complex signature verification
193240
- Prevents unauthorized subscription creation
194241
- More gas-efficient than cryptographic signature verification
195242

@@ -241,25 +288,53 @@ npx hardhat test test/contracts/talent_plus/
241288
## Architecture Benefits
242289

243290
### Simplified Design
244-
- **Direct Access Control**: Uses simple `msg.sender` checks instead of complex signature verification
291+
- **Direct Access**: Uses simple public function calls instead of complex signature verification
245292
- **Gas Efficient**: Removes expensive cryptographic operations
246-
- **Easy to Understand**: Straightforward logic - only trusted signers can create subscriptions
293+
- **Easy to Understand**: Straightforward logic - anyone can create subscriptions
247294
- **Maintainable**: Less complex code means fewer potential bugs and easier maintenance
248295

249296
### Use Cases
250-
- **Gift Subscriptions**: Trusted signers can purchase subscriptions for any wallet
297+
- **Gift Subscriptions**: Anyone can purchase subscriptions for any wallet
251298
- **Corporate Subscriptions**: Companies can manage subscriptions for their employees
252299
- **Promotional Subscriptions**: Marketing teams can give away subscriptions
253300
- **Admin-Managed Subscriptions**: Administrators can create subscriptions for users
301+
- **TALENT Holder Rewards**: Users with TALENT tokens or vault staking get automatic discounts
302+
- **Tiered Pricing**: Different discount levels based on combined TALENT holdings (balance + vault staking)
303+
304+
### Discount System Benefits
305+
- **Automatic Discounts**: No manual intervention required - discounts applied automatically
306+
- **TALENT Token Utility**: Increases value and utility of TALENT tokens and vault staking
307+
- **Flexible Configuration**: Admins can adjust discount percentages and requirements
308+
- **Transparent Pricing**: Users can check their discounted price before purchasing
309+
- **Fair Access**: Discounts based on actual token holdings and vault staking, not arbitrary criteria
310+
311+
## Vault Integration
312+
313+
The TalentPlus system integrates with the Talent Vault contract to provide enhanced discount eligibility based on staked TALENT tokens.
314+
315+
### Vault Address
316+
- **Mainnet**: `0x23Ff3256A29847d7EF760943bd6679b565CbdE5a`
317+
- **Testnet**: `0x23Ff3256A29847d7EF760943bd6679b565CbdE5a` (same address)
318+
319+
### How Vault Staking Works
320+
1. **Combined Holdings**: Discount eligibility is calculated using `TALENT balance + vault staked amount`
321+
2. **Automatic Detection**: The system automatically checks both wallet balance and vault staking
322+
3. **Seamless Integration**: Users don't need to do anything special - staking automatically qualifies them for discounts
323+
4. **Flexible Requirements**: Admins can set discount thresholds that consider both sources of TALENT holdings
324+
325+
### Example Scenarios
326+
- **Scenario 1**: User has 500 TALENT in wallet + 500 TALENT staked in vault = 1000 TALENT total → qualifies for discount
327+
- **Scenario 2**: User has 0 TALENT in wallet + 1000 TALENT staked in vault = 1000 TALENT total → qualifies for discount
328+
- **Scenario 3**: User has 1000 TALENT in wallet + 0 TALENT staked in vault = 1000 TALENT total → qualifies for discount
254329

255330
## Dependencies
256331

257-
- **OpenZeppelin Contracts**: `Ownable`, `ReentrancyGuard`, `IERC20`, `SafeERC20`
332+
- **OpenZeppelin Contracts**: `Ownable`, `ReentrancyGuard`, `IERC20`
258333
- **Solidity**: `^0.8.24`
259334

260335
## Network Configuration
261336

262337
The contracts support both mainnet and testnet deployments with network-specific configurations for:
263338
- TALENT token addresses
339+
- Vault contract addresses
264340
- Fee receiver addresses
265-
- Trusted signer addresses

contracts/talent_plus/TalentPlus.sol

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,25 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity ^0.8.24;
33

4-
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
5-
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
64
import "./TalentPlusSubscription.sol";
75
import "@openzeppelin/contracts/access/Ownable.sol";
86
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
97

108
contract TalentPlus is Ownable, ReentrancyGuard {
11-
using SafeERC20 for IERC20;
129

13-
// TALENT token address
14-
IERC20 public immutable TALENT_TOKEN;
15-
16-
address public trustedSigner;
1710
address public feeReceiver;
1811
TalentPlusSubscription public talentPlusSubscription;
1912

20-
event SubscriptionCreated(address indexed payer, address indexed recipient, string subscriptionSlug);
13+
event SubscriptionCreated(address indexed payer, address indexed recipient, string subscriptionSlug, uint256 finalPrice, bool discountApplied);
2114

2215
bool public enabled;
2316

2417
constructor(
25-
address _trustedSigner,
2618
address _talentPlusSubscriptionAddress,
27-
address _feeReceiver,
28-
address _talentTokenAddress
19+
address _feeReceiver
2920
) Ownable(msg.sender) {
30-
trustedSigner = _trustedSigner;
3121
talentPlusSubscription = TalentPlusSubscription(_talentPlusSubscriptionAddress);
3222
feeReceiver = _feeReceiver;
33-
TALENT_TOKEN = IERC20(_talentTokenAddress);
3423
enabled = true;
3524
}
3625

@@ -74,24 +63,35 @@ contract TalentPlus is Ownable, ReentrancyGuard {
7463
* @notice Creates a subscription for a specified wallet.
7564
* @param wallet The wallet address to create the subscription for.
7665
* @param subscriptionSlug The subscription slug to set in TalentPlusSubscription.
77-
* @dev Only the trusted signer can call this function. TalentPlus contract is a trusted signer in TalentPlusSubscription.
66+
* @dev Can be called by anyone. TalentPlus contract is a trusted signer in TalentPlusSubscription.
67+
* @dev Requires ETH payment equal to the subscription cost.
7868
*/
79-
function subscribe(address wallet, string memory subscriptionSlug) public nonReentrant {
69+
function subscribe(address wallet, string memory subscriptionSlug) public payable nonReentrant {
8070
require(enabled, "Subscription is disabled for this contract");
8171
require(wallet != address(0), "Invalid wallet address");
8272
require(bytes(subscriptionSlug).length > 0, "Subscription slug cannot be empty");
83-
require(msg.sender == trustedSigner, "Only trusted signer can create subscriptions");
8473

85-
// Get the subscription model details including price
86-
(, uint256 subscriptionCost, bool isActive) = talentPlusSubscription.getSubscriptionModel(subscriptionSlug);
74+
// Get the subscription model details and calculate discounted price
75+
(, , , , bool isActive) = talentPlusSubscription.getSubscriptionModel(subscriptionSlug);
8776
require(isActive, "Subscription model is not active");
77+
78+
// Calculate discounted price based on TALENT holdings
79+
(uint256 finalPrice, bool discountApplied,) = talentPlusSubscription.calculateDiscountedPrice(subscriptionSlug, wallet);
80+
require(msg.value >= finalPrice, "Insufficient ETH payment");
81+
82+
// Transfer ETH to fee receiver
83+
(bool success, ) = feeReceiver.call{value: finalPrice}("");
84+
require(success, "ETH transfer failed");
8885

89-
// Transfer TALENT tokens from trusted signer to fee receiver
90-
TALENT_TOKEN.safeTransferFrom(msg.sender, feeReceiver, subscriptionCost);
86+
// Refund excess ETH if any
87+
if (msg.value > finalPrice) {
88+
(bool refundSuccess, ) = msg.sender.call{value: msg.value - finalPrice}("");
89+
require(refundSuccess, "ETH refund failed");
90+
}
9191

9292
// Set the subscription for the target wallet in TalentPlusSubscription
93-
talentPlusSubscription.addUserSubscription(wallet, subscriptionSlug);
93+
talentPlusSubscription.addUserSubscription(wallet, subscriptionSlug, msg.sender, finalPrice);
9494

95-
emit SubscriptionCreated(msg.sender, wallet, subscriptionSlug);
95+
emit SubscriptionCreated(msg.sender, wallet, subscriptionSlug, finalPrice, discountApplied);
9696
}
9797
}

0 commit comments

Comments
 (0)