A decentralized subscription management system built on Solana, utilizing a Rust-based backend and an Anchor Solana program to store and manage subscription data entirely on-chain.
The On-Chain Subscription Manager allows users to create, retrieve, renew, cancel, and close subscriptions using Solana’s blockchain. It eliminates the need for an external database by storing subscription state in program-derived accounts (PDAs). The backend provides a RESTful API for interaction, secured with JWT authentication based on Solana wallet signatures.
- Decentralized: Subscription data is stored on Solana PDAs.
- Secure Authentication: Uses Solana wallet signatures and JWTs.
- Full Lifecycle: Supports creating, renewing, canceling, and closing subscriptions.
- No Database: All data is managed on-chain.
- RESTful API: Built with Rust and Actix Web.
- Backend: Rust, Actix Web, Solana Rust SDK, Borsh, Anchor (serialization)
- Blockchain: Solana Devnet, Anchor program
- Configuration:
.envfile - Testing: Postman, Solana CLI
/OnChainSubscriptionManager3/ ├── backend/ │ ├── src/ │ │ ├── main.rs # Backend server │ │ ├── middlewares.rs # Authentication middleware │ ├── .env # Configuration (not tracked) │ ├── Cargo.toml # Rust dependencies ├── programs/ │ ├── on-chain-subscription-manager/ │ │ ├── src/ │ │ │ ├── lib.rs # Solana program │ ├── Anchor.toml # Anchor configuration ├── tests/ │ ├── subscription-manager.js # Anchor test script ├── package.json # Node dependencies for testing ├── README.md # This file
- Rust: Install with
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh - Solana CLI: Install via
sh -c "$(curl -sSfL https://release.solana.com/v1.18.4/install)" - Anchor: Install with
cargo install --git https://github.com/coral-xyz/anchor avm --locked --force - Node.js: Required for Anchor tests (
npm installin root directory) - Postman: For API testing (optional)
git clone <repository-url>
cd OnChainSubscriptionManager3Create a .env file in the backend/ directory:
SERVER_HOST=127.0.0.1
SERVER_PORT=8080
SOLANA_RPC_URL=https://api.devnet.solana.com
SOLANA_PROGRAM_ID=6sQWJct5BtcfWSQEpzzxvi5t3Ba3tE3p3fp54tXw5PUS
JWT_SECRET=your-secret-key-here
TREASURY_PUBKEY= < Your treeasury pub key>
PHANTOM_PRIVATE_KEY=<private-key>- Replace PHANTOM_PRIVATE_KEY with the base58 private key.
- Ensure TREASURY_PUBKEY has sufficient SOL (~2 SOL recommended for testing).
cd backend
cargo build
cd ..
anchor build
anchor deploy
- Update SOLANA_PROGRAM_ID in .env if the deployed program ID differs.
cd backend
RUST_LOG=info cargo run
- Server starts at http://127.0.0.1:8080.
- Start the backend:
RUST_LOG=info cargo run
- Use RUST_LOG=debug for detailed logs.
- Authenticate:
- POST http://127.0.0.1:8080/auth
- Body:
{
"public_key": "Ha8xAt36P3SwUZzTXZFPpda3DzcwgKFafeQYLsAN13fd",
"signature": "<base58-signature>",
"timestamp": 1743118015
}
- Response provides a JWT token.
- Create a Subscription:
- POST http://127.0.0.1:8080/api/subscriptions
- Headers: Authorization: Bearer
- Body:
{
"plan_id": 1,
"duration": 60,
"amount": 1000000
}
- Retrieve Subscription:
- GET http://127.0.0.1:8080/api/subscriptions/1
- Headers: Authorization: Bearer
- Renew, Cancel, Close:
- Use POST /api/subscriptions/1/renew, /cancel, /close with the same header.
- Check an account:
solana account 9HZ45GCgySsPgiTY6eToaGggT7BZHXhZZxUvgQPZafHL --url https://api.devnet.solana.com
- Description: Authenticates a user with a signed message.
- Request:
{
"public_key": "Ha8xAt36P3SwUZzTXZFPpda3DzcwgKFafeQYLsAN13fd",
"signature": "<base58-signature>",
"timestamp": 1743118015
}
- Response:
{
"token": "<jwt-token>",
"expires_in": 86400,
"public_key": "Ha8xAt36P3SwUZzTXZFPpda3DzcwgKFafeQYLsAN13fd"
}
- Description: Creates a new subscription.
- Headers: Authorization: Bearer
- Request:
{
"plan_id": 1,
"duration": 60,
"amount": 1000000
}
- Response:
{
"signature": "<transaction-signature>"
}
- Description: Retrieves subscription details.
- Headers: Authorization: Bearer
- Example: GET /api/subscriptions/1
- Response:
{
"id": "9HZ45GCgySsPgiTY6eToaGggT7BZHXhZZxUvgQPZafHL",
"plan_id": 1,
"duration": 60,
"amount": 1000000,
"active": true,
"start_time": 1743123080,
"history": [1743123080],
"owner": "Ha8xAt36P3SwUZzTXZFPpda3DzcwgKFafeQYLsAN13fd"
}
- Description: Renews an expired subscription.
- Headers: Authorization: Bearer
- Example: POST /api/subscriptions/1/renew
- Response:
{
"signature": "<transaction-signature>"
}
- Description: Cancels an active subscription.
- Headers: Authorization: Bearer
- Example: POST /api/subscriptions/1/cancel
- Response:
{
"signature": "<transaction-signature>"
}
- Description: Closes a subscription, deleting the account.
- Headers: Authorization: Bearer
- Example: POST /api/subscriptions/1/close
- Response:
{
"signature": "<transaction-signature>"
}
- Program ID: 6sQWJct5BtcfWSQEpzzxvi5t3Ba3tE3p3fp54tXw5PUS
- Account: Subscription
- Size: 157 bytes
- Fields:
- user: Pubkey (32 bytes)
- plan_id: u64 (8 bytes)
- start_time: i64 (8 bytes)
- duration:u64 (8 bytes)
- amount: u64 (8 bytes)
- active: bool (1 byte)
- history: Vec (4 bytes len + 8 bytes/entry, max 10 entries)
- Instructions:
- create_subscription: Initializes a subscription PDA.
- renew_subscription: Renews expired subscriptions.
- cancel_subscription: Sets active to false.
- close_subscription: Deletes the PDA.
- Fork the repository.
- Create a feature branch (git checkout -b feature/your-feature).
- Commit changes (git commit -m "Add your feature").
- Push to the branch (git push origin feature/your-feature).
- Open a pull request.
- Use RUST_LOG=debug for detailed logs.
- Test endpoints with Postman or curl.
- Ensure Solana program and backend structs remain aligned.
- "Account already in use": Delete the existing PDA or use a different plan_id.
- "Deserialization error": Verify Subscription struct matches on-chain data.
- "Transaction failed": Check logs for simulation errors, ensure treasury has SOL.
- "Invalid signature": Confirm timestamp is within 24 hours and signature matches message.
MIT License - feel free to use, modify, and distribute this code.