Subscription

Recurring payment subscriptions with automatic renewal

Pending Anchor 0.32
Program ID AqkVLdxNgW5Hw7BEzzTiKME3rnWLLspWhMqKcWsgiqGR
Binary Size 246K
Cluster Devnet

Instructions

IX create_plan

Creates a new subscription plan with a price, billing duration, and name.

ParameterTypeDescription
priceu64Token amount charged per billing period
durationi64Length of each billing period in seconds
nameStringPlan name (max 32 characters)
IX subscribe

Subscribes to a plan, paying the first billing period and creating a subscription record.

ParameterTypeDescription
No parameters
IX renew

Renews an expired subscription by paying for the next billing period.

ParameterTypeDescription
No parameters
IX cancel

Cancels an active subscription. The subscription remains valid until the current period expires.

ParameterTypeDescription
No parameters

Accounts

Plan

FieldTypeDescription
authorityPubkeyPlan creator who receives payments
mintPubkeyToken mint used for payments
nameStringName of the subscription plan
priceu64Cost per billing period
durationi64Billing period length in seconds
active_subsu64Number of currently active subscriptions
bumpu8PDA bump seed

Subscription

FieldTypeDescription
planPubkeyThe plan this subscription is for
subscriberPubkeyWallet of the subscriber
start_tsi64Timestamp when the subscription started
expires_ati64Timestamp when the current period expires
activeboolWhether the subscription is active
bumpu8PDA bump seed

PDA Derivation

plan
seeds = [b"plan", authority, mint]
subscription
seeds = [b"subscription", plan, subscriber]
vault
seeds = [b"vault", plan]

Error Codes

NameTooLong Plan name exceeds the maximum 32 character limit
AlreadyActive The subscription is still active and cannot be renewed yet
SubscriptionExpired The subscription has expired and must be renewed
NotExpired Cannot renew a subscription that has not yet expired
Overflow Arithmetic overflow in billing calculation

Usage Example

import { Program, BN } from "@coral-xyz/anchor";
import { PublicKey } from "@solana/web3.js";

// Create a monthly plan at 10 USDC
const [planPda] = PublicKey.findProgramAddressSync(
  [Buffer.from("plan"), authority.publicKey.toBuffer(), usdcMint.toBuffer()],
  program.programId
);

await program.methods
  .createPlan(
    new BN(10_000_000),     // 10 USDC (6 decimals)
    new BN(30 * 86400),     // 30 days
    "Pro Monthly"
  )
  .accounts({
    plan: planPda,
    authority: authority.publicKey,
    mint: usdcMint,
  })
  .signers([authority])
  .rpc();

// Subscribe to the plan
const [subPda] = PublicKey.findProgramAddressSync(
  [Buffer.from("subscription"), planPda.toBuffer(), subscriber.publicKey.toBuffer()],
  program.programId
);

await program.methods
  .subscribe()
  .accounts({
    plan: planPda,
    subscription: subPda,
    subscriber: subscriber.publicKey,
    mint: usdcMint,
  })
  .signers([subscriber])
  .rpc();