Skip to content

Commit 44d8d93

Browse files
Add talent plus with USDC
1 parent 9ecca9c commit 44d8d93

File tree

13 files changed

+687
-209
lines changed

13 files changed

+687
-209
lines changed

contracts/talent_plus/README.md

Lines changed: 69 additions & 29 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 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.
7+
The TalentPlus system enables anyone to purchase subscriptions for users using USDC (ERC20) 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

@@ -25,12 +25,12 @@ The core subscription management contract that handles subscription models and u
2525
#### Main Functions
2626

2727
**Subscription Model Management:**
28-
- `addSubscriptionModel(string subscriptionSlug, uint256 durationInSeconds, uint256 priceInEth, uint256 discountPercentage, uint256 talentRequiredForDiscount)`
29-
- `updateSubscriptionModel(string subscriptionSlug, uint256 durationInSeconds, uint256 priceInEth, uint256 discountPercentage, uint256 talentRequiredForDiscount)`
28+
- `addSubscriptionModel(string subscriptionSlug, uint256 durationInSeconds, uint256 price, uint256 discountPercentage, uint256 talentRequiredForDiscount)` - Price in USDC (6 decimals)
29+
- `updateSubscriptionModel(string subscriptionSlug, uint256 durationInSeconds, uint256 price, uint256 discountPercentage, uint256 talentRequiredForDiscount)` - Price in USDC (6 decimals)
3030
- `deactivateSubscriptionModel(string subscriptionSlug)`
3131

3232
**User Subscription Management:**
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.
33+
- `addUserSubscription(address wallet, string subscriptionSlug, address payer, uint256 pricePaid)` - Standard subscription with model-based duration. `payer` is who paid, `pricePaid` is the USDC amount paid.
3434
- `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.
3535

3636
**Query Functions:**
@@ -53,7 +53,9 @@ The core subscription management contract that handles subscription models and u
5353
struct SubscriptionModel {
5454
string subscriptionSlug;
5555
uint256 durationInSeconds;
56-
uint256 priceInTalent;
56+
uint256 price; // Price in USDC (6 decimals)
57+
uint256 discountPercentage;
58+
uint256 talentRequiredForDiscount;
5759
bool active;
5860
}
5961
@@ -66,45 +68,51 @@ struct UserActiveSubscription {
6668

6769
### 2. TalentPlus.sol
6870

69-
The payment and subscription creation contract that handles ETH payments and integrates with TalentPlusSubscription. Anyone can call the subscription functions.
71+
The payment and subscription creation contract that handles USDC (ERC20) payments and integrates with TalentPlusSubscription. Anyone can call the subscription functions.
7072

7173
#### Key Features
7274

73-
- **ETH Payment Integration**: Handles native ETH payments for subscriptions
75+
- **USDC Payment Integration**: Handles ERC20 USDC payments for subscriptions
7476
- **TALENT-Based Discounts**: Automatically applies discounts based on TALENT token holdings and vault staking
7577
- **Direct Access**: Anyone can create subscriptions by calling the subscribe function
7678
- **Dynamic Pricing**: Fetches subscription costs and applies discounts from TalentPlusSubscription contract
7779
- **Gift Subscriptions**: Anyone can purchase subscriptions for any wallet address
7880
- **Administrative Control**: Owner-managed contract settings
7981
- **Integration**: Seamless integration with TalentPlusSubscription
82+
- **SafeERC20**: Uses OpenZeppelin's SafeERC20 for secure token transfers
8083

8184
#### Main Functions
8285

8386
**Core Subscription:**
84-
- `subscribe(address wallet, string subscriptionSlug)` - Main subscription function (anyone can purchase for any wallet, requires ETH payment)
87+
- `subscribe(address wallet, string subscriptionSlug, uint256 tokenAmount)` - Main subscription function (anyone can purchase for any wallet, requires USDC payment). User must approve the contract to spend USDC first.
8588

8689
**Administrative:**
8790
- `setEnabled(bool _enabled)` - Enable/disable contract
8891
- `setDisabled()` - Disable contract
89-
- `updateReceiver(address _feeReceiver)` - Update ETH fee receiver address
92+
- `updateReceiver(address _feeReceiver)` - Update fee receiver address
9093
- `updateTalentPlusSubscription(address _talentPlusSubscriptionAddress)` - Update subscription contract address
9194

95+
#### Payment Token
96+
97+
- **USDC Address (Base)**: `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`
98+
- **Decimals**: 6
99+
- Users must approve the TalentPlus contract to spend USDC before subscribing
100+
92101
#### Events
93102

94103
```solidity
95-
event SubscriptionCreated(address indexed payer, address indexed recipient, string subscriptionSlug, uint256 finalPrice, bool discountApplied);
104+
event SubscriptionCreated(address indexed payer, address indexed recipient, string subscriptionSlug, uint256 pricePaid, bool discountApplied);
96105
```
97106

98107
TalentPlusSubscription also emits enriched subscription events capturing the payer and amount paid:
99108

100109
```solidity
101110
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);
103111
event UserSubscriptionExtended(address indexed wallet, string indexed subscriptionSlug, uint256 expirationTime, uint256 startTime, address payer, uint256 pricePaid);
104112
```
105113

106114
Notes:
107-
- For purchases through `TalentPlus.subscribe`, `payer` is the caller and `pricePaid` is the final ETH price after any discount.
115+
- For purchases through `TalentPlus.subscribe`, `payer` is the caller and `pricePaid` is the final USDC price after any discount.
108116
- For `addUserSubscriptionWithExpiration`, `payer = msg.sender` and `pricePaid = 0`.
109117

110118
## Integration Flow
@@ -116,19 +124,22 @@ sequenceDiagram
116124
participant User
117125
participant TalentPlus
118126
participant TalentPlusSubscription
127+
participant USDC_Token
119128
participant TALENT_Token
120129
participant FeeReceiver
121130
122-
User->>TalentPlus: subscribe(wallet, slug) + ETH
131+
User->>USDC_Token: approve(TalentPlus, amount)
132+
User->>TalentPlus: subscribe(wallet, slug, tokenAmount)
123133
TalentPlus->>TalentPlusSubscription: getSubscriptionModel(slug)
124134
TalentPlusSubscription-->>TalentPlus: (duration, price, discount%, talentRequired, active)
125135
TalentPlus->>TalentPlusSubscription: calculateDiscountedPrice(slug, wallet)
126136
TalentPlusSubscription->>TALENT_Token: balanceOf(wallet)
127137
TALENT_Token-->>TalentPlusSubscription: balance
128138
TalentPlusSubscription-->>TalentPlus: (finalPrice, discountApplied)
129-
TalentPlus->>TalentPlus: verify msg.value >= finalPrice
130-
TalentPlus->>FeeReceiver: transfer ETH
131-
TalentPlus->>TalentPlusSubscription: addUserSubscription(wallet, slug)
139+
TalentPlus->>TalentPlus: verify tokenAmount >= finalPrice
140+
TalentPlus->>USDC_Token: safeTransferFrom(user, feeReceiver, finalPrice)
141+
TalentPlus->>USDC_Token: safeTransfer(user, excess) [if any]
142+
TalentPlus->>TalentPlusSubscription: addUserSubscription(wallet, slug, user, finalPrice)
132143
TalentPlusSubscription-->>TalentPlus: success
133144
TalentPlus->>TalentPlus: emit SubscriptionCreated(user, wallet, slug, finalPrice, discountApplied)
134145
```
@@ -173,22 +184,28 @@ sequenceDiagram
173184
### Basic Subscription Purchase
174185

175186
```solidity
187+
// First, user must approve USDC spending
188+
usdc.approve(talentPlusAddress, parseUnits("1000", 6)); // Approve up to 1000 USDC
189+
176190
// User purchases subscription for themselves (full price)
177-
talentPlus.subscribe(userWallet, "premium", { value: parseEther("100") });
191+
// Price: 100 USDC = 100 * 10^6 = 100000000
192+
talentPlus.subscribe(userWallet, "premium", parseUnits("100", 6));
178193
179194
// User purchases subscription as a gift for another user (with discount)
180195
// Recipient has 5000 TALENT tokens, so gets 20% discount on premium subscription
181-
talentPlus.subscribe(recipientWallet, "premium", { value: parseEther("80") }); // 100 - 20% = 80 ETH
196+
// Discounted price: 80 USDC = 80 * 10^6 = 80000000
197+
talentPlus.subscribe(recipientWallet, "premium", parseUnits("80", 6)); // 100 - 20% = 80 USDC
182198
```
183199

184200
### Discount System Management (Admin)
185201

186202
```solidity
187203
// Admin creates subscription model with discount
204+
// Price in USDC (6 decimals): 100 USDC = 100 * 10^6 = 100000000
188205
talentPlusSubscription.addSubscriptionModel(
189206
"premium", // subscription slug
190207
90 * 24 * 60 * 60, // 90 days duration
191-
parseEther("100"), // 100 ETH base price
208+
parseUnits("100", 6), // 100 USDC base price
192209
20, // 20% discount
193210
parseEther("5000") // 5000 TALENT tokens required for discount
194211
);
@@ -197,13 +214,14 @@ talentPlusSubscription.addSubscriptionModel(
197214
talentPlusSubscription.updateSubscriptionModel(
198215
"premium",
199216
90 * 24 * 60 * 60,
200-
parseEther("100"),
217+
parseUnits("100", 6), // 100 USDC
201218
25, // Updated to 25% discount
202219
parseEther("10000") // Updated to 10000 TALENT tokens required
203220
);
204221
205222
// Check discounted price for a specific wallet
206223
(uint256 finalPrice, bool discountApplied) = talentPlusSubscription.calculateDiscountedPrice("premium", userWallet);
224+
// Returns price in USDC (6 decimals)
207225
```
208226

209227
### Custom Subscription Creation (Admin)
@@ -220,10 +238,13 @@ talentPlusSubscription.addUserSubscriptionWithExpiration(
220238

221239
```solidity
222240
// Add new subscription model
241+
// Price in USDC (6 decimals): 500 USDC = 500 * 10^6 = 500000000
223242
talentPlusSubscription.addSubscriptionModel(
224243
"enterprise",
225244
365 * 24 * 60 * 60, // 1 year
226-
ethers.utils.parseEther("500") // 500 TALENT
245+
parseUnits("500", 6), // 500 USDC
246+
0, // No discount
247+
parseEther("0") // No TALENT required
227248
);
228249
```
229250

@@ -254,20 +275,19 @@ talentPlusSubscription.addSubscriptionModel(
254275
### TalentPlusSubscription Events
255276

256277
```solidity
257-
event SubscriptionModelAdded(string indexed subscriptionSlug, uint256 durationInSeconds, uint256 priceInTalent);
258-
event SubscriptionModelUpdated(string indexed subscriptionSlug, uint256 durationInSeconds, uint256 priceInTalent);
259-
event SubscriptionModelDeactivated(string indexed subscriptionSlug);
260-
event UserSubscriptionAdded(address indexed wallet, string indexed subscriptionSlug, uint256 expirationTime, uint256 startTime);
261-
event UserSubscriptionReplaced(address indexed wallet, string indexed oldSlug, string indexed newSlug, uint256 expirationTime, uint256 startTime);
262-
event UserSubscriptionExtended(address indexed wallet, string indexed subscriptionSlug, uint256 expirationTime, uint256 startTime);
278+
event SubscriptionModelAdded(string subscriptionSlug, uint256 durationInSeconds, uint256 price, uint256 discountPercentage, uint256 talentRequiredForDiscount);
279+
event SubscriptionModelUpdated(string subscriptionSlug, uint256 durationInSeconds, uint256 price, uint256 discountPercentage, uint256 talentRequiredForDiscount);
280+
event SubscriptionModelDeactivated(string subscriptionSlug);
281+
event UserSubscriptionAdded(address indexed wallet, string subscriptionSlug, uint256 expirationTime, uint256 startTime, address payer, uint256 pricePaid);
282+
event UserSubscriptionExtended(address indexed wallet, string subscriptionSlug, uint256 expirationTime, uint256 startTime, address payer, uint256 pricePaid);
263283
event TrustedSignerAdded(address indexed signer);
264284
event TrustedSignerRemoved(address indexed signer);
265285
```
266286

267287
### TalentPlus Events
268288

269289
```solidity
270-
event SubscriptionCreated(address indexed user, string subscriptionSlug);
290+
event SubscriptionCreated(address indexed payer, address indexed recipient, string subscriptionSlug, uint256 pricePaid, bool discountApplied);
271291
```
272292

273293
## Deployment
@@ -329,12 +349,32 @@ The TalentPlus system integrates with the Talent Vault contract to provide enhan
329349

330350
## Dependencies
331351

332-
- **OpenZeppelin Contracts**: `Ownable`, `ReentrancyGuard`, `IERC20`
352+
- **OpenZeppelin Contracts**: `Ownable`, `ReentrancyGuard`, `IERC20`, `SafeERC20`
333353
- **Solidity**: `^0.8.24`
334354

355+
## Payment Token Configuration
356+
357+
### USDC Token (Base Network)
358+
- **Address**: `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`
359+
- **Decimals**: 6
360+
- **Symbol**: USDC
361+
362+
### Price Format
363+
All prices in the system are stored in USDC units with 6 decimals:
364+
- $1 USDC = `1000000` (1 * 10^6)
365+
- $20 USDC = `20000000` (20 * 10^6)
366+
- $100 USDC = `100000000` (100 * 10^6)
367+
368+
### User Approval Required
369+
Before subscribing, users must approve the TalentPlus contract to spend USDC:
370+
```solidity
371+
usdc.approve(talentPlusAddress, amount);
372+
```
373+
335374
## Network Configuration
336375

337376
The contracts support both mainnet and testnet deployments with network-specific configurations for:
338377
- TALENT token addresses
339378
- Vault contract addresses
340379
- Fee receiver addresses
380+
- Payment token addresses (USDC)

contracts/talent_plus/TalentPlus.sol

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,29 @@ pragma solidity ^0.8.24;
44
import "./TalentPlusSubscription.sol";
55
import "@openzeppelin/contracts/access/Ownable.sol";
66
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
7+
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
8+
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
79

810
contract TalentPlus is Ownable, ReentrancyGuard {
11+
using SafeERC20 for IERC20;
912

1013
address public feeReceiver;
1114
TalentPlusSubscription public talentPlusSubscription;
15+
address public paymentToken;
1216

13-
event SubscriptionCreated(address indexed payer, address indexed recipient, string subscriptionSlug, uint256 finalPrice, bool discountApplied);
17+
event SubscriptionCreated(address indexed payer, address indexed recipient, string subscriptionSlug, uint256 pricePaid, bool discountApplied);
1418

1519
bool public enabled;
1620

1721
constructor(
1822
address _talentPlusSubscriptionAddress,
19-
address _feeReceiver
23+
address _feeReceiver,
24+
address _paymentToken
2025
) Ownable(msg.sender) {
26+
require(_paymentToken != address(0), "Invalid payment token address");
2127
talentPlusSubscription = TalentPlusSubscription(_talentPlusSubscriptionAddress);
2228
feeReceiver = _feeReceiver;
29+
paymentToken = _paymentToken;
2330
enabled = true;
2431
}
2532

@@ -60,13 +67,19 @@ contract TalentPlus is Ownable, ReentrancyGuard {
6067
}
6168

6269
/**
63-
* @notice Creates a subscription for a specified wallet.
70+
* @notice Creates a subscription for a specified wallet using the payment token.
6471
* @param wallet The wallet address to create the subscription for.
6572
* @param subscriptionSlug The subscription slug to set in TalentPlusSubscription.
73+
* @param tokenAmount The amount of tokens to pay (must be >= required amount).
6674
* @dev Can be called by anyone. TalentPlus contract is a trusted signer in TalentPlusSubscription.
67-
* @dev Requires ETH payment equal to the subscription cost.
75+
* @dev Requires token payment. User must have approved this contract to spend tokens.
76+
* @dev Uses SafeERC20 for secure token transfers.
6877
*/
69-
function subscribe(address wallet, string memory subscriptionSlug) public payable nonReentrant {
78+
function subscribe(
79+
address wallet,
80+
string memory subscriptionSlug,
81+
uint256 tokenAmount
82+
) public nonReentrant {
7083
require(enabled, "Subscription is disabled for this contract");
7184
require(wallet != address(0), "Invalid wallet address");
7285
require(bytes(subscriptionSlug).length > 0, "Subscription slug cannot be empty");
@@ -77,18 +90,17 @@ contract TalentPlus is Ownable, ReentrancyGuard {
7790

7891
// Calculate discounted price based on TALENT holdings
7992
(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");
85-
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");
93+
require(tokenAmount >= finalPrice, "Insufficient token payment");
94+
95+
// Transfer tokens from user to fee receiver using SafeERC20
96+
IERC20(paymentToken).safeTransferFrom(msg.sender, feeReceiver, finalPrice);
97+
98+
// Refund excess tokens if any
99+
if (tokenAmount > finalPrice) {
100+
uint256 excessAmount = tokenAmount - finalPrice;
101+
IERC20(paymentToken).safeTransfer(msg.sender, excessAmount);
90102
}
91-
103+
92104
// Set the subscription for the target wallet in TalentPlusSubscription
93105
talentPlusSubscription.addUserSubscription(wallet, subscriptionSlug, msg.sender, finalPrice);
94106

0 commit comments

Comments
 (0)