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.1

Published

TypeScript client for the **Profile Subscription** Solana program — fully on-chain recurring payments using Star Atlas Player Profile vaults.

Readme

@staratlas/profile-subscription

TypeScript client for the Profile Subscription Solana program — fully on-chain recurring payments using Star Atlas Player Profile vaults.

| 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,
  getSettleInstruction,
  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

Creates a new subscription. The subscriber signs with their Player Profile key (requires SUBSCRIBE permission scoped to the subscription program).

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,
  // Subscription accounts
  subscriptionAccount: subscriptionPda,
  serviceProvider: serviceProviderAddress,
  tokenMint: usdcMintAddress,
  systemProgram: SYSTEM_PROGRAM_ADDRESS,
  profileProgram: PLAYER_PROFILE_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(
  subscriberTokenAccount,
  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: subscriberUsdcAta,       // subscriber's token account
  destinationTokenAccount: providerUsdcAta,     // service provider's token account
  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)
  • If the transfer fails (insufficient funds, revoked delegate), sets status to PastDue

For cancelled subscriptions: only settles up to cancelled_at timestamp, then transitions to Expired.

3. 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.

4. 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: currentServiceProviderKeypair,
  subscriptionAccount: subscriptionPda,
  newServiceProvider: newPayoutAddress,
});

Note: This updates both service_provider (payout destination) AND payout_key_owner (signing authority). The old owner loses control permanently.

5. Close Subscription

Reclaims rent from a terminal (Expired) subscription account.

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

const ix = getCloseSubscriptionInstruction({
  rentRecipient: subscriberKeypair,   // receives the rent refund
  subscriptionAccount: subscriptionPda,
});

Only works on Expired subscriptions.


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 the InitializeSubscription transaction with your configured parameters (amount, period, metadata)
  • Include the SPL token approve instruction for the subscription PDA delegate
  • 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 on all active 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: subscriberAta,
  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 service_provider address — The destination_token_account owner in Settle must match service_provider on the subscription. The program enforces this, but your off-chain system should also verify it matches your expected wallet.

  3. 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.

  4. 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.

  5. 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.

  6. 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 PSubAEkp16MhpP7AfB5kBbpWQ4cppUj9nxS5vUkHT13:

| Permission | Bit | Required For | |---|---|---| | SUBSCRIBE | 1 << 0 | InitializeSubscription | | CANCEL_SUBSCRIPTION | 1 << 1 | CancelSubscription |

Add these to a profile key:

const permissions = (1n << 0n) | (1n << 1n); // SUBSCRIBE + CANCEL_SUBSCRIPTION
// 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