opencard 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API.md +564 -0
- package/DATABASE.md +143 -0
- package/README.md +106 -0
- package/admin/index.html +18 -0
- package/admin/package-lock.json +2663 -0
- package/admin/package.json +25 -0
- package/admin/postcss.config.js +6 -0
- package/admin/src/App.tsx +198 -0
- package/admin/src/index.css +44 -0
- package/admin/src/main.tsx +10 -0
- package/admin/src/pages/Cards.tsx +181 -0
- package/admin/src/pages/Dashboard.tsx +139 -0
- package/admin/src/pages/Transactions.tsx +223 -0
- package/admin/tailwind.config.js +27 -0
- package/admin/tsconfig.json +20 -0
- package/admin/tsconfig.tsbuildinfo +1 -0
- package/admin/vite.config.ts +17 -0
- package/drizzle.config.ts +11 -0
- package/examples/agent-client-sdk.ts +36 -0
- package/examples/agent-client.ts +111 -0
- package/examples/agent-fund-sdk.ts +35 -0
- package/package.json +41 -0
- package/sdk/README.md +139 -0
- package/sdk/package-lock.json +240 -0
- package/sdk/package.json +43 -0
- package/sdk/src/client.ts +194 -0
- package/sdk/src/errors.ts +66 -0
- package/sdk/src/index.ts +35 -0
- package/sdk/src/types.ts +138 -0
- package/sdk/src/x402.ts +158 -0
- package/sdk/tsconfig.json +20 -0
- package/src/config/env.ts +45 -0
- package/src/config/tiers.ts +51 -0
- package/src/db/index.ts +9 -0
- package/src/db/migrate.ts +16 -0
- package/src/db/schema.ts +82 -0
- package/src/index.ts +89 -0
- package/src/middleware/errorHandler.ts +27 -0
- package/src/middleware/rateLimit.ts +54 -0
- package/src/middleware/walletAuth.ts +89 -0
- package/src/middleware/x402.ts +194 -0
- package/src/routes/admin.ts +150 -0
- package/src/routes/cards.ts +120 -0
- package/src/routes/paid.ts +154 -0
- package/src/routes/public.ts +40 -0
- package/src/services/cardService.ts +395 -0
- package/src/services/kripicard.ts +128 -0
- package/src/services/walletService.ts +78 -0
- package/src/types/index.ts +128 -0
- package/src/utils/logger.ts +19 -0
- package/src/utils/pricing.ts +75 -0
- package/tsconfig.json +21 -0
package/API.md
ADDED
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
# OpenCard API Documentation
|
|
2
|
+
|
|
3
|
+
**Base URL**: `http://localhost:3000` (development) | `https://your-domain.com` (production)
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Authentication
|
|
8
|
+
|
|
9
|
+
OpenCard uses two authentication modes:
|
|
10
|
+
|
|
11
|
+
### 1. x402 Payment (Paid Endpoints)
|
|
12
|
+
|
|
13
|
+
This flow is designed for **AI agents** to autonomously pay for services.
|
|
14
|
+
|
|
15
|
+
1. **Agent Request**: `POST /cards/create/tier/10` (no payment)
|
|
16
|
+
2. **Server Response**: `402 Payment Required`
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"x402Version": 1,
|
|
20
|
+
"accepts": [
|
|
21
|
+
{
|
|
22
|
+
"scheme": "exact",
|
|
23
|
+
"network": "eip155:8453", // Base Mainnet
|
|
24
|
+
"asset": "0x8335...2913", // USDC Contract
|
|
25
|
+
"maxAmountRequired": "17200000", // 17.20 USDC (6 decimals)
|
|
26
|
+
"payTo": "0x3Be8...c288" // Treasury Address
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
3. **Agent Action**: Parse `accepts`, send 17.20 USDC to `payTo` on Base.
|
|
32
|
+
4. **Agent Retry**: Resend request with `X-Payment` header:
|
|
33
|
+
```json
|
|
34
|
+
// Header value is base64-encoded JSON:
|
|
35
|
+
{
|
|
36
|
+
"scheme": "exact",
|
|
37
|
+
"network": "eip155:8453",
|
|
38
|
+
"payload": {
|
|
39
|
+
"authorization": {
|
|
40
|
+
"from": "0xAgentAddress",
|
|
41
|
+
"to": "0xTreasuryAddress",
|
|
42
|
+
"value": "17200000"
|
|
43
|
+
},
|
|
44
|
+
"txHash": "0xTxHash..."
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 2. Wallet Signature (Free Endpoints)
|
|
50
|
+
|
|
51
|
+
Sign an EIP-712 typed message and include these headers:
|
|
52
|
+
|
|
53
|
+
| Header | Description |
|
|
54
|
+
| -------------------- | ----------------------------------------- |
|
|
55
|
+
| `X-WALLET-ADDRESS` | Your `0x...` wallet address |
|
|
56
|
+
| `X-WALLET-SIGNATURE` | EIP-712 signature |
|
|
57
|
+
| `X-WALLET-TIMESTAMP` | Unix timestamp (must be within 5 minutes) |
|
|
58
|
+
|
|
59
|
+
**EIP-712 Domain:**
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"name": "OpenCard",
|
|
64
|
+
"version": "1",
|
|
65
|
+
"chainId": 8453
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**EIP-712 Types:**
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"Auth": [
|
|
74
|
+
{ "name": "action", "type": "string" },
|
|
75
|
+
{ "name": "timestamp", "type": "uint256" }
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Message:**
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"action": "opencard-auth",
|
|
85
|
+
"timestamp": 1707600000
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Public Endpoints
|
|
92
|
+
|
|
93
|
+
### `GET /health`
|
|
94
|
+
|
|
95
|
+
Health check.
|
|
96
|
+
|
|
97
|
+
**Response** `200`:
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"status": "ok",
|
|
102
|
+
"timestamp": "2026-02-11T14:00:00.000Z",
|
|
103
|
+
"version": "1.0.0"
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
### `GET /pricing`
|
|
110
|
+
|
|
111
|
+
Returns full pricing breakdown for all tiers.
|
|
112
|
+
|
|
113
|
+
**Response** `200`:
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"creation": {
|
|
118
|
+
"tiers": [
|
|
119
|
+
{
|
|
120
|
+
"loadAmount": 10,
|
|
121
|
+
"totalCost": 17.2,
|
|
122
|
+
"issuanceFee": 3.0,
|
|
123
|
+
"topUpFee": 2.2,
|
|
124
|
+
"ourFee": 2.0,
|
|
125
|
+
"endpoint": "/cards/create/tier/10"
|
|
126
|
+
}
|
|
127
|
+
]
|
|
128
|
+
},
|
|
129
|
+
"funding": {
|
|
130
|
+
"tiers": [
|
|
131
|
+
{
|
|
132
|
+
"fundAmount": 10,
|
|
133
|
+
"totalCost": 14.2,
|
|
134
|
+
"topUpFee": 2.2,
|
|
135
|
+
"ourFee": 2.0,
|
|
136
|
+
"endpoint": "/cards/fund/tier/10"
|
|
137
|
+
}
|
|
138
|
+
]
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
### `GET /cards/tiers`
|
|
146
|
+
|
|
147
|
+
Returns available tiers with endpoints and fee breakdowns.
|
|
148
|
+
|
|
149
|
+
**Response** `200`:
|
|
150
|
+
|
|
151
|
+
```json
|
|
152
|
+
{
|
|
153
|
+
"creation": [
|
|
154
|
+
{
|
|
155
|
+
"loadAmount": 10,
|
|
156
|
+
"totalCost": 17.2,
|
|
157
|
+
"endpoint": "/cards/create/tier/10",
|
|
158
|
+
"breakdown": {
|
|
159
|
+
"cardLoad": 10,
|
|
160
|
+
"issuanceFee": 3,
|
|
161
|
+
"topUpFee": 2.2,
|
|
162
|
+
"ourFee": 2,
|
|
163
|
+
"buffer": 0
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
],
|
|
167
|
+
"funding": [
|
|
168
|
+
{
|
|
169
|
+
"fundAmount": 10,
|
|
170
|
+
"totalCost": 14.2,
|
|
171
|
+
"endpoint": "/cards/fund/tier/10",
|
|
172
|
+
"breakdown": {
|
|
173
|
+
"fundAmount": 10,
|
|
174
|
+
"topUpFee": 2.2,
|
|
175
|
+
"ourFee": 2
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
]
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Paid Endpoints (x402)
|
|
185
|
+
|
|
186
|
+
These endpoints require USDC payment via the x402 protocol on Base.
|
|
187
|
+
|
|
188
|
+
### `POST /cards/create/tier/:amount`
|
|
189
|
+
|
|
190
|
+
Create a new virtual card loaded with the specified tier amount.
|
|
191
|
+
|
|
192
|
+
**Available tiers**: `10`, `25`, `50`, `100`, `200`, `500`
|
|
193
|
+
|
|
194
|
+
**Request Body:**
|
|
195
|
+
|
|
196
|
+
```json
|
|
197
|
+
{
|
|
198
|
+
"nameOnCard": "JOHN DOE",
|
|
199
|
+
"email": "john@example.com"
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**402 Response** (when no payment provided):
|
|
204
|
+
|
|
205
|
+
```json
|
|
206
|
+
{
|
|
207
|
+
"x402Version": 1,
|
|
208
|
+
"accepts": [
|
|
209
|
+
{
|
|
210
|
+
"scheme": "exact",
|
|
211
|
+
"network": "eip155:8453",
|
|
212
|
+
"maxAmountRequired": "17200000",
|
|
213
|
+
"resource": "/cards/create/tier/10",
|
|
214
|
+
"description": "Create card with $10 load",
|
|
215
|
+
"payTo": "0x3Be892b244B7CE6731A297A7eBca16F16f27c288",
|
|
216
|
+
"maxTimeoutSeconds": 300,
|
|
217
|
+
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
|
|
218
|
+
}
|
|
219
|
+
]
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
**Success Response** `201`:
|
|
224
|
+
|
|
225
|
+
```json
|
|
226
|
+
{
|
|
227
|
+
"success": true,
|
|
228
|
+
"card": {
|
|
229
|
+
"cardId": "uuid",
|
|
230
|
+
"kripiCardId": "kc_123",
|
|
231
|
+
"nameOnCard": "JOHN DOE",
|
|
232
|
+
"balance": 10,
|
|
233
|
+
"status": "active",
|
|
234
|
+
"createdAt": "2026-02-11T14:00:00.000Z"
|
|
235
|
+
},
|
|
236
|
+
"payment": {
|
|
237
|
+
"amountCharged": 17.2,
|
|
238
|
+
"txHash": "0x...",
|
|
239
|
+
"network": "base"
|
|
240
|
+
},
|
|
241
|
+
"details": {
|
|
242
|
+
"cardNumber": "4111111111111111",
|
|
243
|
+
"expiryMonth": 12,
|
|
244
|
+
"expiryYear": 2028,
|
|
245
|
+
"cvv": "123",
|
|
246
|
+
"billingAddress": {
|
|
247
|
+
"street": "123 Main St",
|
|
248
|
+
"city": "San Francisco",
|
|
249
|
+
"state": "CA",
|
|
250
|
+
"zip": "94105",
|
|
251
|
+
"country": "US"
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
### `POST /cards/fund/tier/:amount`
|
|
260
|
+
|
|
261
|
+
Add funds to an existing card.
|
|
262
|
+
|
|
263
|
+
**Available tiers**: `10`, `25`, `50`, `100`, `200`, `500`
|
|
264
|
+
|
|
265
|
+
**Request Body:**
|
|
266
|
+
|
|
267
|
+
```json
|
|
268
|
+
{
|
|
269
|
+
"cardId": "uuid-of-existing-card"
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
**Success Response** `200`:
|
|
274
|
+
|
|
275
|
+
```json
|
|
276
|
+
{
|
|
277
|
+
"success": true,
|
|
278
|
+
"cardId": "uuid",
|
|
279
|
+
"kripiCardId": "kc_123",
|
|
280
|
+
"fundedAmount": 25,
|
|
281
|
+
"newBalance": 35.0,
|
|
282
|
+
"payment": {
|
|
283
|
+
"amountCharged": 29.4,
|
|
284
|
+
"txHash": "0x...",
|
|
285
|
+
"network": "base"
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## Wallet-Signed Endpoints
|
|
293
|
+
|
|
294
|
+
All require [wallet signature auth headers](#2-wallet-signature-free-endpoints).
|
|
295
|
+
|
|
296
|
+
### `GET /cards`
|
|
297
|
+
|
|
298
|
+
List all cards owned by the authenticated wallet.
|
|
299
|
+
|
|
300
|
+
**Response** `200`:
|
|
301
|
+
|
|
302
|
+
```json
|
|
303
|
+
{
|
|
304
|
+
"cards": [
|
|
305
|
+
{
|
|
306
|
+
"cardId": "uuid",
|
|
307
|
+
"kripiCardId": "kc_123",
|
|
308
|
+
"nameOnCard": "JOHN DOE",
|
|
309
|
+
"lastFour": "****",
|
|
310
|
+
"balance": 10.0,
|
|
311
|
+
"status": "active",
|
|
312
|
+
"createdAt": "2026-02-11T14:00:00.000Z"
|
|
313
|
+
}
|
|
314
|
+
]
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
### `GET /cards/:cardId`
|
|
321
|
+
|
|
322
|
+
Get detailed card info (fetches live balance from KripiCard).
|
|
323
|
+
|
|
324
|
+
**Response** `200`:
|
|
325
|
+
|
|
326
|
+
```json
|
|
327
|
+
{
|
|
328
|
+
"card": {
|
|
329
|
+
"cardId": "uuid",
|
|
330
|
+
"kripiCardId": "kc_123",
|
|
331
|
+
"nameOnCard": "JOHN DOE",
|
|
332
|
+
"email": "john@example.com",
|
|
333
|
+
"balance": 8.5,
|
|
334
|
+
"initialAmountUsd": 10,
|
|
335
|
+
"status": "active",
|
|
336
|
+
"createdAt": "2026-02-11T14:00:00.000Z",
|
|
337
|
+
"updatedAt": "2026-02-11T15:30:00.000Z"
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
### `GET /cards/:cardId/details`
|
|
345
|
+
|
|
346
|
+
Get sensitive card details (number, CVV, expiry). **Rate limited: 3 requests per card per hour.**
|
|
347
|
+
|
|
348
|
+
**Response** `200`:
|
|
349
|
+
|
|
350
|
+
```json
|
|
351
|
+
{
|
|
352
|
+
"details": {
|
|
353
|
+
"cardNumber": "4111111111111111",
|
|
354
|
+
"expiryMonth": 12,
|
|
355
|
+
"expiryYear": 2028,
|
|
356
|
+
"cvv": "123",
|
|
357
|
+
"billingAddress": {
|
|
358
|
+
"street": "123 Main St",
|
|
359
|
+
"city": "San Francisco",
|
|
360
|
+
"state": "CA",
|
|
361
|
+
"zip": "94105",
|
|
362
|
+
"country": "US"
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
### `POST /cards/:cardId/freeze`
|
|
371
|
+
|
|
372
|
+
Freeze a card (blocks all transactions).
|
|
373
|
+
|
|
374
|
+
**Response** `200`:
|
|
375
|
+
|
|
376
|
+
```json
|
|
377
|
+
{
|
|
378
|
+
"success": true,
|
|
379
|
+
"cardId": "uuid",
|
|
380
|
+
"status": "frozen"
|
|
381
|
+
}
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
### `POST /cards/:cardId/unfreeze`
|
|
387
|
+
|
|
388
|
+
Unfreeze a previously frozen card.
|
|
389
|
+
|
|
390
|
+
**Response** `200`:
|
|
391
|
+
|
|
392
|
+
```json
|
|
393
|
+
{
|
|
394
|
+
"success": true,
|
|
395
|
+
"cardId": "uuid",
|
|
396
|
+
"status": "active"
|
|
397
|
+
}
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
---
|
|
401
|
+
|
|
402
|
+
## Admin Endpoints
|
|
403
|
+
|
|
404
|
+
All require `Authorization: Bearer <ADMIN_SECRET>` header.
|
|
405
|
+
|
|
406
|
+
### `GET /admin/api/stats`
|
|
407
|
+
|
|
408
|
+
System-wide statistics.
|
|
409
|
+
|
|
410
|
+
**Response** `200`:
|
|
411
|
+
|
|
412
|
+
```json
|
|
413
|
+
{
|
|
414
|
+
"cards": {
|
|
415
|
+
"totalCards": 15,
|
|
416
|
+
"activeCards": 12,
|
|
417
|
+
"frozenCards": 3
|
|
418
|
+
},
|
|
419
|
+
"transactions": {
|
|
420
|
+
"totalTransactions": 22,
|
|
421
|
+
"completedTransactions": 18,
|
|
422
|
+
"failedTransactions": 2,
|
|
423
|
+
"pendingTransactions": 2,
|
|
424
|
+
"totalRevenue": "156.00",
|
|
425
|
+
"totalVolume": "1250.00"
|
|
426
|
+
},
|
|
427
|
+
"wallets": {
|
|
428
|
+
"totalWallets": 8
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
---
|
|
434
|
+
|
|
435
|
+
### `GET /admin/api/transactions`
|
|
436
|
+
|
|
437
|
+
List transactions with optional filtering.
|
|
438
|
+
|
|
439
|
+
**Query Parameters:**
|
|
440
|
+
|
|
441
|
+
| Param | Type | Default | Description |
|
|
442
|
+
| -------- | ------ | ------- | ---------------------------------------- |
|
|
443
|
+
| `status` | string | — | Filter: `completed`, `failed`, `pending` |
|
|
444
|
+
| `limit` | number | 50 | Max results (capped at 100) |
|
|
445
|
+
| `offset` | number | 0 | Pagination offset |
|
|
446
|
+
|
|
447
|
+
**Response** `200`:
|
|
448
|
+
|
|
449
|
+
```json
|
|
450
|
+
{
|
|
451
|
+
"transactions": [
|
|
452
|
+
{
|
|
453
|
+
"id": "uuid",
|
|
454
|
+
"walletAddress": "0x...",
|
|
455
|
+
"cardId": "uuid",
|
|
456
|
+
"type": "card_creation",
|
|
457
|
+
"amountUsd": "17.20",
|
|
458
|
+
"cardAmountUsd": "10.00",
|
|
459
|
+
"feeUsd": "2.00",
|
|
460
|
+
"kripiCardFeeUsd": "5.20",
|
|
461
|
+
"txHash": "0x...",
|
|
462
|
+
"status": "completed",
|
|
463
|
+
"errorMessage": null,
|
|
464
|
+
"createdAt": "2026-02-11T14:00:00.000Z"
|
|
465
|
+
}
|
|
466
|
+
],
|
|
467
|
+
"limit": 50,
|
|
468
|
+
"offset": 0
|
|
469
|
+
}
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
---
|
|
473
|
+
|
|
474
|
+
### `GET /admin/api/cards`
|
|
475
|
+
|
|
476
|
+
List all cards with optional wallet filter.
|
|
477
|
+
|
|
478
|
+
**Query Parameters:**
|
|
479
|
+
|
|
480
|
+
| Param | Type | Default | Description |
|
|
481
|
+
| -------- | ------ | ------- | --------------------------- |
|
|
482
|
+
| `wallet` | string | — | Filter by wallet address |
|
|
483
|
+
| `limit` | number | 50 | Max results (capped at 100) |
|
|
484
|
+
| `offset` | number | 0 | Pagination offset |
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
### `GET /admin/api/wallets`
|
|
489
|
+
|
|
490
|
+
List all registered wallets (up to 100).
|
|
491
|
+
|
|
492
|
+
**Response** `200`:
|
|
493
|
+
|
|
494
|
+
```json
|
|
495
|
+
{
|
|
496
|
+
"wallets": [
|
|
497
|
+
{
|
|
498
|
+
"id": "uuid",
|
|
499
|
+
"address": "0x...",
|
|
500
|
+
"firstSeenAt": "2026-02-11T14:00:00.000Z",
|
|
501
|
+
"totalSpentUsd": "154.20",
|
|
502
|
+
"cardCount": 3
|
|
503
|
+
}
|
|
504
|
+
]
|
|
505
|
+
}
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
---
|
|
509
|
+
|
|
510
|
+
## Error Responses
|
|
511
|
+
|
|
512
|
+
All errors follow the format:
|
|
513
|
+
|
|
514
|
+
```json
|
|
515
|
+
{
|
|
516
|
+
"error": "Human-readable error message"
|
|
517
|
+
}
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
| Status | Meaning |
|
|
521
|
+
| ------ | ------------------------------------------ |
|
|
522
|
+
| `400` | Invalid request body or parameters |
|
|
523
|
+
| `401` | Missing or invalid authentication |
|
|
524
|
+
| `402` | Payment required (x402 flow) |
|
|
525
|
+
| `404` | Card not found or doesn't belong to wallet |
|
|
526
|
+
| `429` | Rate limit exceeded |
|
|
527
|
+
| `500` | Internal server error |
|
|
528
|
+
|
|
529
|
+
---
|
|
530
|
+
|
|
531
|
+
## Rate Limits
|
|
532
|
+
|
|
533
|
+
| Scope | Limit |
|
|
534
|
+
| --------------- | ---------------------------- |
|
|
535
|
+
| General API | 100 requests / 15 min per IP |
|
|
536
|
+
| Paid endpoints | 10 requests / 5 min per IP |
|
|
537
|
+
| Card details | 3 requests / hour per card |
|
|
538
|
+
| Admin endpoints | 30 requests / 15 min per IP |
|
|
539
|
+
|
|
540
|
+
---
|
|
541
|
+
|
|
542
|
+
## Pricing Tiers
|
|
543
|
+
|
|
544
|
+
### Card Creation
|
|
545
|
+
|
|
546
|
+
| Load | Issuance Fee | Top-Up Fee | Our Fee | **Total** |
|
|
547
|
+
| ---- | ------------ | ---------- | ------- | ----------- |
|
|
548
|
+
| $10 | $3.00 | $2.20 | $2.00 | **$17.20** |
|
|
549
|
+
| $25 | $3.00 | $2.50 | $2.00 | **$32.50** |
|
|
550
|
+
| $50 | $3.00 | $3.00 | $2.00 | **$58.00** |
|
|
551
|
+
| $100 | $3.00 | $4.00 | $3.00 | **$110.00** |
|
|
552
|
+
| $200 | $3.00 | $6.00 | $5.00 | **$214.00** |
|
|
553
|
+
| $500 | $3.00 | $12.00 | $7.00 | **$522.00** |
|
|
554
|
+
|
|
555
|
+
### Card Funding
|
|
556
|
+
|
|
557
|
+
| Amount | Top-Up Fee | Our Fee | **Total** |
|
|
558
|
+
| ------ | ---------- | ------- | ----------- |
|
|
559
|
+
| $10 | $2.20 | $2.00 | **$14.20** |
|
|
560
|
+
| $25 | $2.50 | $2.00 | **$29.50** |
|
|
561
|
+
| $50 | $3.00 | $2.00 | **$55.00** |
|
|
562
|
+
| $100 | $4.00 | $3.00 | **$107.00** |
|
|
563
|
+
| $200 | $6.00 | $5.00 | **$211.00** |
|
|
564
|
+
| $500 | $12.00 | $7.00 | **$519.00** |
|
package/DATABASE.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# OpenCard — Neon Database Setup
|
|
2
|
+
|
|
3
|
+
## 1. Create a Neon Project
|
|
4
|
+
|
|
5
|
+
1. Go to [https://neon.tech](https://neon.tech) and sign up / log in
|
|
6
|
+
2. Click **"New Project"**
|
|
7
|
+
3. Configure:
|
|
8
|
+
- **Project name**: `opencard`
|
|
9
|
+
- **Postgres version**: 16 (latest)
|
|
10
|
+
- **Region**: Pick closest to your server (e.g. `us-east-2` for US)
|
|
11
|
+
- **Compute size**: Free tier (0.25 CU) is fine for MVP
|
|
12
|
+
4. Click **"Create Project"**
|
|
13
|
+
|
|
14
|
+
## 2. Get Your Connection String
|
|
15
|
+
|
|
16
|
+
After project creation, Neon shows your connection details. Copy the **connection string**:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
postgresql://neondb_owner:YOUR_PASSWORD@ep-XXXXX.us-east-2.aws.neon.tech/neondb?sslmode=require
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
> [!IMPORTANT]
|
|
23
|
+
> Save this immediately — the password is only shown once. You can reset it later from the Neon dashboard under **Connection Details**.
|
|
24
|
+
|
|
25
|
+
## 3. Configure OpenCard
|
|
26
|
+
|
|
27
|
+
Add the connection string to your `.env` file:
|
|
28
|
+
|
|
29
|
+
```env
|
|
30
|
+
DATABASE_URL=postgresql://neondb_owner:YOUR_PASSWORD@ep-XXXXX.us-east-2.aws.neon.tech/neondb?sslmode=require
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## 4. Push the Schema
|
|
34
|
+
|
|
35
|
+
From the OpenCard project root, run:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm run db:push
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
This uses Drizzle Kit to push the schema directly to Neon. It creates 3 tables:
|
|
42
|
+
|
|
43
|
+
| Table | Purpose |
|
|
44
|
+
| -------------- | -------------------------------------- |
|
|
45
|
+
| `wallets` | Agent wallet addresses, spend tracking |
|
|
46
|
+
| `cards` | Virtual cards linked to wallets |
|
|
47
|
+
| `transactions` | Payment & card operation history |
|
|
48
|
+
|
|
49
|
+
### Expected Output
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
[✓] Changes applied to database
|
|
53
|
+
- Created table "wallets"
|
|
54
|
+
- Created table "cards"
|
|
55
|
+
- Created table "transactions"
|
|
56
|
+
- Created indexes
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## 5. Verify
|
|
60
|
+
|
|
61
|
+
You can verify tables were created in the **Neon Console**:
|
|
62
|
+
|
|
63
|
+
1. Go to your project → **SQL Editor**
|
|
64
|
+
2. Run:
|
|
65
|
+
|
|
66
|
+
```sql
|
|
67
|
+
SELECT table_name FROM information_schema.tables WHERE table_schema = 'public';
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
You should see: `wallets`, `cards`, `transactions`
|
|
71
|
+
|
|
72
|
+
## Schema Overview
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
┌──────────────────────┐
|
|
76
|
+
│ wallets │
|
|
77
|
+
├──────────────────────┤
|
|
78
|
+
│ id (uuid) │──┐
|
|
79
|
+
│ address (text) │ │
|
|
80
|
+
│ first_seen_at (ts) │ │
|
|
81
|
+
│ total_spent_usd │ │
|
|
82
|
+
│ card_count │ │
|
|
83
|
+
└──────────────────────┘ │
|
|
84
|
+
│
|
|
85
|
+
┌──────────────────────┐ │ ┌──────────────────────┐
|
|
86
|
+
│ cards │ │ │ transactions │
|
|
87
|
+
├──────────────────────┤ │ ├──────────────────────┤
|
|
88
|
+
│ id (uuid) │──┼───→│ card_id (uuid) │
|
|
89
|
+
│ wallet_id (uuid)←──┘ │ │ wallet_address(text) │
|
|
90
|
+
│ wallet_address(text) │ │ │ type (text) │
|
|
91
|
+
│ kripi_card_id (text) │ │ │ amount_usd │
|
|
92
|
+
│ name_on_card (text) │ │ │ card_amount_usd │
|
|
93
|
+
│ email (text) │ │ │ fee_usd │
|
|
94
|
+
│ initial_amount_usd │ │ │ kripi_fee_usd │
|
|
95
|
+
│ current_balance_usd │ │ │ tx_hash (text) │
|
|
96
|
+
│ status (text) │ │ │ status (text) │
|
|
97
|
+
│ created_at (ts) │ │ │ error_message (text) │
|
|
98
|
+
│ updated_at (ts) │ │ │ created_at (ts) │
|
|
99
|
+
└──────────────────────┘ │ └──────────────────────┘
|
|
100
|
+
│
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Migrations (Optional)
|
|
104
|
+
|
|
105
|
+
If you prefer versioned migrations instead of `db:push`:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
# Generate migration files from schema changes
|
|
109
|
+
npm run db:generate
|
|
110
|
+
|
|
111
|
+
# Apply migrations
|
|
112
|
+
npm run db:migrate
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Migration files are saved to `drizzle/migrations/`.
|
|
116
|
+
|
|
117
|
+
## Neon Free Tier Limits
|
|
118
|
+
|
|
119
|
+
| Resource | Limit |
|
|
120
|
+
| --------- | ----------------- |
|
|
121
|
+
| Compute | 191.9 hours/month |
|
|
122
|
+
| Storage | 512 MB |
|
|
123
|
+
| Branches | 10 |
|
|
124
|
+
| Databases | Unlimited |
|
|
125
|
+
|
|
126
|
+
This is more than enough for an MVP. The compute auto-suspends after 5 min of inactivity and resumes on the next query (~500ms cold start).
|
|
127
|
+
|
|
128
|
+
## Troubleshooting
|
|
129
|
+
|
|
130
|
+
**"Connection refused"**
|
|
131
|
+
|
|
132
|
+
- Check that `?sslmode=require` is in your connection string
|
|
133
|
+
- Verify the endpoint ID matches your Neon project
|
|
134
|
+
|
|
135
|
+
**"Password authentication failed"**
|
|
136
|
+
|
|
137
|
+
- Reset your password in Neon Console → **Connection Details** → **Reset Password**
|
|
138
|
+
- Update `DATABASE_URL` in `.env`
|
|
139
|
+
|
|
140
|
+
**"Relation does not exist"**
|
|
141
|
+
|
|
142
|
+
- Run `npm run db:push` to create tables
|
|
143
|
+
- Check you're connecting to the correct database name
|