Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

A user initializes a subscription authority

Source: subscriptions/tests/test_initialize_subscription_authority.rs L92

  • When: the user initializes a subscription authority
  • Then: initSubscriptionAuthority executes source-free ✓
  • Then: subscriptionAuthority.user = 2rj1EEgg32bupe12mMYnPFaKQCPPyYBhKYmDFZ8cXxpT ✓
  • Then: subscriptionAuthority.tokenMint = HhbhtPWNt5Rd2gvrjhGCekommdNQwVLY6ZMp3aBv4xtG ✓
  • Then: subscriptionAuthority.payer = 2rj1EEgg32bupe12mMYnPFaKQCPPyYBhKYmDFZ8cXxpT ✓

Result: ✓ success — 7801 CU · claims 4/4 ✓

sequenceDiagram
    participant p0 as alice
    participant p1 as Subscriptions
    participant p2 as system
    participant p3 as token
    p0->>p1: initSubscriptionAuthority
    activate p1
    p1->>p2: createAccount
    activate p2
    p2-->>p1: ✓
    deactivate p2
    p1->>p3: approve
    activate p3
    p3-->>p1: ✓ 126cu
    deactivate p3
    p1-->>p0: ✓ 7801cu
    deactivate p1

Authority

flowchart LR
    Subscriptions["Subscriptions"]:::program
    alice(["alice"]):::signer
    SAaliceMint(["SA(alice, Mint)"]):::signer
    aliceATAMint[("alice/ATA(Mint)")]:::state
    system["system"]:::program
    token["token"]:::program
    alice -->|signs| Subscriptions
    Subscriptions -->|writes| SAaliceMint
    Subscriptions -->|writes| aliceATAMint
    alice -->|signs| system
    SAaliceMint -->|signs| system
    token -->|writes| aliceATAMint
    alice -->|signs| token
    classDef program fill:#dae8fc,stroke:#6c8ebf;
    classDef signer fill:#d5e8d4,stroke:#82b366;
    classDef state fill:#ffe6cc,stroke:#d79b00;

Ownership

flowchart LR
    system["system"]:::program
    alice[("alice")]:::state
    Subscriptions["Subscriptions"]:::program
    SAaliceMint[("SA(alice, Mint)")]:::state
    token["token"]:::program
    aliceATAMint[("alice/ATA(Mint)")]:::state
    system -->|owns| alice
    Subscriptions -->|owns| SAaliceMint
    token -->|owns| aliceATAMint
    classDef program fill:#dae8fc,stroke:#6c8ebf;
    classDef signer fill:#d5e8d4,stroke:#82b366;
    classDef state fill:#ffe6cc,stroke:#d79b00;

Accounts

AccountSignerWritableOwner
alicesystem
SA(alice, Mint)·Subscriptions
Mint··token
alice/ATA(Mint)·token
system··Nati…
token··BPFL…
Call tree and logs
A user initializes a subscription authority
alice (7801cu)
└─ Subscriptions::initSubscriptionAuthority ✓ 7801cu
   ├─ system::createAccount ✓
   └─ token::approve ✓ 126cu
Program De1egAFMkMWZSN5rYXRj9CAdheBamobVNubTsi9avR44 invoke [1]
Program 11111111111111111111111111111111 invoke [2]
Program 11111111111111111111111111111111 success
Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]
Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 126 of 192342 compute units
Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success
Program De1egAFMkMWZSN5rYXRj9CAdheBamobVNubTsi9avR44 consumed 7801 of 200000 compute units
Program De1egAFMkMWZSN5rYXRj9CAdheBamobVNubTsi9avR44 success

Integration

What an integrator writes: the derivations and the instruction, byte-identical to the transaction this page witnessed. Addresses are this scenario’s; supply your own.

#![allow(unused)]
fn main() {
use solana_instruction::{AccountMeta, Instruction};
use solana_pubkey::{pubkey, Pubkey};

// --- initSubscriptionAuthority ---
let program_id: Pubkey = pubkey!("De1egAFMkMWZSN5rYXRj9CAdheBamobVNubTsi9avR44");
let owner: Pubkey = pubkey!("2rj1EEgg32bupe12mMYnPFaKQCPPyYBhKYmDFZ8cXxpT"); // alice: this scenario's value; supply your own
let token_mint: Pubkey = pubkey!("HhbhtPWNt5Rd2gvrjhGCekommdNQwVLY6ZMp3aBv4xtG"); // Mint: this scenario's value; supply your own
let user_ata: Pubkey = pubkey!("3fVRopUs1BsF6RqteSmcyctBCx2YQ6Zkv7prsUz95VHY"); // supply your own
let system_program: Pubkey = pubkey!("11111111111111111111111111111111"); // the system program
let token_program: Pubkey = pubkey!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); // the token program
let subscription_authority = Pubkey::find_program_address(&[b"SubscriptionAuthority", owner.as_ref(), token_mint.as_ref()], &program_id).0;
let ix = Instruction {
    program_id,
    accounts: vec![
        AccountMeta::new(owner, true), // owner
        AccountMeta::new(subscription_authority, false), // subscriptionAuthority
        AccountMeta::new_readonly(token_mint, false), // tokenMint
        AccountMeta::new(user_ata, false), // userAta
        AccountMeta::new_readonly(system_program, false), // systemProgram
        AccountMeta::new_readonly(token_program, false), // tokenProgram
    ],
    // discriminator ++ borsh()
    data: vec![0x00],
};
}

The same call through the Codama-generated TypeScript client (the async builder derives every PDA the resolver derived here, so only the free inputs appear):

import { address } from "@solana/kit";
import { getInitSubscriptionAuthorityInstructionAsync } from "your-generated-client";

const ix = await getInitSubscriptionAuthorityInstructionAsync({
  owner: ownerSigner, // alice (a TransactionSigner)
  tokenMint: address("HhbhtPWNt5Rd2gvrjhGCekommdNQwVLY6ZMp3aBv4xtG"), // Mint
  userAta: address("3fVRopUs1BsF6RqteSmcyctBCx2YQ6Zkv7prsUz95VHY"),
  // systemProgram defaults to the system program
  // tokenProgram defaults to the token program
});
How this page verifies itself: the test that ran it

The narrative above is this test; the page and the code cannot drift.

#![allow(unused)]
fn main() {
#[test]
fn initialize_subscription_authority() {
    let mut story = Story::load(SO, IDL);
    let alice = story.cast("alice");
    let mint = story.mint(&alice, MINT_DECIMALS);
    story.fund(&alice, &[(mint, 1_000_000)]);
    let user_ata = story.ata(&alice.pubkey(), &mint);

    let outcome = story.init_subscription_authority(InitAuthorityArgs {
        user: &alice,
        mint,
        sponsor: None,
        token_program: None,
    });
    story.then_holds(
        "initSubscriptionAuthority executes source-free",
        outcome.success,
    );

    let pda = story.subscription_authority_pda(&alice.pubkey(), &mint);
    let user = story.track_field::<Pubkey>("subscriptionAuthority", pda, "user");
    let token_mint = story.track_field::<Pubkey>("subscriptionAuthority", pda, "tokenMint");
    let payer = story.track_field::<Pubkey>("subscriptionAuthority", pda, "payer");
    let _init_id = story.track_field::<i64>("subscriptionAuthority", pda, "initId");

    story.then_eq(&user, alice.pubkey());
    story.then_eq(&token_mint, mint);
    // With no sponsor, the payer defaults to the user.
    story.then_eq(&payer, alice.pubkey());
    // The witness validates the init_id field exists and is an i64

    // The instruction's Approve CPI delegates the ATA to the authority in full.
    assert_eq!(
        story.ata_delegate(&user_ata),
        Some(pda),
        "the ATA is delegated to the authority"
    );
    assert_eq!(
        story.ata_delegated_amount(&user_ata),
        u64::MAX,
        "the delegated amount is u64::MAX"
    );
    emit_scenario(
        &story,
        &outcome,
        "A user initializes a subscription authority",
    );
}
}