---
name: solana-subscriptions
description: |
  Subscribe to Solana on-chain recurring payment plans using a Dynamic MPC wallet.
  Handles wallet creation, balance checks, SOL→USDC swaps, and the full
  lifecycle for both sides: subscribers (subscribe / cancel / view) and merchants
  (create-plan / collect / sunset / delete) — all from a single script.
allowed-tools:
  - Bash
  - Read
---

# Solana Subscriptions

Use this skill when the user asks to:
- Subscribe to a service, newsletter, or recurring payment plan on Solana
- Cancel a Solana subscription
- View their active subscriptions
- Send SOL or an SPL token to another address (or sweep the whole wallet)
- Create / publish a subscription plan (merchant flow)
- Collect payment from subscribers (merchant flow)
- Retire (sunset) or delete a plan (merchant flow)
- Set up a wallet for Solana subscriptions

The underlying script is `subscriptions-skill.mjs` in this directory. It signs every
transaction with a Dynamic MPC wallet. Sensitive secrets (the key share, API token,
and passphrases) are stored in the macOS Keychain when available — falling back to
`.env` otherwise — and are never logged.

---

## Prerequisites

Only two manual steps — the wallet bootstraps itself after that.

1. **Install dependencies once** (from this directory):
   ```bash
   pnpm install
   ```

2. **Configure credentials** — copy `.env.example` → `.env` and fill in:
   - `DYNAMIC_ENVIRONMENT_ID` — from https://app.dynamic.xyz → Settings
   - `DYNAMIC_AUTH_TOKEN` — a `dyn_...` token from the same page

That's it. Any command auto-creates the wallet on first use (you can still run
`pnpm subs setup` explicitly if you prefer). USDC can be sent directly or minted
from SOL with `pnpm subs swap <amount_usdc>`.

---

## Wallet bootstrap & funding (important for the conversational flow)

The script handles its own prerequisites, so Claude should **just run the command
the user asked for** and react to what it prints:

- **No wallet yet?** The command prints `No wallet configured yet — creating one…`,
  creates the MPC wallet, and continues automatically. No separate setup step.
- **Wallet has no SOL?** Any on-chain command stops *before* doing anything and
  prints a funding prompt with the wallet address and the amount needed. When you
  see this, **relay the address to the user and ask them to send SOL** (there is no
  mainnet faucet — they fund it from an exchange or another wallet). After they
  confirm, re-run the same command, or run `pnpm subs balance` to verify it landed.

So a typical first interaction is: run the command → it creates the wallet → it asks
the user to fund the printed address → user funds it → re-run the command.

---

## Step-by-step flow

### 1 — Discover plans

Ask the user for the merchant's Solana wallet address, then:

```bash
pnpm subs list-plans <merchant_address>
```

Output example:
```
[0] HXq3...abc   0.100000 USDC/1h  (active)
    metadata: https://cryptonfas.com/plans/weekly
[1] 9Yz2...def   5.000000 USDC/7d  (active)
```

### 2 — Subscribe

```bash
pnpm subs subscribe <merchant_address> <plan_index>
```

The script will:
- Initialize a SubscriptionAuthority account if this is the user's first subscription
- Submit the `subscribe` transaction
- Print the subscription PDA for future reference

### 3 — Check subscriptions

```bash
pnpm subs subs
```

### 4 — Cancel

```bash
pnpm subs cancel <plan_pda> <sub_pda>
```

Cancellation queues the expiry at end of the current period — the user keeps access until then.

---

## Send & sweep funds

Send SOL or any SPL token to another address:

```bash
pnpm subs send <recipient> <amount> [SOL|USDC|<mint>]   # token defaults to SOL
pnpm subs send <recipient> all USDC                     # send the entire USDC balance
pnpm subs send-all <recipient>                          # sweep ALL SOL + USDC
```

Notes:
- `send-all` moves USDC first (it needs SOL to pay the fee and the recipient's
  token-account rent), then sweeps SOL. It leaves the rent-exempt minimum
  (~0.00089 SOL) behind, so the wallet ends up *almost* empty, not zero.
- Sending an SPL token creates the recipient's associated token account if needed
  (the sender pays that one-time rent).
- ⚠️ Transfers are irreversible. Confirm the recipient address with the user before
  sending, especially for `send-all`.

---

## Merchant flow

### Create a plan

```bash
pnpm subs create-plan <amount_usdc> <period_hours> [metadata_uri]
# e.g. 0.50 USDC every 24h:
pnpm subs create-plan 0.50 24 https://example.com/plan.json
```

Prints the plan PDA. The calling wallet is the merchant, the authorized puller, and
the payout destination. Share `<merchant_address>` so subscribers can `list-plans`.

### Collect payment

Within each billing period the merchant may pull up to the plan amount from a
subscriber. `<delegator>` is the subscriber's wallet:

```bash
pnpm subs collect <plan_pda> <sub_pda> <delegator> <amount_usdc>
```

> If `collect` fails with `Invalid account owner` immediately after a `subscribe`,
> the new accounts have not propagated to the RPC yet — wait a few seconds and retry.

### Sunset a plan (stop new subscriptions)

```bash
pnpm subs sunset-plan <plan_pda> [end_in_minutes]
```

Sets the plan to `Sunset`: no new subscriptions, existing ones honored until the end
date. The protocol requires the end date to be **at least one full billing period in
the future**, so the command never sets it sooner. A plan is **immutable once
sunset** — this is a one-shot change.

### Delete a plan (reclaim rent)

```bash
pnpm subs delete-plan <plan_pda>
```

Only works **after** a sunset plan's end date has passed (`PLAN_NOT_EXPIRED`
otherwise). Closes the plan account and returns its rent to the merchant.

---

## Secrets & security

- **At-rest secrets** (key share, API token, `WALLET_BACKUP_PASSWORD`) live in the
  **macOS Keychain** when available, not `.env`. `setup` writes them there.
- **`setup` backs the share up to Dynamic** (encrypted with `WALLET_BACKUP_PASSWORD`,
  auto-generated if unset) so it's recoverable.
- The **keystore passphrase is per-session and never stored** — `setup` prompts you
  to set one; signing reads it from `SUBS_KEYSTORE_PASSPHRASE` (export it) or prompts.
  This keeps the decryption key in a different place than the share.

Helper commands:

```bash
pnpm subs secrets          # move any secrets in .env into the Keychain & show locations
pnpm subs encrypt-shares   # encrypt the key share if you created the wallet without a passphrase
```

- **Never paste the keystore passphrase into chat** or run signing commands unattended
  with it exported. Encryption protects secrets at rest, not against code in the session.

---

## Handling common errors

| Error | Fix |
|---|---|
| `Missing credentials` | Copy `.env.example` → `.env` and fill in both values |
| `Key shares are encrypted but SUBS_KEYSTORE_PASSPHRASE is not set` | Export the passphrase used at setup before running |
| `no record of a prior credit` | Wallet needs SOL for gas — fund it |
| `0x205 alreadySubscribed` | Treated as success — subscription already active |
| `Invalid account owner` (right after subscribe) | RPC lag — wait a few seconds and retry `collect` |
| `custom program error: 0x1ff` (`INVALID_END_TS`) | Sunset end date must be ≥ one billing period out; let the command default it |
| `custom program error: 0x203` (`PLAN_NOT_EXPIRED`) | Can't delete until the sunset end date passes |
| `Insufficient` | Run `swap` to get USDC first |

---

## Natural language examples

> "Claude, use my Dynamic wallet to subscribe to Crypto NFAs weekly"

1. Run `list-plans <merchant>` to find the weekly plan index
2. Run `subscribe <merchant> <index>`

> "Subscribe me to this newsletter for 0.1 USDC/hour"

1. Run `list-plans <merchant>` to find the matching plan
2. Confirm price with user
3. Run `subscribe <merchant> <index>`

> "Cancel my crypto newsletter subscription"

1. Run `subs` to list active subscriptions
2. Identify the right one
3. Run `cancel <plan_pda> <sub_pda>`

> "Create a plan that charges 5 USDC a month"

1. Run `create-plan 5 720` (720h ≈ 30 days)
2. Share the merchant wallet address so subscribers can find it via `list-plans`
