A merchant deletes a plan
Source: subscriptions/tests/test_delete_plan.rs L118
- When: the merchant creates plan 1
- When: the owner updates the plan
- Then: the merchant sunsets the plan ✓
- Then: the plan account holds rent before deletion ✓
- When: the owner deletes the plan
- Then: the merchant deletes the expired plan ✓
- Then: the plan account is drained ✓
- Then: the merchant’s balance grew ✓
- Then: the reclaimed rent (less fees) lands with the owner ✓
Result: ✓ success — 365 CU · claims 6/6 ✓
sequenceDiagram
participant p0 as merchant
participant p1 as Subscriptions
p0->>p1: deletePlan
activate p1
p1-->>p0: ✓ 365cu
deactivate p1
Authority
flowchart LR
Subscriptions["Subscriptions"]:::program
merchant(["merchant"]):::signer
planmerchant1[("plan(merchant, 1)")]:::state
merchant -->|signs| Subscriptions
Subscriptions -->|writes| planmerchant1
classDef program fill:#dae8fc,stroke:#6c8ebf;
classDef signer fill:#d5e8d4,stroke:#82b366;
classDef state fill:#ffe6cc,stroke:#d79b00;
Ownership
flowchart LR
system["system"]:::program
merchant[("merchant")]:::state
planmerchant1[("plan(merchant, 1)")]:::state
system -->|owns| merchant
system -->|owns| planmerchant1
classDef program fill:#dae8fc,stroke:#6c8ebf;
classDef signer fill:#d5e8d4,stroke:#82b366;
classDef state fill:#ffe6cc,stroke:#d79b00;
Accounts
| Account | Signer | Writable | Owner |
|---|---|---|---|
| merchant | ✓ | ✓ | system |
| plan(merchant, 1) | · | ✓ | system |
Call tree and logs
A merchant deletes a plan
merchant (365cu)
└─ Subscriptions::deletePlan ✓ 365cu
Program De1egAFMkMWZSN5rYXRj9CAdheBamobVNubTsi9avR44 invoke [1]
Program De1egAFMkMWZSN5rYXRj9CAdheBamobVNubTsi9avR44 consumed 365 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};
// --- deletePlan ---
let program_id: Pubkey = pubkey!("De1egAFMkMWZSN5rYXRj9CAdheBamobVNubTsi9avR44");
let owner: Pubkey = pubkey!("4jnW4cX63scWb3VGDVaacYunDWNFsGERr3YYUKPuNxJp"); // merchant: this scenario's value; supply your own
let plan_pda: Pubkey = pubkey!("2PQuEytmHE7C2a9ynpmsRfYK7UgXNtCqSR4qnNNgeZwp"); // plan(merchant, 1): this scenario's value; supply your own
let ix = Instruction {
program_id,
accounts: vec![
AccountMeta::new(owner, true), // owner
AccountMeta::new(plan_pda, false), // planPda
],
// discriminator ++ borsh()
data: vec![0x09],
};
}
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 { getDeletePlanInstructionAsync } from "your-generated-client";
const ix = await getDeletePlanInstructionAsync({
owner: ownerSigner, // merchant (a TransactionSigner)
planPda: address("2PQuEytmHE7C2a9ynpmsRfYK7UgXNtCqSR4qnNNgeZwp"), // plan(merchant, 1)
});
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 delete_plan_happy_path() {
let mut story = Story::load(SO, IDL);
let end_ts = story.now() + days(2);
let plan_pda = stage_plan(&mut story, end_ts);
let owner = merchant(&mut story);
let update_outcome = story.update_plan(
&owner,
plan_pda,
UpdatePlanArgs {
status: STATUS_SUNSET,
end_ts,
..Default::default()
},
);
story.then_holds("the merchant sunsets the plan", update_outcome.success);
story.warp(days(3) as u64);
let rent = lamports(&story, &plan_pda);
story.then_holds("the plan account holds rent before deletion", rent > 0);
let owner_before = lamports(&story, &owner.pubkey());
let outcome = story.delete_plan(&owner, plan_pda);
story.then_holds("the merchant deletes the expired plan", outcome.success);
story.then_holds("the plan account is drained", is_drained(&story, &plan_pda));
let owner_after = lamports(&story, &owner.pubkey());
story.then_holds("the merchant's balance grew", owner_after > owner_before);
story.then(
"the reclaimed rent (less fees) lands with the owner",
move |_| owner_after >= owner_before + rent - 10_000,
);
emit_scenario(&story, &outcome, "A merchant deletes a plan");
}
}