- Solana program for managing subscription logic for users.
- You have a backend (BE) that performs certain logic and other users benefit from it.
- The backend uses some form of authentication but you want to include subscription management.
- If you are interested in a decentralized solution, this program might be suitable.
- Your backend will provide authentication tokens only to users subscribed to an on-chain program.
- The current version requires deploying a new program for each subscription tracker, meaning you can't use a program already deployed by other users.
- To deploy
- (A) generate new programId
solana-keygen new -o ./target/w_3_subs_tracker-keypair.json
- update env: anchor
anchor upgrade target/deploy/w_3_subs_tracker.so --provider.cluster ${cluster: devnet|mainet} --program-id ${from step (A) }
- deploy:
anchor deploy
(min rent > 2.505 SOL) - initialize main state with local keypair
- copy programId after deployment and update it in Anchor.toml and lib.rs
- replace lib.rs and Anchor.toml programId with programId from step (A)
- run
anchor run mainStateInit
- (A) generate new programId
- To close program
solana program close ${program id} --bypass-warning
- Users can create subscription accounts (PDA) with an initial deposit, known as
credits
. - Users can choose their desired subscription type (e.g., FREE, BASIC, PREMIUM).
- Users can fund their PDA accounts at any time.
- After calling a predefined backend API, the backend will check the status of the given subscription PDA account, its credits, and the desired type. If eligible, the subscription type will be set to the desired type, and credits will be converted to debits with a valid till date, indicating the subscription period.
- Users can unsubscribe to withdraw their credits and a portion of their debits (fees apply). Part of the debits will be deducted as fees set during initialization (
main_state.fees
), and some for the time already subscribed. Fees or time deductions will be transferred to the owner of themain_state
account. The PDA account cannot be closed. - The authority of the main state can withdraw funds from any existing PDA account, but only debits part, not credits (more explained bellow).
- The backend will establish an API endpoint for updating subscription accounts. After hitting this API, the backend will find given user's subscription account on-chain and perform against it the following steps:
- If
subscription.authority_writable.valid_till > now
: The account is in an active subscription. The user must unsubscribe first to set a new desired type. If true, do not proceed. - Check the desired subscription from
subscription.subscription_status_writable.desired_subscription_type
and calculate the required amount. If the desired subscription type is invalid, do not proceed. - If
subscription.subscription_status_writable.after_verify_credit_lamports < required_amount
: The user has insufficient credits for the subscription. They must fund their account with more SOL. If true, do not proceed. - If all above conditions are false, the backend can set the user's subscription account.
- If
- Two account types are handled:
main_state (73 bytes)
:- A single instance created after program deployment by the chosen wallet as the signer, by calling
fn initialize_main_state(fees: u8)
or in TS,function initializeMainState(fees: number)
. The initialization sets the fee parameter for later changes in subscription types or withdrawals. The public key used will become the owner and authority. The owner can change the authority, owner, and fees. The authority can perform actions on existing subscription accounts. Only onemain_state
will exist during the program's lifetime.
- A single instance created after program deployment by the chosen wallet as the signer, by calling
subscription (106 bytes)
:-
- A single PDA account per user with seeds = (b"subscription", user.key().as_ref(), main_state.key().as_ref()) and auto bump.
- Users can create (not reinitializable) PDA using
fn create_subscription(initial_deposit: u64, account_type: SubscriptionType)
providingmain_state
and their signature. - Users can fund their PDA accounts using
fn fund_subscription(new_deposit: u64)
, providingmain_state
and their signature. The transferred SOL will appear insubscription_pda_account.subscription_status_writable.after_verify_credit_lamports
(credits). - Users can set their desired account type by calling
fn unsubscribe(withdraw_content: boolean, new_desired_subs_type: Option<SubscriptionType>)
. To change the subscription type, the user must ensure no active subscription, i.e.,subscription_pda_account.authority_writable.valid_till < now
. When unsubscribing without withdrawing funds, unused debits (with fees and time passed applied) will be moved to credits, andsubscription_pda_account.authority_writable.valid_till
will be set to 0. This makes the user eligible to change the account type. - Users can withdraw all funds from the subscription account by calling
fn unsubscribe(withdraw_content: boolean, new_desired_subs_type: Option<SubscriptionType>)
withwithdraw_content
set to true. All credits (100%) and debits (after fees and time usage deduction) will be transferred to the user's account. Remaining funds will be transferred to the owner of themain_state
account. The PDA remains open for future deposits.
-
- The authority can withdraw all eligible funds by calling
fn withdraw(amount: Option<u64>)
. The authority cannot withdraw credits but can withdraw used debits and partially used debits. If an amount is specified, it will attempt to withdraw that amount; otherwise, it will withdraw all available debits. - The authority can set subscription info for any user's PDA, including the subscription type, valid until date, and the amount of SOL transferred from credits to debits, by calling
fn set_subscription_info(new_date: Option<i64>, accumulated_sol: Option<u64>, subscription_type: Option<SubscriptionType>)
. Unspecified arguments retain their previous values.
- The authority can withdraw all eligible funds by calling
-
- This is my first smart contract, so it may not follow best practices. Any feedback or suggestions for improvement are welcome.