npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@staratlas/profile-subscription

v0.46.4

Published

TypeScript client for the **Profile Subscription** Solana program — fully on-chain recurring payments with Star Atlas Player Profile validation and optional Profile Vault-backed settlement.

Downloads

708

Readme

@staratlas/profile-subscription

TypeScript client for the Profile Subscription Solana program — fully on-chain recurring payments with Star Atlas Player Profile validation and optional Profile Vault-backed settlement.

| Network | Program ID | |---------|------------| | Mainnet | PSubzJGRCQKoBadKLcg3cqryrb7ZLPujdqSokUoQptQ | | Devnet / Testnet | PSubAEkp16MhpP7AfB5kBbpWQ4cppUj9nxS5vUkHT13 |

Note: The SDK's exported PROFILE_SUBSCRIPTION_PROGRAM_ADDRESS constant is the devnet address. For mainnet deployments, pass the mainnet program ID explicitly via the programAddress config option on each instruction builder.

Install

npm install @staratlas/profile-subscription @solana/kit

Quick Start

import {
  getInitializeSubscriptionInstruction,
  getInitializeFromVaultAndChargeInstruction,
  getSettleInstruction,
  getSettleFromVaultInstruction,
  getCancelSubscriptionInstruction,
  getUpdatePayoutKeyInstruction,
  getCloseSubscriptionInstruction,
  fetchSubscriptionAccount,
  findSubscriptionAccountPda,
  PROFILE_SUBSCRIPTION_PROGRAM_ADDRESS,
} from '@staratlas/profile-subscription';

Subscription Lifecycle

┌──────────┐   subscribe   ┌────────┐   settle (success)   ┌────────┐
│          │ ──────────────▶│ Active │ ────────────────────▶ │ Active │
│  (none)  │                └────────┘                       └────────┘
└──────────┘                    │  │                              │
                                │  │ settle (transfer fails)     │
                                │  ▼                             │
                                │ ┌──────────┐                   │
                                │ │ PastDue  │───settle (ok)────▶│
                                │ └──────────┘                   │
                                │      │                         │
                                │      │ cancel                  │ cancel
                                │      ▼                         ▼
                                │ ┌───────────┐            ┌───────────┐
                                └▶│ Cancelled │            │ Cancelled │
                                  └───────────┘            └───────────┘
                                       │ settle (up to cancelled_at)
                                       ▼
                                  ┌──────────┐
                                  │ Expired  │──── close ────▶ (account deleted)
                                  └──────────┘

Status Definitions

| Status | Meaning | Can Settle? | Can Cancel? | Can Close? | |--------|---------|-------------|-------------|------------| | Active | Subscription is live, payments current | ✅ | ✅ | ❌ | | PastDue | Settlement attempted but transfer failed (insufficient funds, revoked delegate, etc.) | ✅ | ✅ | ❌ | | Cancelled | Subscriber cancelled. Outstanding periods before cancelled_at can still be settled | ✅ (up to boundary) | ❌ | ❌ | | Expired | Cancelled + fully settled. Terminal state | ❌ | ❌ | ✅ |


Instructions

1. Initialize Subscription (Profile ATA delegate path)

Creates a new subscription that settles from the subscriber profile's token ATA. The subscriber signs with their Player Profile key (requires SUBSCRIBE permission scoped to the subscription program), and a wallet signer funds the subscription account rent.

import { findSubscriptionAccountPda, getInitializeSubscriptionInstruction } from '@staratlas/profile-subscription';

// Derive the subscription PDA
const [subscriptionPda] = await findSubscriptionAccountPda({
  profile: profileAddress,
  serviceProvider: serviceProviderAddress,
  subscriptionId: 1n, // unique per profile+provider
});

const ix = getInitializeSubscriptionInstruction({
  // Profile validation accounts
  profileValidationSigner: subscriberKeypair,
  profileValidationProfile: profileAddress,
  profileValidationCertificate: undefined, // optional
  profileValidationProgram: PLAYER_PROFILE_PROGRAM_ADDRESS,
  // Rent funder and subscription accounts
  funder: walletSigner,
  subscriptionAccount: subscriptionPda,
  serviceProvider: serviceProviderAddress,
  tokenMint: usdcMintAddress,
  systemProgram: SYSTEM_PROGRAM_ADDRESS,
  // Instruction args
  keyIndex: 0,         // profile key index with SUBSCRIBE permission
  subscriptionId: 1n,  // unique subscription identifier
  amountPerPeriod: 29_990_000n,  // 29.99 USDC (6 decimals)
  periodSeconds: 2_592_000n,     // 30 days
  metadata: new Uint8Array(64),  // custom tier/plan metadata
});

After initialization, the subscriber must also approve the subscription PDA as a token delegate on their token account:

// Client-side: approve delegate for the subscription PDA
const approveIx = createApproveInstruction(
  subscriberProfileUsdcAta,
  subscriptionPda,      // delegate
  subscriberWallet,     // owner
  BigInt(2**64 - 1),   // max approval
);

2. Settle (Permissionless Crank)

Collects payment for elapsed billing periods. Anyone can call this — no signer required beyond the transaction fee payer.

import { getSettleInstruction } from '@staratlas/profile-subscription';

const ix = getSettleInstruction({
  subscriptionAccount: subscriptionPda,
  sourceTokenAccount: subscriberProfileUsdcAta, // subscriber profile's ATA
  destinationTokenAccount: providerUsdcAta,     // payout_key_owner's ATA
  tokenProgram: TOKEN_PROGRAM_ADDRESS,
});

Settlement rules:

  • Calculates periods_owed = (now - last_settled_at) / period_seconds
  • Capped at 3 periods per transaction (prevents accumulation shock)
  • Transfers periods_owed × amount_per_period tokens
  • Advances last_settled_at by exact period increments (no drift)
  • Requires sourceTokenAccount to be the subscriber profile's ATA for the subscription mint
  • Requires destinationTokenAccount to be the payout owner's ATA for the subscription mint
  • If transfer fails on an active subscription (insufficient funds, revoked delegate), sets status to PastDue

For cancelled subscriptions: only settles up to cancelled_at timestamp, remains Cancelled on transfer failure, and transitions to Expired once all full periods through cancellation are settled. If no full periods are owed, it expires without charging.

3. Initialize From Vault And Charge

Creates a Profile Vault-backed subscription and charges the first billing period in the same transaction. The subscriber profile key needs SUBSCRIBE | SETTLE_FROM_VAULT scoped to the Profile Subscription program. Separately, the derived subscription PDA must already be present as a Player Profile key with Profile Vault DRAIN_VAULT permission scoped to the selected vault authority or vault token account. Program-wide Profile Vault drain scope is rejected for recurring billing.

import {
  findSubscriptionAccountPda,
  getInitializeFromVaultAndChargeInstruction,
} from '@staratlas/profile-subscription';

const [subscriptionPda] = await findSubscriptionAccountPda(
  {
    profile: profileAddress,
    serviceProvider: serviceProviderAddress,
    subscriptionId: 1n,
  },
  { programAddress } // pass mainnet program ID when needed
);

const ix = getInitializeFromVaultAndChargeInstruction(
  {
    profileValidationSigner: subscriberKeypair,
    profileValidationProfile: profileAddress,
    profileValidationCertificate: undefined,
    profileValidationProgram: PLAYER_PROFILE_PROGRAM_ADDRESS,
    profile,
    funder: walletSigner,
    subscriptionAccount: subscriptionPda,
    serviceProvider: serviceProviderAddress,
    tokenMint: usdcMintAddress,
    vaultAuthority,
    vaultTokenAccount,
    destinationTokenAccount: providerUsdcAta,
    profileVaultProgram: PROFILE_VAULT_PROGRAM_ADDRESS,
    tokenProgram: TOKEN_PROGRAM_ADDRESS,
    systemProgram: SYSTEM_PROGRAM_ADDRESS,
    keyIndex: 0,
    subscriptionId: 1n,
    vaultKeyIndex, // profile key index for the subscription PDA + DRAIN_VAULT
    amountPerPeriod: 29_990_000n,
    periodSeconds: 2_592_000n,
    metadata: new Uint8Array(64),
  },
  { programAddress }
);

4. Settle From Vault (Permissionless Crank)

Collects elapsed billing periods from the configured profile vault through profile_vault::drain_vault, with profile_subscription signing as the subscription PDA after validating amount, timing, mint, vault source, destination, and the scoped DRAIN_VAULT permission. Anyone can crank it.

import { getSettleFromVaultInstruction } from '@staratlas/profile-subscription';

const ix = getSettleFromVaultInstruction(
  {
    profile: profileAddress,
    subscriptionAccount: subscriptionPda,
    vaultAuthority,
    vaultTokenAccount,
    destinationTokenAccount: providerUsdcAta,
    profileVaultProgram: PROFILE_VAULT_PROGRAM_ADDRESS,
    tokenProgram: TOKEN_PROGRAM_ADDRESS,
    vaultKeyIndex,
  },
  { programAddress }
);

Vault-backed settlement follows the same period math and 3-period cap as Settle. If vault balance is insufficient, active subscriptions move to PastDue; cancelled subscriptions remain Cancelled until they can be fully settled or expire.

5. Cancel Subscription

Subscriber cancels. Requires CANCEL_SUBSCRIPTION permission on their profile key.

import { getCancelSubscriptionInstruction } from '@staratlas/profile-subscription';

const ix = getCancelSubscriptionInstruction({
  profileValidationSigner: subscriberKeypair,
  profileValidationProfile: profileAddress,
  profileValidationCertificate: undefined,
  profileValidationProgram: PLAYER_PROFILE_PROGRAM_ADDRESS,
  subscriptionAccount: subscriptionPda,
  profileProgram: PLAYER_PROFILE_PROGRAM_ADDRESS,
  keyIndex: 0,
});

Important: Cancellation does NOT settle outstanding payments. The service provider should call Settle before or after cancellation to collect any owed periods up to cancelled_at.

6. Update Payout Key

Service provider changes the payout destination. Only the current payout_key_owner can call this.

import { getUpdatePayoutKeyInstruction } from '@staratlas/profile-subscription';

const ix = getUpdatePayoutKeyInstruction({
  payoutKeyOwner: currentPayoutOwnerKeypair,
  subscriptionAccount: subscriptionPda,
  newServiceProvider: newPayoutAddress,
});

Note: This updates payout_key_owner, which is both the payout destination owner checked during settlement and the authority for future payout rotations. The immutable service_provider field remains the original PDA seed identity so payout rotation cannot break settlement signing.

7. Close Subscription

Reclaims rent from a terminal (Expired) subscription account.

import { getCloseSubscriptionInstruction } from '@staratlas/profile-subscription';

const ix = getCloseSubscriptionInstruction({
  // Profile validation accounts
  profileValidationSigner: subscriberKeypair,
  profileValidationProfile: profileAddress,
  profileValidationCertificate: undefined,
  profileValidationProgram: PLAYER_PROFILE_PROGRAM_ADDRESS,
  subscriptionAccount: subscriptionPda,
  profileProgram: PLAYER_PROFILE_PROGRAM_ADDRESS,
  keyIndex: 0,
});

Only works on Expired subscriptions and requires the profile key to have CANCEL_SUBSCRIPTION permission. Rent is refunded to the validated profile account.


Reading Subscription State

import { fetchSubscriptionAccount } from '@staratlas/profile-subscription';

const sub = await fetchSubscriptionAccount(rpc, subscriptionPda);

console.log(sub.data.status);           // 'Active' | 'PastDue' | 'Cancelled' | 'Expired'
console.log(sub.data.amountPerPeriod);  // bigint (token base units)
console.log(sub.data.periodSeconds);    // bigint
console.log(sub.data.periodsSettled);   // bigint
console.log(sub.data.lastSettledAt);    // bigint (unix timestamp)
console.log(sub.data.cancelledAt);      // bigint (0 if not cancelled)
console.log(sub.data.serviceProvider);  // Address
console.log(sub.data.tokenMint);        // Address
console.log(sub.data.metadata);         // Uint8Array(64)

Service Provider Integration Guide

What Your System Needs to Do

As a service provider integrating profile subscriptions (e.g., Zink), your backend is responsible for:

1. Transaction Construction (Frontend/API)

When a user clicks "Pay with Crypto":

  • Derive the subscription PDA from their profile + your service provider address + a unique subscription ID
  • Build either the InitializeSubscription profile-ATA transaction or the InitializeFromVaultAndCharge Profile Vault-backed transaction with your configured parameters (amount, period, metadata)
  • For the profile-ATA path, include the SPL token approve instruction for the subscription PDA delegate
  • For the vault-backed path, ensure the subscription PDA has a Profile Vault DRAIN_VAULT key scoped to the selected vault authority/token account and pass its vaultKeyIndex
  • Present for the user to sign

Your liability: The service_provider address, amount_per_period, period_seconds, and metadata are set by the transaction you construct. Triple-check these values — they're immutable after creation.

2. Settlement Crank (Backend)

Run a bot/cron that periodically calls Settle for profile-ATA subscriptions or SettleFromVault for vault-backed subscriptions:

// Poll all subscription PDAs for your service provider
// For each active subscription where time > last_settled_at + period_seconds:
const ix = getSettleInstruction({
  subscriptionAccount: pda,
  sourceTokenAccount: subscriberProfileAta,
  destinationTokenAccount: yourAta,
  tokenProgram: TOKEN_PROGRAM_ADDRESS,
});
// Send transaction (any wallet can pay the fee)

Settlement is capped at 3 periods per transaction. If a subscriber was PastDue for months, you need to call settle multiple times to collect all owed periods.

Your liability: If you don't run the crank, you don't get paid. The program doesn't push payments — it requires an explicit settle call.

3. Status Sync (Backend)

Monitor subscription account changes and sync to your off-chain database:

| On-chain Status | Your System Should | |---|---| | Active | Grant access. Verify periodsSettled is current. | | PastDue | Grace period → attempt settle again. After N failures, restrict access. | | Cancelled | Settle any remaining periods. Access continues until end of current paid period. | | Expired | Revoke access. Optionally close the account to reclaim rent. |

4. Metadata Usage

The 64-byte metadata field is yours to define. Example encoding:

bytes 0-3:   plan_id (u32)        — which subscription tier
bytes 4-7:   features (u32)       — feature flags bitmask  
bytes 8-15:  custom_id (u64)      — your internal reference ID
bytes 16-63: reserved             — future use

This is set at initialization and is immutable. If a user changes plans, create a new subscription (cancel old → init new).

Security Considerations for Service Providers

  1. Validate the subscription PDA derivation — When your backend reads subscription data, verify the PDA is correctly derived from the expected seeds. Don't trust arbitrary account addresses.

  2. Verify your payout addressSettle requires destination_token_account to be the ATA for payout_key_owner and the subscription mint. service_provider is immutable PDA seed identity; payout_key_owner is the current payout destination/rotation authority. Your off-chain system should verify both match your expected provider identity and payout wallet.

  3. Use the correct settlement sourceSettle rejects arbitrary same-mint source accounts and requires the subscriber profile ATA. SettleFromVault requires the vault token account to be owned by the supplied vault authority and rejects program-scoped Profile Vault drain keys; the subscription PDA's DRAIN_VAULT permission must be scoped to the vault authority or vault token account.

  4. Handle PastDue gracefully — A PastDue status means the token transfer failed. This could be:

    • Subscriber ran out of funds
    • Subscriber revoked the token delegate
    • Subscriber closed their token account

    Your system should attempt to re-settle periodically but eventually give up and restrict access.

  5. Don't rely solely on on-chain state for access control — Use on-chain state as the source of truth, but cache locally with reasonable TTLs. Polling every subscription every second is wasteful; every few minutes is fine for most use cases.

  6. Payout key security — If your payout_key_owner private key is compromised, an attacker can redirect all future settlements to their wallet via UpdatePayoutKey. Store this key with the same security as your hot wallet. Rotation updates payout_key_owner only; the original service_provider seed stays immutable.

  7. Settlement timing — The program uses Clock::get() for timestamps. Validators have ~1-2 second clock skew tolerance. Don't rely on sub-second timing precision for billing period boundaries.


Permissions

The program uses Player Profile permissions scoped to the active Profile Subscription program ID (the SDK default is devnet PSubAEkp16MhpP7AfB5kBbpWQ4cppUj9nxS5vUkHT13; pass the mainnet program ID in instruction config when building mainnet transactions):

| Permission | Bit | Required For | |---|---|---| | SUBSCRIBE | 1 << 0 | InitializeSubscription, InitializeFromVaultAndCharge | | CANCEL_SUBSCRIPTION | 1 << 1 | CancelSubscription, CloseSubscription | | SETTLE_FROM_VAULT | 1 << 2 | InitializeFromVaultAndCharge authorization |

Add these to a profile key:

const permissions = (1n << 0n) | (1n << 1n) | (1n << 2n); // SUBSCRIBE + CANCEL + SETTLE_FROM_VAULT
// Use AddKeys instruction on the Player Profile program
// with scope = PROFILE_SUBSCRIPTION_PROGRAM_ADDRESS

Program Addresses

| Network | Program ID | |---------|------------| | Mainnet | PSubzJGRCQKoBadKLcg3cqryrb7ZLPujdqSokUoQptQ | | Devnet / Testnet | PSubAEkp16MhpP7AfB5kBbpWQ4cppUj9nxS5vUkHT13 |

Related Programs

| Program | Mainnet Address | |---------|----------------| | Player Profile | pprofELXjL5Kck7Jn5hCpwAL82DpTkSYBENzahVtbc9 | | Profile Vault | pv1ttom8tbyh83C1AVh6QH2naGRdVQUVt3HY1Yst5sv | | SPL Token | TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA |

Constants

| Constant | Value | Description | |---|---|---| | MAX_SETTLEMENT_PERIODS | 3 | Max periods settled per transaction | | PROFILE_SUBSCRIPTION_PROGRAM_ADDRESS | PSubAEkp16MhpP7AfB5kBbpWQ4cppUj9nxS5vUkHT13 | SDK default (devnet) |

Using the Mainnet Program ID

The SDK defaults to the devnet program address. For mainnet, override via the config parameter:

const MAINNET_PROGRAM_ID = 'PSubzJGRCQKoBadKLcg3cqryrb7ZLPujdqSokUoQptQ';

const ix = getInitializeSubscriptionInstruction(
  { /* ...accounts and args... */ },
  { programAddress: address(MAINNET_PROGRAM_ID) }
);

License

Apache-2.0