A recurring delegation lets a user approve another wallet or service to pull up to a limit that resets every period.
This guide keeps the moving parts visible. You derive the accounts first, initialize the Subscription Authority if needed, create the recurring delegation, then use that delegation PDA for transfers.
Install
pnpm add @solana/subscriptions @solana/kit @solana/kit-plugin-rpc @solana/kit-plugin-signer @solana-program/token
Create The Delegation
The setup has four parts:
- Create a client with the user signer and subscriptions plugin.
- Derive the user's token account, Subscription Authority PDA, and recurring delegation PDA.
- Initialize the Subscription Authority if it does not exist yet.
- Create the recurring delegation with the period amount, period length, start time, and expiry.
import { address, createClient } from '@solana/kit';import { solanaLocalRpc } from '@solana/kit-plugin-rpc';import { signer } from '@solana/kit-plugin-signer';import { findAssociatedTokenPda, TOKEN_PROGRAM_ADDRESS } from '@solana-program/token';import {fetchMaybeSubscriptionAuthority,findRecurringDelegationPda,findSubscriptionAuthorityPda,subscriptionsProgram,} from '@solana/subscriptions';const client = createClient().use(signer(userSigner)).use(solanaLocalRpc({ rpcUrl: 'http://127.0.0.1:8899' })).use(subscriptionsProgram());const tokenMint = address('TOKEN_MINT_ADDRESS_HERE');const delegatee = address('DELEGATEE_WALLET_ADDRESS_HERE');const now = BigInt(Math.floor(Date.now() / 1000));const nonce = 0n;const amountPerPeriod = 1_000_000n;const periodLengthS = 86_400n;const startTs = now;const expiryTs = now + periodLengthS * 30n;const [userAta] = await findAssociatedTokenPda({mint: tokenMint,owner: userSigner.address,tokenProgram: TOKEN_PROGRAM_ADDRESS,});const [subscriptionAuthorityPda] = await findSubscriptionAuthorityPda({user: userSigner.address,tokenMint,});const [delegationPda] = await findRecurringDelegationPda({subscriptionAuthority: subscriptionAuthorityPda,delegator: userSigner.address,delegatee,nonce,});const subscriptionAuthority = await fetchMaybeSubscriptionAuthority(client.rpc,subscriptionAuthorityPda,);if (!subscriptionAuthority.exists) {await client.subscriptions.instructions.initSubscriptionAuthority({tokenMint,tokenProgram: TOKEN_PROGRAM_ADDRESS,userAta,}).sendTransaction();}await client.subscriptions.instructions.createRecurringDelegation({tokenMint,delegatee,nonce,amountPerPeriod,periodLengthS,startTs,expiryTs,}).sendTransaction();
Transfer From The Delegation
The delegatee signs each transfer. The program checks the current period and rejects transfers that would exceed the period's remaining allowance.
const receiverAta = address('RECEIVER_TOKEN_ACCOUNT_ADDRESS_HERE');await client.subscriptions.instructions.transferRecurring({delegatee: delegateeSigner,delegator: userSigner.address,delegatorAta: userAta,tokenMint,delegationPda,amount: 100_000n,receiverAta,tokenProgram: TOKEN_PROGRAM_ADDRESS,}).sendTransaction();
Revoke The Delegation
The delegator can revoke the recurring delegation at any time. Revoking closes the delegation PDA and returns its rent to the signer.
await client.subscriptions.instructions.revokeDelegation({authority: userSigner,delegationAccount: delegationPda,}).sendTransaction();
Notes
amountPerPeriodis in base units. For a 6-decimal token,1_000_000means1token.- The program rejects transfers that exceed the current period's remaining allowance.
- Once the next period starts, the pulled amount resets.
expiryTsis a hard stop: once it passes, transfers fail and the sponsor can recover rent. There is no spend-time grace period.- Set
startTsto0to start the delegation when the transaction lands on chain instead of at a fixed time. This widens the window to get the user to sign and onboard. When you do this,expiryTsmust be non-zero — a start-on-landing delegation cannot also be set to never expire. - The user signs setup and revoke transactions. The delegatee signs transfers.
Is this page helpful?