solana_kit_subscriptions 0.2.3
solana_kit_subscriptions: ^0.2.3 copied to clipboard
Subscriptions program client for Solana Kit. Generated from the upstream Codama IDL with ergonomic helpers.
solana_kit_subscriptions #
Dart client for the Solana Subscriptions Delegation Program.
The package is generated from the upstream solana-foundation/subscriptions Codama IDL and exposes typed accounts, instructions, PDAs, errors, and codecs for subscription authorities, fixed delegations, recurring delegations, and subscription-plan flows.
Upstream Compatibility #
- Upstream repository:
solana-foundation/subscriptions - Supported upstream baseline:
ts-client-v0.4.0-rc.2 - Program address:
De1egAFMkMWZSN5rYXRj9CAdheBamobVNubTsi9avR44 - IDL source: generated upstream from Codama-annotated Pinocchio Rust code
Installation #
dart pub add solana_kit_subscriptions
Most apps also use packages such as solana_kit, solana_kit_token, and solana_kit_associated_token_account to create signers, derive token accounts, build transactions, and submit them to RPC.
Documentation #
- Package page: https://pub.dev/packages/solana_kit_subscriptions
- API reference: https://pub.dev/documentation/solana_kit_subscriptions/latest/
- Workspace docs: https://openbudgetfun.github.io/solana_kit/
- Source code: https://github.com/openbudgetfun/solana_kit/tree/main/packages/solana_kit_subscriptions
- Upstream guide: https://solana.com/docs/payments/subscriptions/overview
Concepts and usage #
The Subscriptions Delegation Program lets users authorize future SPL Token or Token-2022 transfers with explicit limits. In Dart, use solana_kit_subscriptions for generated PDAs, account decoders, and instruction builders.
Each (user, token mint) pair gets a program-controlled Subscription Authority. The user's token account approves that authority once. The program then checks every requested pull against an active authorization record.
| Model | Use |
|---|---|
| Fixed delegation | Let a delegatee spend up to one total allowance, optionally with an expiry. |
| Recurring delegation | Let a delegatee spend up to a limit that resets each period. |
| Subscription plan | Let a merchant publish terms that subscribers accept and approved collectors charge. |
import 'package:solana_kit/solana_kit.dart';
import 'package:solana_kit_subscriptions/solana_kit_subscriptions.dart';
Future<void> main() async {
const user = Address('11111111111111111111111111111112');
const tokenMint = Address('So11111111111111111111111111111111111111112');
final (authority, bump) = await findSubscriptionAuthorityPda(
programAddress: subscriptionsProgramAddress,
seeds: SubscriptionAuthoritySeeds(user: user, tokenMint: tokenMint),
);
print('authority=$authority bump=$bump');
}
Amounts are token base units. For a 6-decimal token, 1_000_000 means 1 token.
Create a Subscription Authority once per (user, token mint) pair. The user's associated token account must exist first. Build the instruction with getInitSubscriptionAuthorityInstruction, add it to a transaction, and sign with the owner.
import 'package:solana_kit/solana_kit.dart';
import 'package:solana_kit_subscriptions/solana_kit_subscriptions.dart';
Future<void> main() async {
const owner = Address('11111111111111111111111111111112');
const tokenMint = Address('So11111111111111111111111111111111111111112');
const userAta = Address('11111111111111111111111111111113');
const systemProgram = Address('11111111111111111111111111111111');
const tokenProgram = Address('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA');
final (subscriptionAuthority, _) = await findSubscriptionAuthorityPda(
programAddress: subscriptionsProgramAddress,
seeds: SubscriptionAuthoritySeeds(user: owner, tokenMint: tokenMint),
);
final instruction = getInitSubscriptionAuthorityInstruction(
programAddress: subscriptionsProgramAddress,
owner: owner,
subscriptionAuthority: subscriptionAuthority,
tokenMint: tokenMint,
userAta: userAta,
systemProgram: systemProgram,
tokenProgram: tokenProgram,
);
print(instruction.accounts!.length);
}
Fetch and decode SubscriptionAuthority before creating it when your app may have initialized the authority already.
A fixed delegation lets a delegatee pull up to a fixed token amount. Each successful transfer reduces the remaining allowance. Use expiryTs: BigInt.zero for no expiry.
import 'package:solana_kit/solana_kit.dart';
import 'package:solana_kit_subscriptions/solana_kit_subscriptions.dart';
Future<void> main() async {
const subscriptionAuthority = Address('11111111111111111111111111111112');
const delegator = Address('11111111111111111111111111111113');
const delegatee = Address('11111111111111111111111111111114');
final (delegationAccount, _) = await findFixedDelegationPda(
programAddress: subscriptionsProgramAddress,
seeds: FixedDelegationSeeds(
subscriptionAuthority: subscriptionAuthority,
delegator: delegator,
delegatee: delegatee,
nonce: BigInt.from(1),
),
);
final instruction = getCreateFixedDelegationInstruction(
programAddress: subscriptionsProgramAddress,
delegator: delegator,
subscriptionAuthority: subscriptionAuthority,
delegationAccount: delegationAccount,
delegatee: delegatee,
systemProgram: systemProgramAddress,
fixedDelegation: CreateFixedDelegationData(
nonce: BigInt.from(1),
amount: BigInt.from(1_000_000),
expiryTs: BigInt.zero,
expectedSubscriptionAuthorityInitId: BigInt.zero,
),
);
print(instruction.accounts!.length);
}
The delegator signs setup and revoke transactions. The delegatee signs transfer transactions built with getTransferFixedInstruction.
A recurring delegation lets a delegatee pull up to a token limit that resets every period. The program rejects transfers that exceed the current period's remaining allowance.
import 'package:solana_kit/solana_kit.dart';
import 'package:solana_kit_subscriptions/solana_kit_subscriptions.dart';
Future<void> main() async {
const subscriptionAuthority = Address('11111111111111111111111111111112');
const delegator = Address('11111111111111111111111111111113');
const delegatee = Address('11111111111111111111111111111114');
const delegationAccount = Address('11111111111111111111111111111115');
final instruction = getCreateRecurringDelegationInstruction(
programAddress: subscriptionsProgramAddress,
delegator: delegator,
subscriptionAuthority: subscriptionAuthority,
delegationAccount: delegationAccount,
delegatee: delegatee,
systemProgram: systemProgramAddress,
recurringDelegation: CreateRecurringDelegationData(
nonce: BigInt.from(7),
amountPerPeriod: BigInt.from(5_000_000),
periodLengthS: BigInt.from(30 * 24 * 60 * 60),
startTs: BigInt.from(DateTime.now().millisecondsSinceEpoch ~/ 1000),
expiryTs: BigInt.zero,
expectedSubscriptionAuthorityInitId: BigInt.zero,
),
);
print(instruction.accounts!.length);
}
Use getTransferRecurringInstruction for collection. It updates the recurring delegation account so the remaining allowance and billing window stay consistent on-chain.
Subscription plans let a merchant publish reusable terms. A subscriber accepts a plan with getSubscribeInstruction, which creates a subscription delegation account tied to the accepted terms.
import 'package:solana_kit/solana_kit.dart';
import 'package:solana_kit_subscriptions/solana_kit_subscriptions.dart';
Future<void> main() async {
const emptyAddress = Address('11111111111111111111111111111111');
const merchant = Address('11111111111111111111111111111112');
const planPda = Address('11111111111111111111111111111113');
const tokenMint = Address('So11111111111111111111111111111111111111112');
final createPlanInstruction = getCreatePlanInstruction(
programAddress: subscriptionsProgramAddress,
merchant: merchant,
planPda: planPda,
tokenMint: tokenMint,
systemProgram: systemProgramAddress,
tokenProgram: tokenProgramAddress,
planData: PlanData(
planId: BigInt.from(1),
mint: tokenMint,
terms: PlanTerms(
amount: BigInt.from(5_000_000),
periodHours: BigInt.from(24 * 30),
createdAt: BigInt.from(DateTime.now().millisecondsSinceEpoch ~/ 1000),
),
endTs: BigInt.zero,
destinations: const [emptyAddress, emptyAddress, emptyAddress, emptyAddress],
pullers: const [emptyAddress, emptyAddress, emptyAddress, emptyAddress],
metadataUri: 'https://example.com/subscription-plan.json',
),
);
print(createPlanInstruction.accounts!.length);
}
After the plan exists, derive the subscription delegation PDA and call getSubscribeInstruction. Use getTransferSubscriptionInstruction for billing, getCancelSubscriptionInstruction for subscriber cancellation, and getResumeSubscriptionInstruction to resume a paused subscription.
Decode accounts #
Generated account decoders parse fetched account data into typed account models.
import 'dart:typed_data';
import 'package:solana_kit_accounts/solana_kit_accounts.dart';
import 'package:solana_kit_addresses/solana_kit_addresses.dart';
import 'package:solana_kit_rpc_types/solana_kit_rpc_types.dart';
import 'package:solana_kit_subscriptions/solana_kit_subscriptions.dart';
void main() {
final encodedAccount = Account<Uint8List>(
address: const Address('11111111111111111111111111111111'),
data: Uint8List(0),
executable: false,
lamports: Lamports(BigInt.zero),
programAddress: const Address('11111111111111111111111111111111'),
space: BigInt.zero,
);
final account = decodePlan(encodedAccount);
print(account.data.owner);
}
Close the Subscription Authority after all fixed, recurring, and subscription delegations that depend on it have been closed or revoked. Closing returns the authority account rent and removes the program authority for that (user, token mint) pair.
import 'package:solana_kit/solana_kit.dart';
import 'package:solana_kit_subscriptions/solana_kit_subscriptions.dart';
void main() {
const user = Address('11111111111111111111111111111112');
const subscriptionAuthority = Address('11111111111111111111111111111113');
final instruction = getCloseSubscriptionAuthorityInstruction(
programAddress: subscriptionsProgramAddress,
user: user,
subscriptionAuthority: subscriptionAuthority,
);
print(instruction.accounts!.length);
}
The user signs the transaction. If your app stores derived addresses, recompute the PDA before closing so the instruction targets the canonical authority for the user and mint.
How generated program clients work #
Generated program clients share one API shape, so what you learn in one program transfers to the next:
- Program address constant — a
...ProgramAddressconstant identifies the program on-chain. - Identification helpers —
identify...Programandidentify...Instructionmatch programs and instructions without string comparisons. - Instruction builders and parsers —
get...Instructionencodes parameters,parse...Instructiondecodes a transaction instruction back into typed arguments. - Account codecs —
get...AccountCodecanddecode...Accountturn on-chain bytes into typed account objects. - Plan helpers —
get...InstructionPlanhelpers compose multi-instruction flows (such as creating an account before acting on it) into transaction plans the standard executor can run.
Errors thrown by these helpers and by transaction execution surface as SolanaError; match program-specific failures with the program error helpers.
Match program errors from your program #
Transaction failures surface as SolanaError values. When a transaction fails with a custom program error, the RPC response identifies the failing instruction by index — pair it with the transaction message to attribute the error to a program and match custom error codes.
import 'package:solana_kit_addresses/solana_kit_addresses.dart';
import 'package:solana_kit_programs/solana_kit_programs.dart';
Future<void> handleTransactionFailure(Object error) async {
const myProgramAddress = Address('11111111111111111111111111111111');
final transactionMessage = TransactionMessageInput(
instructions: {0: InstructionInput(programAddress: myProgramAddress)},
);
if (isProgramError(error, transactionMessage, myProgramAddress, 42)) {
// Custom program error code 42 from this program.
} else if (isProgramError(error, transactionMessage, myProgramAddress)) {
// Any other custom error from this program.
}
}
transactionMessage is a lightweight TransactionMessageInput — a map from instruction index to InstructionInput(programAddress: ...). Build it from the same instructions you sent, so matching stays accurate even when the transaction mixes instructions from several programs.
Key APIs #
subscriptionsProgramAddressfindSubscriptionAuthorityPdafindFixedDelegationPdafindRecurringDelegationPdafindPlanPdafindSubscriptionDelegationPdagetInitSubscriptionAuthorityInstructiongetCreateFixedDelegationInstructiongetCreateRecurringDelegationInstructiongetCreatePlanInstructiongetSubscribeInstructiongetTransferFixedInstructiongetTransferRecurringInstructiongetTransferSubscriptionInstructiongetCancelSubscriptionInstructiongetResumeSubscriptionInstructiongetCloseSubscriptionAuthorityInstructiondecodeSubscriptionAuthority,decodeFixedDelegation,decodeRecurringDelegation,decodePlan,decodeSubscriptionDelegation
Testing and coverage #
The package includes parity-style generated surface tests for account codecs, instruction builders/parsers, PDA derivation, error lookup, and value-object paths. The package is tracked by the solana_kit_subscriptions Codecov flag.