Overview
The Subscription contract handles user subscription plans for accessing premium DCA (Dollar-Cost Averaging) features on GweAI. Plans are purchased with USDC and grant time-based access.
Contract Address: 0xcFbdEaba321700A9C125b41dB6bBd6BBBA752287
Verification: View on BaseScan
Subscription Plans
Plan Price Duration Features Free $0 Forever Basic trading Monthly $2 USDC 30 days DCA strategies, analytics Yearly $20 USDC 365 days DCA strategies, analytics, priority support
Plan Type Enum
enum PlanType {
FREE , // 0
MONTHLY , // 1
YEARLY // 2
}
Key Features
USDC-based payments (6 decimals)
Automatic expiry tracking
Access control for DCA features
Owner delegation system
Plan upgrade/downgrade support
Treasury payment routing
Main Functions
purchasePlan
Purchase or upgrade a subscription plan.
The plan to purchase: 0 (FREE), 1 (MONTHLY), or 2 (YEARLY)
Function Signature: 0x98693010
Process:
User approves USDC spending to subscription contract
User calls purchasePlan(1) for Monthly or purchasePlan(2) for Yearly
Contract transfers USDC from user to treasury
Contract updates user’s subscription and expiry timestamp
PlanPurchased event is emitted
You must approve the subscription contract to spend USDC before calling purchasePlan. The frontend handles this automatically.
getSubscription
Get subscription details for a user.
Address of the user to query
Returns:
planType (uint8) - Current plan type
expiryTimestamp (uint256) - Unix timestamp when plan expires
hasAccess (boolean) - Whether user currently has access
isExpired (boolean) - Whether the plan has expired
checkAccess
Check if a user has active subscription access.
Address of the user to check
Returns:
hasAccess (boolean) - True if user has valid subscription
getPlanDetails
Get pricing and duration for a plan type.
Plan type to query (0, 1, or 2)
Returns:
price (uint256) - Price in USDC (6 decimals)
duration (uint256) - Duration in seconds
isActive (boolean) - Whether plan is currently available
Integration Example
Purchasing a subscription from the frontend:
import {
purchaseSubscription ,
getUserSubscription ,
PlanType
} from '@/services/contractService' ;
import { parseUnits } from 'viem' ;
const SUBSCRIPTION_ADDRESS = '0xcFbdEaba321700A9C125b41dB6bBd6BBBA752287' ;
const USDC_ADDRESS = '0xBEE08798a3634e29F47e3d277C9d11507D55F66a' ;
// Step 1: Check current subscription
const subscription = await getUserSubscription ( userAddress , publicClient );
console . log ( 'Current plan:' , subscription . planType ); // 0, 1, or 2
console . log ( 'Has access:' , subscription . hasAccess );
console . log ( 'Expires:' , new Date ( Number ( subscription . expiryTimestamp ) * 1000 ));
// Step 2: Purchase Monthly plan
const planPrice = parseUnits ( '2' , 6 ); // $2 USDC
// Approve USDC
const approveHash = await walletClient . writeContract ({
address: USDC_ADDRESS ,
abi: ERC20_ABI ,
functionName: 'approve' ,
args: [ SUBSCRIPTION_ADDRESS , planPrice ],
});
console . log ( 'Approval tx:' , approveHash );
// Wait for approval confirmation
await publicClient . waitForTransactionReceipt ({ hash: approveHash });
// Purchase plan
const purchaseHash = await walletClient . writeContract ({
address: SUBSCRIPTION_ADDRESS ,
abi: SUBSCRIPTION_PLAN_ABI ,
functionName: 'purchasePlan' ,
args: [ PlanType . MONTHLY ], // 1 for Monthly
});
console . log ( 'Purchase tx:' , purchaseHash );
// Step 3: Verify purchase
const updatedSubscription = await getUserSubscription ( userAddress , publicClient );
console . log ( 'New plan:' , updatedSubscription . planType );
console . log ( 'New expiry:' , new Date ( Number ( updatedSubscription . expiryTimestamp ) * 1000 ));
Full Purchase Flow
Here’s the complete implementation from contractService.ts:
export const purchaseSubscription = async (
planType : PlanType ,
walletClient : WalletClient
) : Promise < any > => {
// Plan prices (USDC has 6 decimals)
const prices : Record < PlanType , bigint > = {
[PlanType. FREE ]: BigInt ( 0 ),
[PlanType. MONTHLY ]: BigInt ( 2_000000 ), // $2
[PlanType. YEARLY ]: BigInt ( 20_000000 ), // $20
};
const price = prices [ planType ];
console . log ( '[contractService] Approving USDC:' , price . toString ());
// Step 1: Approve USDC spending
const approveHash = await walletClient . writeContract ({
address: USDC_TOKEN_ADDRESS ,
abi: ERC20_ABI ,
functionName: 'approve' ,
args: [ SUBSCRIPTION_CONTRACT_ADDRESS , price ],
account: walletClient . account ! ,
chain: walletClient . chain ,
});
console . log ( '[contractService] Approve tx hash:' , approveHash );
// Wait for approval to be mined
await new Promise ( resolve => setTimeout ( resolve , 2000 ));
console . log ( '[contractService] Purchasing plan:' , planType );
// Step 2: Purchase the plan
const purchaseHash = await walletClient . writeContract ({
address: SUBSCRIPTION_CONTRACT_ADDRESS ,
abi: SUBSCRIPTION_PLAN_ABI ,
functionName: 'purchasePlan' ,
args: [ planType ],
account: walletClient . account ! ,
chain: walletClient . chain ,
});
console . log ( '[contractService] Purchase tx hash:' , purchaseHash );
return {
hash: purchaseHash ,
wait : async () => ({ hash: purchaseHash })
};
};
Checking Access
Validate subscription access before allowing DCA features:
import { checkUserAccess } from '@/services/contractService' ;
const hasAccess = await checkUserAccess ( userAddress , provider );
if ( ! hasAccess ) {
// Show upgrade prompt
console . log ( 'User needs to upgrade to access DCA features' );
// Redirect to subscription page
} else {
// Allow access to premium features
console . log ( 'User has valid subscription' );
}
Subscription Data Structure
struct Subscription {
PlanType planType; // Current plan (FREE, MONTHLY, YEARLY)
uint256 expiryTimestamp; // When subscription expires
bool hasLifetimeAccess; // Reserved for future use
address owner; // Can delegate to another address
}
Owner Delegation
Users can delegate their subscription to another address:
setOwner
Address to delegate subscription access to
Useful for sharing subscriptions or managing multiple wallets.
getOwner
Address to query owner for
Returns:
owner (address) - The delegated owner address (or user’s own address)
Admin Functions
revokeExpiredAccess
Manually revoke access for an expired subscription:
function revokeExpiredAccess ( address user ) external ;
Used to clean up expired subscriptions. Access is automatically checked, but this allows explicit revocation.
Contract Events
PlanPurchased
Emitted when a user purchases a plan:
event PlanPurchased (
address indexed user ,
PlanType planType ,
uint256 expiryTimestamp
);
PlanExpired
Emitted when a subscription expires:
event PlanExpired (
address indexed user ,
PlanType previousPlan
);
OwnerSet
Emitted when delegation is set:
event OwnerSet (
address indexed user ,
address indexed owner
);
AccessRevoked
Emitted when access is explicitly revoked:
event AccessRevoked (
address indexed user
);
TreasuryUpdated
Emitted when treasury address changes:
event TreasuryUpdated (
address indexed newTreasury
);
USDC Token
Subscriptions are paid with USDC on Base Sepolia:
USDC Contract: 0xBEE08798a3634e29F47e3d277C9d11507D55F66a
Decimals: 6
Format: $2.00 = 2,000,000 (2 * 10^6)
import { parseUnits , formatUnits } from 'ethers' ;
// Parse human-readable amount
const amount = parseUnits ( '2' , 6 ); // 2 USDC
console . log ( amount ); // 2000000n
// Format to human-readable
const formatted = formatUnits ( 2000000 n , 6 );
console . log ( formatted ); // "2.0"
Checking USDC Balance
Before purchasing, verify user has sufficient USDC:
import { getUSDCBalance } from '@/services/contractService' ;
const balance = await getUSDCBalance ( userAddress , publicClient );
console . log ( 'USDC Balance:' , formatUnits ( balance , 6 ));
if ( balance < parseUnits ( '2' , 6 )) {
console . log ( 'Insufficient USDC balance' );
// Show error or faucet link
}
Plan Utilities
Helper functions for working with plans:
import {
getPlanName ,
formatExpiryDate ,
getTimeRemaining
} from '@/services/contractService' ;
// Get plan name
const name = getPlanName ( PlanType . MONTHLY );
console . log ( name ); // "Monthly"
// Format expiry date
const expiryDate = formatExpiryDate ( 1735689600 n );
console . log ( expiryDate ); // "January 1, 2025"
// Get time remaining
const remaining = getTimeRemaining ( 1735689600 n );
console . log ( remaining ); // "45 days remaining"
Security Considerations
Important Security Notes:
Always approve exact amounts or use maximum uint256 for convenience
Verify USDC token address matches the whitelisted address
Check subscription expiry before allowing access to premium features
Contract address is immutable in codebase to prevent frontend manipulation
All payments go directly to verified treasury address
Treasury Address
All subscription payments are sent to:
0x39c0b97A8F2194fcd7396296F7697a84dd81077A
This is the same treasury that receives:
Protocol fees from swaps
Early withdrawal penalties from vault staking
Error Handling
Common errors and solutions:
Error Cause Solution ”Insufficient USDC balance” User lacks USDC Get USDC from faucet or DEX ”Approval failed” Transaction rejected Check wallet connection ”Invalid plan type” Plan type > 2 Use 0 (FREE), 1 (MONTHLY), or 2 (YEARLY) “USDC contract has no code” Wrong network Switch to Base Sepolia (84532)
Testing
To test subscription functionality:
# Get test USDC from Base Sepolia faucet
# USDC: 0xBEE08798a3634e29F47e3d277C9d11507D55F66a
# Run subscription tests
npm run test:subscription
Contract Overview View all contract addresses and tokens
Router Contract Swap tokens to get USDC for subscriptions