A delegatee pulls against a fixed delegation
Source: subscriptions/tests/test_transfer_fixed_delegation.rs L160
- When: the user initializes a subscription authority
- Then: initSubscriptionAuthority succeeds ✓
- When: a fixed delegation is created
- Then: createFixedDelegation succeeds ✓
- Then: Bob’s ATA starts empty ✓
- When: the delegatee pulls against the delegation
- Then: transfer_fixed succeeds ✓
- Then: Bob received 30 tokens ✓
- Then: fixedDelegation.amount = 20000000 ✓
- Then: fixedDelegation.expiryTs = 86400 ✓
Result: ✓ success — 5554 CU · claims 7/7 ✓
sequenceDiagram
participant p0 as bob
participant p1 as Subscriptions
participant p2 as token
p0->>p1: transferFixed
activate p1
p1->>p2: transferChecked
activate p2
p2-->>p1: ✓ 113cu
deactivate p2
p1->>p1: emit:fixedTransferEvent
activate p1
p1-->>p1: ✓ 137cu
deactivate p1
p1-->>p0: ✓ 5554cu
deactivate p1
Authority
flowchart LR
Subscriptions["Subscriptions"]:::program
B1u4[("B1u4…")]:::state
aliceATAMint[("alice/ATA(Mint)")]:::state
bobATAMint[("bob/ATA(Mint)")]:::state
bob(["bob"]):::signer
token["token"]:::program
SAaliceMint(["SA(alice, Mint)"]):::signer
eventAuthority(["eventAuthority"]):::signer
Subscriptions -->|writes| B1u4
Subscriptions -->|writes| aliceATAMint
Subscriptions -->|writes| bobATAMint
bob -->|signs| Subscriptions
token -->|writes| aliceATAMint
token -->|writes| bobATAMint
SAaliceMint -->|signs| token
eventAuthority -->|signs| Subscriptions
classDef program fill:#dae8fc,stroke:#6c8ebf;
classDef signer fill:#d5e8d4,stroke:#82b366;
classDef state fill:#ffe6cc,stroke:#d79b00;
Ownership
flowchart LR
Subscriptions["Subscriptions"]:::program
B1u4[("B1u4…")]:::state
token["token"]:::program
aliceATAMint[("alice/ATA(Mint)")]:::state
bobATAMint[("bob/ATA(Mint)")]:::state
system["system"]:::program
bob[("bob")]:::state
Subscriptions -->|owns| B1u4
token -->|owns| aliceATAMint
token -->|owns| bobATAMint
system -->|owns| bob
classDef program fill:#dae8fc,stroke:#6c8ebf;
classDef signer fill:#d5e8d4,stroke:#82b366;
classDef state fill:#ffe6cc,stroke:#d79b00;
Accounts
| Account | Signer | Writable | Owner |
|---|---|---|---|
| B1u4… | · | ✓ | Subscriptions |
| SA(alice, Mint) | · | · | Subscriptions |
| alice/ATA(Mint) | · | ✓ | token |
| bob/ATA(Mint) | · | ✓ | token |
| Mint | · | · | token |
| token | · | · | BPFL… |
| bob | ✓ | ✓ | system |
| eventAuthority | · | · | system |
| Subscriptions | · | · | BPFL… |
Call tree and logs
A delegatee pulls against a fixed delegation
bob (5554cu)
└─ Subscriptions::transferFixed ✓ 5554cu
├─ token::transferChecked ✓ 113cu
└─ Subscriptions::emit:fixedTransferEvent ✓ 137cu
Program De1egAFMkMWZSN5rYXRj9CAdheBamobVNubTsi9avR44 invoke [1]
Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]
Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 113 of 195993 compute units
Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success
Program De1egAFMkMWZSN5rYXRj9CAdheBamobVNubTsi9avR44 invoke [2]
Program De1egAFMkMWZSN5rYXRj9CAdheBamobVNubTsi9avR44 consumed 137 of 194605 compute units
Program De1egAFMkMWZSN5rYXRj9CAdheBamobVNubTsi9avR44 success
Program De1egAFMkMWZSN5rYXRj9CAdheBamobVNubTsi9avR44 consumed 5554 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};
// --- transferFixed ---
let program_id: Pubkey = pubkey!("De1egAFMkMWZSN5rYXRj9CAdheBamobVNubTsi9avR44");
let delegation_pda: Pubkey = pubkey!("B1u4dvZ4DrFrUm3tm4K7zqN1Nb9fu2NuAFExDfyDocqg"); // supply your own
let subscription_authority: Pubkey = pubkey!("4RDKDQRnKtZC5ti4wunSAayArwxdZida5St595CiKJ7i"); // SA(alice, Mint): this scenario's value; supply your own
let delegator_ata: Pubkey = pubkey!("3fVRopUs1BsF6RqteSmcyctBCx2YQ6Zkv7prsUz95VHY"); // supply your own
let receiver_ata: Pubkey = pubkey!("5UJQzG1wu6euuhQRnsHbqjKC8DHmfS4jVNzghS3PL4Hx"); // supply your own
let token_mint: Pubkey = pubkey!("HhbhtPWNt5Rd2gvrjhGCekommdNQwVLY6ZMp3aBv4xtG"); // Mint: this scenario's value; supply your own
let token_program: Pubkey = pubkey!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); // the token program
let delegatee: Pubkey = pubkey!("6DAhWE3pSJQrSFRNjkGQznpUi7wWnqWf3FnRyZQqxbvu"); // bob: this scenario's value; supply your own
let event_authority = Pubkey::find_program_address(&[b"event_authority"], &program_id).0;
let ix = Instruction {
program_id,
accounts: vec![
AccountMeta::new(delegation_pda, false), // delegationPda
AccountMeta::new_readonly(subscription_authority, false), // subscriptionAuthority
AccountMeta::new(delegator_ata, false), // delegatorAta
AccountMeta::new(receiver_ata, false), // receiverAta
AccountMeta::new_readonly(token_mint, false), // tokenMint
AccountMeta::new_readonly(token_program, false), // tokenProgram
AccountMeta::new_readonly(delegatee, true), // delegatee
AccountMeta::new_readonly(event_authority, false), // eventAuthority
AccountMeta::new_readonly(program_id, false), // selfProgram
],
// discriminator ++ borsh(transferData: {amount: 30000000, delegator: 2rj1…, mint: Hhbh…})
data: vec![0x04, 0x80, 0xc3, 0xc9, 0x01, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x98, 0xa3, 0xfc, 0x7a, 0x9f, 0x8b, 0x5c, 0x8b, 0x0e, 0xad, 0xe0, 0x77, 0x5a, 0x59, 0x3e, 0x44, 0x99, 0xa9, 0x76, 0x55, 0x97, 0xcd, 0x1b, 0xdc, 0xae, 0x63, 0xcd, 0x00, 0xf5, 0x19, 0xbc, 0xf8, 0x21, 0x76, 0xd4, 0x30, 0xe3, 0x1b, 0x7f, 0x8d, 0xe5, 0x16, 0xa8, 0xf6, 0xa3, 0x97, 0xc4, 0x57, 0x80, 0x12, 0x98, 0x73, 0x7a, 0xb3, 0x11, 0x69, 0xc0, 0x99, 0x8f, 0x1c, 0x02, 0x2b, 0xa1],
};
}
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 { getTransferFixedInstructionAsync } from "your-generated-client";
const ix = await getTransferFixedInstructionAsync({
delegationPda: address("B1u4dvZ4DrFrUm3tm4K7zqN1Nb9fu2NuAFExDfyDocqg"),
subscriptionAuthority: address("4RDKDQRnKtZC5ti4wunSAayArwxdZida5St595CiKJ7i"), // SA(alice, Mint)
delegatorAta: address("3fVRopUs1BsF6RqteSmcyctBCx2YQ6Zkv7prsUz95VHY"),
receiverAta: address("5UJQzG1wu6euuhQRnsHbqjKC8DHmfS4jVNzghS3PL4Hx"),
tokenMint: address("HhbhtPWNt5Rd2gvrjhGCekommdNQwVLY6ZMp3aBv4xtG"), // Mint
// tokenProgram defaults to the token program
delegatee: delegateeSigner, // bob (a TransactionSigner)
transferData: {
amount: 30000000n,
delegator: address("2rj1EEgg32bupe12mMYnPFaKQCPPyYBhKYmDFZ8cXxpT") /* alice */,
mint: address("HhbhtPWNt5Rd2gvrjhGCekommdNQwVLY6ZMp3aBv4xtG") /* Mint */
},
});
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 test_fixed_transfer_success() {
let mut story = Story::load(SO, IDL);
let amount: u64 = 50_000_000;
let expiry_ts = story.now() + days(1) as i64;
let (alice, bob, delegation_pda, mint, _alice_ata, bob_ata) =
setup_fixed_delegation(&mut story, amount, expiry_ts, 0);
story.then_holds("Bob's ATA starts empty", balance(&story, &bob_ata) == 0);
let out = story.transfer_fixed(xfer(&bob, delegation_pda, alice.pubkey(), mint, 30_000_000));
story.then_holds("transfer_fixed succeeds", out.success);
story.then_holds(
"Bob received 30 tokens",
balance(&story, &bob_ata) == 30_000_000,
);
let remaining = story.track_field::<u64>("fixedDelegation", delegation_pda, "amount");
story.then_eq(&remaining, 20_000_000u64);
let expiry = story.track_field::<i64>("fixedDelegation", delegation_pda, "expiryTs");
story.then_eq(&expiry, expiry_ts);
emit_scenario(&story, &out, "A delegatee pulls against a fixed delegation");
}
}