Skip to content

Commit 0abe19b

Browse files
committed
Merge remote-tracking branch 'origin/main' into 176-upgradeable-proxy
2 parents ea6482f + 178873d commit 0abe19b

30 files changed

Lines changed: 2435 additions & 162 deletions

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"kiroAgent.configureMCP": "Disabled"
3+
}

App.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { StatusBar } from 'expo-status-bar';
33
import { AppNavigator } from './src/navigation/AppNavigator';
44
import { useNotifications } from './src/hooks/useNotifications';
55
import { useTransactionQueue } from './src/hooks/useTransactionQueue';
6+
import ErrorBoundary from './src/components/ErrorBoundary';
67

78
// Import WalletConnect compatibility layer
89
import '@walletconnect/react-native-compat';
@@ -73,8 +74,10 @@ export default function App() {
7374
return (
7475
<>
7576
<StatusBar style="light" />
76-
<NotificationBootstrap />
77-
<AppNavigator />
77+
<ErrorBoundary>
78+
<NotificationBootstrap />
79+
<AppNavigator />
80+
</ErrorBoundary>
7881
<AppKit />
7982
</>
8083
);
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
# Runbook: Subscription Lifecycle Operations
2+
3+
Covers day-to-day operational procedures for managing subscription states on the SubTrackr Soroban contract.
4+
5+
## Subscription States
6+
7+
```
8+
subscribe()
9+
10+
11+
[Active] ──pause_subscription()──► [Paused] ──resume_subscription()──► [Active]
12+
│ │
13+
│ │
14+
└──cancel_subscription()────────────┘
15+
16+
17+
[Cancelled]
18+
19+
charge_subscription() ──► payment fails ──► [PastDue] (manual intervention required)
20+
```
21+
22+
| Status | Billable | Can Pause | Can Cancel | Can Charge |
23+
| ------ | -------- | --------- | ---------- | ---------- |
24+
| Active | Yes | Yes | Yes | Yes |
25+
| Paused | No | No | Yes | No |
26+
| Cancelled | No | No | No | No |
27+
| PastDue | No | No | Yes | No |
28+
29+
---
30+
31+
## Procedures
32+
33+
### Create a Subscription Plan
34+
35+
```bash
36+
soroban contract invoke \
37+
--id $CONTRACT_ID \
38+
--network $NETWORK \
39+
-- create_plan \
40+
--merchant $MERCHANT_ADDRESS \
41+
--name "Plan Name" \
42+
--price 10000000 \ # in stroops (1 XLM = 10,000,000 stroops)
43+
--token $TOKEN_ADDRESS \
44+
--interval Monthly # Weekly | Monthly | Quarterly | Yearly
45+
```
46+
47+
Verify the plan was created:
48+
49+
```bash
50+
soroban contract invoke \
51+
--id $CONTRACT_ID \
52+
--network $NETWORK \
53+
-- get_plan \
54+
--plan_id <RETURNED_ID>
55+
```
56+
57+
---
58+
59+
### Charge a Due Subscription
60+
61+
`charge_subscription` is permissionless — any caller can trigger it.
62+
63+
```bash
64+
soroban contract invoke \
65+
--id $CONTRACT_ID \
66+
--network $NETWORK \
67+
-- charge_subscription \
68+
--subscription_id <ID>
69+
```
70+
71+
Common errors:
72+
73+
| Error | Cause | Action |
74+
| ----- | ----- | ------ |
75+
| `Subscription not active` | Status is Paused/Cancelled | Check status with `get_subscription` |
76+
| `Payment not yet due` | `next_charge_at` is in the future | Wait until due date |
77+
78+
---
79+
80+
### Process a Refund
81+
82+
**Step 1 — Subscriber requests refund:**
83+
84+
```bash
85+
soroban contract invoke \
86+
--id $CONTRACT_ID \
87+
--network $NETWORK \
88+
-- request_refund \
89+
--subscription_id <ID> \
90+
--amount <STROOPS>
91+
```
92+
93+
**Step 2 — Admin approves or rejects (requires admin key):**
94+
95+
```bash
96+
# Approve
97+
soroban contract invoke \
98+
--id $CONTRACT_ID \
99+
--network $NETWORK \
100+
--source $ADMIN_KEY \
101+
-- approve_refund \
102+
--subscription_id <ID>
103+
104+
# Reject
105+
soroban contract invoke \
106+
--id $CONTRACT_ID \
107+
--network $NETWORK \
108+
--source $ADMIN_KEY \
109+
-- reject_refund \
110+
--subscription_id <ID>
111+
```
112+
113+
Refund events emitted: `refund_requested`, `refund_approved`, `refund_rejected`.
114+
115+
---
116+
117+
### Deactivate a Plan
118+
119+
Prevents new subscribers. Existing subscriptions are unaffected.
120+
121+
```bash
122+
soroban contract invoke \
123+
--id $CONTRACT_ID \
124+
--network $NETWORK \
125+
-- deactivate_plan \
126+
--merchant $MERCHANT_ADDRESS \
127+
--plan_id <ID>
128+
```
129+
130+
> Deactivation is irreversible. Confirm with the merchant before proceeding.
131+
132+
---
133+
134+
### Query Subscription State
135+
136+
```bash
137+
# Get a single subscription
138+
soroban contract invoke --id $CONTRACT_ID --network $NETWORK \
139+
-- get_subscription --subscription_id <ID>
140+
141+
# Get all subscriptions for a user
142+
soroban contract invoke --id $CONTRACT_ID --network $NETWORK \
143+
-- get_user_subscriptions --subscriber $ADDRESS
144+
145+
# Get all plans for a merchant
146+
soroban contract invoke --id $CONTRACT_ID --network $NETWORK \
147+
-- get_merchant_plans --merchant $ADDRESS
148+
```
149+
150+
---
151+
152+
## Billing Cycle Reference
153+
154+
| Interval | Seconds | Approximate Duration |
155+
| -------- | ------- | -------------------- |
156+
| Weekly | 604,800 | 7 days |
157+
| Monthly | 2,592,000 | 30 days |
158+
| Quarterly | 7,776,000 | 90 days |
159+
| Yearly | 31,536,000 | 365 days |
160+
161+
`next_charge_at = last_charged_at + interval_seconds`
162+
163+
---
164+
165+
## Notification Sync
166+
167+
After any subscription mutation, the mobile app syncs renewal reminders:
168+
169+
```ts
170+
// Triggered automatically by subscriptionStore mutations
171+
await notificationService.syncRenewalReminders(subscriptions);
172+
```
173+
174+
Reminders are scheduled:
175+
- 1 day before `nextBillingDate` if sufficient lead time exists
176+
- 1 hour before `nextBillingDate` otherwise
177+
178+
Notifications are skipped when `isActive === false` or `notificationsEnabled === false`.
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
# Runbook: Incident Response
2+
3+
Procedures for detecting, classifying, escalating, and resolving incidents in SubTrackr.
4+
5+
---
6+
7+
## Severity Classification
8+
9+
| Severity | Criteria | Initial Response | Escalation |
10+
| -------- | -------- | ---------------- | ---------- |
11+
| P1 — Critical | Service down, mass payment failures, data loss, security breach | 15 min | Immediate — wake on-call lead |
12+
| P2 — High | Failure rate >10%, notification outage, contract unreachable | 1 hour | After 30 min without resolution |
13+
| P3 — Medium | Single-user billing issue, degraded performance | 4 hours | Next business day if unresolved |
14+
| P4 — Low | UI glitch, minor doc gap, non-critical warning | Next business day | N/A |
15+
16+
---
17+
18+
## Incident Response Lifecycle
19+
20+
```
21+
Detect → Triage → Contain → Investigate → Resolve → Post-mortem
22+
```
23+
24+
### 1. Detect
25+
26+
Alerts fire from:
27+
- `MonitoringService` — transaction failure rate >30% triggers `high-failure-rate` alert
28+
- `MonitoringService` — avg gas >500,000 triggers `gas-spike` alert
29+
- `AlertingService` — dispatches to Slack / PagerDuty / console
30+
- Manual report from user or merchant
31+
32+
### 2. Triage
33+
34+
Acknowledge the alert in PagerDuty within the SLA window. Determine:
35+
36+
- Is the contract reachable on Soroban RPC?
37+
- Is the failure isolated (single user/plan) or widespread?
38+
- Is there a security component (unauthorized access, data exposure)?
39+
40+
```bash
41+
# Quick health check — get total subscription count
42+
soroban contract invoke \
43+
--id $CONTRACT_ID \
44+
--network $NETWORK \
45+
-- get_subscription_count
46+
```
47+
48+
### 3. Contain
49+
50+
Stop the bleeding before investigating root cause.
51+
52+
| Scenario | Containment Action |
53+
| -------- | ------------------ |
54+
| Mass payment failures | Pause affected plans via `deactivate_plan` |
55+
| Compromised admin key | Rotate key; redeploy contract if necessary |
56+
| Runaway charge loop | Identify caller; block at RPC level if possible |
57+
| Corrupted local state | Trigger DR failover (see [DISASTER_RECOVERY_RUNBOOK.md](../DISASTER_RECOVERY_RUNBOOK.md)) |
58+
59+
### 4. Investigate
60+
61+
Check monitoring dashboard:
62+
63+
```ts
64+
const dashboard = monitoringService.getDashboard();
65+
// {
66+
// totalTransactions, successRate, failureCount,
67+
// avgGasUsed, activeAlerts, recentMetrics
68+
// }
69+
```
70+
71+
Query audit log for suspicious activity:
72+
73+
```ts
74+
const events = auditService.query({
75+
from: Date.now() - 3_600_000, // last hour
76+
action: 'SUBSCRIPTION_CANCELLED',
77+
});
78+
```
79+
80+
Check active alerts:
81+
82+
```ts
83+
const alerts = monitoringService.getActiveAlerts();
84+
```
85+
86+
### 5. Resolve
87+
88+
Apply fix. Resolve the alert once confirmed stable:
89+
90+
```ts
91+
monitoringService.resolveAlert(alertId);
92+
```
93+
94+
Notify affected users via notification service if billing was impacted:
95+
96+
```ts
97+
await notificationService.presentChargeFailedNotification(sub, 'Service disruption — no charge applied');
98+
```
99+
100+
### 6. Post-mortem
101+
102+
For P1/P2 incidents, complete a post-mortem within 48 hours covering:
103+
104+
- Timeline of events
105+
- Root cause
106+
- Impact (users affected, revenue impact)
107+
- Corrective actions with owners and due dates
108+
109+
---
110+
111+
## Common Incident Scenarios
112+
113+
### Scenario A — High Transaction Failure Rate
114+
115+
**Alert:** `high-failure-rate` (failure rate >30%)
116+
117+
**Steps:**
118+
1. Check `dashboard.failureCount` and `dashboard.recentMetrics`
119+
2. Identify if failures are concentrated on a specific plan or token
120+
3. Verify token contract is operational on the relevant chain
121+
4. If token contract is down, deactivate affected plans temporarily
122+
5. Resolve alert once failure rate drops below threshold
123+
124+
---
125+
126+
### Scenario B — Contract Unreachable
127+
128+
**Symptoms:** All `soroban contract invoke` calls time out or return RPC errors.
129+
130+
**Steps:**
131+
1. Check Stellar network status: https://status.stellar.org
132+
2. Try alternate RPC endpoint (testnet: `https://soroban-testnet.stellar.org`, mainnet: `https://soroban.stellar.org`)
133+
3. If network-wide outage, communicate status to users; no action on contract needed
134+
4. If isolated RPC issue, switch `SOROBAN_RPC_URL` env var and redeploy app config
135+
136+
---
137+
138+
### Scenario C — Unauthorized Refund Approvals
139+
140+
**Symptoms:** Unexpected `refund_approved` events in contract event stream.
141+
142+
**Steps:**
143+
1. Immediately rotate the admin key
144+
2. Query all recent `approve_refund` calls via Soroban event stream
145+
3. Assess financial impact
146+
4. If contract admin key is compromised, redeploy contract with new admin
147+
5. File security advisory (see [security.md](../security.md))
148+
149+
---
150+
151+
### Scenario D — Notification Delivery Failure
152+
153+
**Symptoms:** Users not receiving billing reminders or charge notifications.
154+
155+
**Steps:**
156+
1. Check Expo push notification service status
157+
2. Verify `notificationService.getPermissionStatus()` returns `GRANTED` for affected users
158+
3. Confirm `syncRenewalReminders` is being called after subscription mutations
159+
4. Check Android notification channel is configured: `ensureAndroidNotificationChannel()`
160+
5. Re-sync reminders manually if needed
161+
162+
---
163+
164+
## Escalation Contacts
165+
166+
| Condition | Escalate To |
167+
| --------- | ----------- |
168+
| P1 security incident | Security team + on-call lead immediately |
169+
| Contract redeployment needed | Contract admin key holder |
170+
| Stellar network outage | Monitor https://status.stellar.org; no internal escalation |
171+
| Persistent P2 >2 hours | Engineering lead |

0 commit comments

Comments
 (0)