npm package: @marinade.finance/notifications-ts-subscription-client
Description: TypeScript client for the Marinade notification subscription API. Allows users to subscribe/unsubscribe to notifications and list active subscriptions.
Latest version: 1.0.3

# ts-subscription-client

TypeScript client for the Marinade notification subscription API.
Allows users to subscribe/unsubscribe to notifications and list
active subscriptions.

## Usage

```typescript
import {
  createSubscriptionClient,
  subscribeMessage,
  unsubscribeMessage,
  listSubscriptionsMessage,
} from '@marinade.finance/notifications-ts-subscription-client'

const client = createSubscriptionClient({
  base_url: 'https://notifications.marinade.finance',
})
```

The message format is built by helper functions (`subscribeMessage`,
`unsubscribeMessage`, `listSubscriptionsMessage`). Each request
includes a signature and message for server-side authentication.

For local development and testing subscriptions end-to-end, see
[notification-service/DEV_GUIDE.md](../notification-service/DEV_GUIDE.md).

## Channels

A "channel" is a delivery medium for notifications. Each subscription
binds a user to a channel + address pair.

### telegram

Telegram subscriptions use a deep-link activation flow:

1. **Subscribe** — the client sends a `POST /v1/subscriptions` request
   with `channel=telegram`. The server creates a subscription with
   `telegram_status='pending'` and returns a `deep_link` in the
   response (e.g., `https://t.me/MarinadeBot?start=feature_sam_auction-<uuid>`).

2. **Activate** — the user opens the deep link in Telegram and
   presses "Start". The telegram-bot processes the link and maps
   the user's chat to the subscription. From this point on,
   notifications are delivered as Telegram messages.

The `telegram_status` field tracks delivery state:
`pending` → `active` → `inactive` / `unsubscribed` / `blocked`

### api

The API channel stores notifications in a server-side outbox.
Clients pull them via the read API (notifications endpoint).
No activation step is needed — subscribing is sufficient.

## Notification types

### sam_auction

SAM auction notifications for validators. Subscribing requires
proof of authority over a bond — the signer must be the bond
authority, validator identity, or vote account owner. The server
verifies this against on-chain bond accounts.

The `additional_data` field for subscribe/unsubscribe should include:

```typescript
{
  config_address: string // bonds program config
  vote_account: string // validator vote account
  bond_pubkey: string // derived bond PDA
}
```

For listing subscriptions (`GET /v1/subscriptions`), no `additional_data`
is needed — the server performs a reverse lookup from the pubkey.

## Known limitations

### Telegram subscription lifecycle

The notification service tracks `telegram_status` for each telegram subscription:
`pending` → `active` → `inactive` / `unsubscribed` / `blocked`.

The consumer filters by `telegram_status` before attempting delivery.
It does not attempt to send notifications to subscriptions marked as `inactive`,
`unsubscribed`, or `blocked`. The status is updated based on the telegram-bot's
`/send` response:

- **200** → `active` (message delivered)
- **404** → `inactive` (subscription never existed in telegram-bot — bad external_id)
- **410 "Subscription inactive"** → `unsubscribed` (user unsubscribed via bot UI)
- **410 "User blocked bot"** → `blocked` (user blocked the bot in Telegram)
- **429 / 5xx** → retry with backoff, status unchanged

#### Re-subscribe after unsubscribe or block

When a user unsubscribes via the Telegram bot UI or blocks the bot, the
notification service marks the subscription as `unsubscribed` or `blocked` and
stops delivery. Clicking the original Telegram deep link again reactivates the
subscription on the telegram-bot side, but the notification service is not
notified — it still sees the old status and skips delivery.

To re-subscribe, the user must use the **CLI subscribe command**. This calls
`POST /v1/subscriptions` which resets `telegram_status` to `pending`, allowing
delivery to resume on the next event.