@gistplus/server 0.1.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/README.md +387 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +32 -0
- package/dist/middleware.d.ts +35 -0
- package/dist/middleware.js +161 -0
- package/dist/pricing.d.ts +68 -0
- package/dist/pricing.js +95 -0
- package/dist/provider.d.ts +91 -0
- package/dist/provider.js +165 -0
- package/dist/session-store.d.ts +49 -0
- package/dist/session-store.js +94 -0
- package/package.json +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
# @gistplus/server
|
|
2
|
+
|
|
3
|
+
[](https://x.com/gistplus)
|
|
4
|
+
|
|
5
|
+
Server middleware for API providers to monetize endpoints with Gist Plus protocol.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @gistplus/server
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## What is This?
|
|
14
|
+
|
|
15
|
+
This package enables **API providers** to:
|
|
16
|
+
- 💰 Monetize endpoints automatically
|
|
17
|
+
- 🤝 Negotiate pricing with agents
|
|
18
|
+
- 📝 Generate cryptographic receipts
|
|
19
|
+
- ⚖️ Enforce SLA guarantees
|
|
20
|
+
- 💵 Accept SOL/USDC/USDT payments
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import express from 'express';
|
|
26
|
+
import { Connection, Keypair } from '@solana/web3.js';
|
|
27
|
+
import { gistMiddleware } from '@gistplus/server';
|
|
28
|
+
|
|
29
|
+
const app = express();
|
|
30
|
+
const connection = new Connection('https://api.devnet.solana.com');
|
|
31
|
+
const wallet = Keypair.generate(); // Your provider wallet
|
|
32
|
+
|
|
33
|
+
// Add Gist Plus middleware
|
|
34
|
+
app.use('/api/*', gistMiddleware({
|
|
35
|
+
connection,
|
|
36
|
+
wallet,
|
|
37
|
+
endpoint: 'https://your-api.com',
|
|
38
|
+
pricing: {
|
|
39
|
+
basePrice: 0.01,
|
|
40
|
+
token: 'USDC'
|
|
41
|
+
},
|
|
42
|
+
sla: {
|
|
43
|
+
maxLatencyMs: 2000,
|
|
44
|
+
minUptimePercent: 99.5
|
|
45
|
+
}
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
// Your API endpoint (now monetized!)
|
|
49
|
+
app.post('/api/inference', async (req, res) => {
|
|
50
|
+
// req.gistSession contains session info
|
|
51
|
+
const result = await runInference(req.body);
|
|
52
|
+
|
|
53
|
+
// Automatic receipt generation
|
|
54
|
+
return res.gistReceipt?.(result);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
app.listen(3000);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
That's it! Your API now:
|
|
61
|
+
- ✅ Responds with 402 Payment Required
|
|
62
|
+
- ✅ Negotiates with agents automatically
|
|
63
|
+
- ✅ Validates sessions
|
|
64
|
+
- ✅ Generates signed receipts
|
|
65
|
+
- ✅ Tracks balances
|
|
66
|
+
|
|
67
|
+
## Core Features
|
|
68
|
+
|
|
69
|
+
### Automatic 402 Responses
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
// When agent hits your endpoint without payment:
|
|
73
|
+
GET /api/inference
|
|
74
|
+
↓
|
|
75
|
+
402 Payment Required
|
|
76
|
+
X-Gist-Offer: {signed offer with your pricing}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Dynamic Pricing
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { LoadBasedPricingStrategy } from '@gistplus/server';
|
|
83
|
+
|
|
84
|
+
const pricing = new LoadBasedPricingStrategy(
|
|
85
|
+
0.01, // base price
|
|
86
|
+
'USDC',
|
|
87
|
+
() => getCurrentLoad() // returns 0-1
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
app.use(gistMiddleware({
|
|
91
|
+
// ...
|
|
92
|
+
pricing // Price increases with load
|
|
93
|
+
}));
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Multiple Pricing Strategies
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
// Static pricing
|
|
100
|
+
pricing: { basePrice: 0.01, token: 'USDC' }
|
|
101
|
+
|
|
102
|
+
// Load-based
|
|
103
|
+
pricing: new LoadBasedPricingStrategy(0.01, 'USDC', getLoad)
|
|
104
|
+
|
|
105
|
+
// Time-based (peak hours)
|
|
106
|
+
pricing: new TimeBasedPricingStrategy(
|
|
107
|
+
0.02, // peak price
|
|
108
|
+
0.01, // off-peak price
|
|
109
|
+
{ start: 9, end: 17 }, // peak hours
|
|
110
|
+
'USDC'
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
// SLA-based (stricter SLA = higher price)
|
|
114
|
+
pricing: new SLABasedPricingStrategy(0.01, 'USDC')
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Receipt Generation
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
app.post('/api/inference', async (req, res) => {
|
|
121
|
+
const result = await processRequest(req.body);
|
|
122
|
+
|
|
123
|
+
// Middleware automatically:
|
|
124
|
+
// - Generates receipt
|
|
125
|
+
// - Signs with your key
|
|
126
|
+
// - Verifies SLA
|
|
127
|
+
// - Updates session balance
|
|
128
|
+
// - Sends X-Gist-Receipt header
|
|
129
|
+
|
|
130
|
+
return res.gistReceipt?.(result);
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Session Management
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
app.post('/api/inference', async (req, res) => {
|
|
138
|
+
// Session info automatically attached
|
|
139
|
+
const session = req.gistSession;
|
|
140
|
+
|
|
141
|
+
console.log(session.remainingBalance);
|
|
142
|
+
console.log(session.requestCount);
|
|
143
|
+
console.log(session.expiresAt);
|
|
144
|
+
|
|
145
|
+
// Process request...
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## API Reference
|
|
150
|
+
|
|
151
|
+
### gistMiddleware(config)
|
|
152
|
+
|
|
153
|
+
Creates Express middleware for Gist Plus protocol.
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
app.use(gistMiddleware({
|
|
157
|
+
connection: Connection, // Solana connection
|
|
158
|
+
wallet: Keypair, // Provider wallet
|
|
159
|
+
endpoint: string, // Your API URL
|
|
160
|
+
pricing: PricingStrategy | { // Pricing config
|
|
161
|
+
basePrice: number,
|
|
162
|
+
token: 'SOL' | 'USDC' | 'USDT' | 'BONK'
|
|
163
|
+
},
|
|
164
|
+
sla: { // SLA guarantees
|
|
165
|
+
maxLatencyMs?: number,
|
|
166
|
+
minUptimePercent?: number,
|
|
167
|
+
maxErrorRatePercent?: number
|
|
168
|
+
},
|
|
169
|
+
sessionDurationMs?: number, // Default: 10 minutes
|
|
170
|
+
offerExpirationMs?: number // Default: 1 minute
|
|
171
|
+
}));
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Request Extensions
|
|
175
|
+
|
|
176
|
+
The middleware adds these to Express Request:
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
interface Request {
|
|
180
|
+
gistSession?: Session; // Current session
|
|
181
|
+
x402Intent?: Intent; // Agent's intent
|
|
182
|
+
x402RequestStartTime?: number; // For latency calc
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Response Extensions
|
|
187
|
+
|
|
188
|
+
The middleware adds these to Express Response:
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
interface Response {
|
|
192
|
+
gistReceipt?: (data: any) => Response; // Generate receipt
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Usage Patterns
|
|
197
|
+
|
|
198
|
+
### Basic Protected Endpoint
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
app.use('/api/*', gistMiddleware(config));
|
|
202
|
+
|
|
203
|
+
app.post('/api/weather', async (req, res) => {
|
|
204
|
+
const data = await getWeather(req.body.city);
|
|
205
|
+
return res.gistReceipt?.(data);
|
|
206
|
+
});
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Custom Validation
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
app.post('/api/premium', async (req, res) => {
|
|
213
|
+
const session = req.gistSession;
|
|
214
|
+
|
|
215
|
+
// Check if enough balance for premium feature
|
|
216
|
+
if (session.remainingBalance < 0.1) {
|
|
217
|
+
return res.status(402).json({
|
|
218
|
+
error: 'Insufficient balance for premium feature'
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const result = await processPremium(req.body);
|
|
223
|
+
return res.gistReceipt?.(result);
|
|
224
|
+
});
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Multiple Endpoints, Different Pricing
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
// Cheap endpoint
|
|
231
|
+
app.use('/api/basic', gistMiddleware({
|
|
232
|
+
...config,
|
|
233
|
+
pricing: { basePrice: 0.001, token: 'USDC' }
|
|
234
|
+
}));
|
|
235
|
+
|
|
236
|
+
// Expensive endpoint
|
|
237
|
+
app.use('/api/premium', gistMiddleware({
|
|
238
|
+
...config,
|
|
239
|
+
pricing: { basePrice: 0.1, token: 'USDC' }
|
|
240
|
+
}));
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### With Error Handling
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
app.post('/api/inference', async (req, res) => {
|
|
247
|
+
try {
|
|
248
|
+
const result = await runInference(req.body);
|
|
249
|
+
return res.gistReceipt?.(result);
|
|
250
|
+
} catch (error) {
|
|
251
|
+
// Errors don't charge the session
|
|
252
|
+
res.status(500).json({ error: error.message });
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Pricing Strategies
|
|
258
|
+
|
|
259
|
+
### Static Pricing
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
pricing: {
|
|
263
|
+
basePrice: 0.01,
|
|
264
|
+
token: 'USDC'
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Load-Based Pricing
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
import { LoadBasedPricingStrategy } from '@gistplus/server';
|
|
272
|
+
|
|
273
|
+
const pricing = new LoadBasedPricingStrategy(
|
|
274
|
+
0.01, // base price
|
|
275
|
+
'USDC',
|
|
276
|
+
() => {
|
|
277
|
+
// Return load factor 0-1
|
|
278
|
+
const currentLoad = getCurrentLoad();
|
|
279
|
+
return currentLoad / maxLoad;
|
|
280
|
+
}
|
|
281
|
+
);
|
|
282
|
+
// Load 0% → 0.01 USDC
|
|
283
|
+
// Load 50% → 0.015 USDC
|
|
284
|
+
// Load 100% → 0.02 USDC
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Time-Based Pricing
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
import { TimeBasedPricingStrategy } from '@gistplus/server';
|
|
291
|
+
|
|
292
|
+
const pricing = new TimeBasedPricingStrategy(
|
|
293
|
+
0.02, // peak price
|
|
294
|
+
0.01, // off-peak price
|
|
295
|
+
{ start: 9, end: 17 }, // 9 AM - 5 PM
|
|
296
|
+
'USDC'
|
|
297
|
+
);
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### SLA-Based Pricing
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
import { SLABasedPricingStrategy } from '@gistplus/server';
|
|
304
|
+
|
|
305
|
+
const pricing = new SLABasedPricingStrategy(0.01, 'USDC');
|
|
306
|
+
// <1s latency SLA → 1.5x price
|
|
307
|
+
// <2s latency SLA → 1.25x price
|
|
308
|
+
// >99.5% uptime → 1.3x price
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Custom Pricing
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
class MyCustomPricing implements PricingStrategy {
|
|
315
|
+
getPrice(intent: Intent): number {
|
|
316
|
+
// Your custom logic
|
|
317
|
+
if (intent.capability === 'premium') return 0.1;
|
|
318
|
+
if (isWeekend()) return 0.005;
|
|
319
|
+
return 0.01;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const pricing = new MyCustomPricing();
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## Examples
|
|
327
|
+
|
|
328
|
+
### AI Inference API
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
app.use('/api/inference', gistMiddleware({
|
|
332
|
+
connection,
|
|
333
|
+
wallet,
|
|
334
|
+
endpoint: 'https://ai.example.com',
|
|
335
|
+
pricing: { basePrice: 0.01, token: 'USDC' },
|
|
336
|
+
sla: { maxLatencyMs: 3000 }
|
|
337
|
+
}));
|
|
338
|
+
|
|
339
|
+
app.post('/api/inference', async (req, res) => {
|
|
340
|
+
const result = await model.generate(req.body.prompt);
|
|
341
|
+
return res.gistReceipt?.(result);
|
|
342
|
+
});
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### Image Generation API
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
app.use('/api/image', gistMiddleware({
|
|
349
|
+
connection,
|
|
350
|
+
wallet,
|
|
351
|
+
endpoint: 'https://image.example.com',
|
|
352
|
+
pricing: { basePrice: 0.05, token: 'USDC' },
|
|
353
|
+
sla: { maxLatencyMs: 10000 }
|
|
354
|
+
}));
|
|
355
|
+
|
|
356
|
+
app.post('/api/image', async (req, res) => {
|
|
357
|
+
const imageUrl = await generateImage(req.body);
|
|
358
|
+
return res.gistReceipt?.({ imageUrl });
|
|
359
|
+
});
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### Data API
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
app.use('/api/data', gistMiddleware({
|
|
366
|
+
connection,
|
|
367
|
+
wallet,
|
|
368
|
+
endpoint: 'https://data.example.com',
|
|
369
|
+
pricing: { basePrice: 0.001, token: 'USDC' },
|
|
370
|
+
sla: { maxLatencyMs: 1000 }
|
|
371
|
+
}));
|
|
372
|
+
|
|
373
|
+
app.post('/api/data', async (req, res) => {
|
|
374
|
+
const data = await fetchData(req.body.query);
|
|
375
|
+
return res.gistReceipt?.(data);
|
|
376
|
+
});
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
## Related Packages
|
|
380
|
+
|
|
381
|
+
- **[@gistplus/core](https://www.npmjs.com/package/@gistplus/core)** - Core protocol (auto-installed)
|
|
382
|
+
- **[@gistplus/client](https://www.npmjs.com/package/@gistplus/client)** - For AI agents
|
|
383
|
+
|
|
384
|
+
## License
|
|
385
|
+
|
|
386
|
+
Apache 2.0
|
|
387
|
+
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @gistplus/server
|
|
3
|
+
*
|
|
4
|
+
* Server-side middleware for API providers to implement Gist Plus protocol.
|
|
5
|
+
* Handles automatic Intent responses, Offer generation, Session management,
|
|
6
|
+
* and Receipt creation.
|
|
7
|
+
*/
|
|
8
|
+
export * from './middleware';
|
|
9
|
+
export * from './provider';
|
|
10
|
+
export * from './session-store';
|
|
11
|
+
export * from './pricing';
|
|
12
|
+
export { Intent, Offer, Session, Receipt, SLA, SessionState, SupportedToken, SUPPORTED_TOKENS, } from '@gistplus/core';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @gistplus/server
|
|
4
|
+
*
|
|
5
|
+
* Server-side middleware for API providers to implement Gist Plus protocol.
|
|
6
|
+
* Handles automatic Intent responses, Offer generation, Session management,
|
|
7
|
+
* and Receipt creation.
|
|
8
|
+
*/
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
21
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
22
|
+
};
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.SUPPORTED_TOKENS = exports.SessionState = void 0;
|
|
25
|
+
__exportStar(require("./middleware"), exports);
|
|
26
|
+
__exportStar(require("./provider"), exports);
|
|
27
|
+
__exportStar(require("./session-store"), exports);
|
|
28
|
+
__exportStar(require("./pricing"), exports);
|
|
29
|
+
// Re-export core types
|
|
30
|
+
var core_1 = require("@gistplus/core");
|
|
31
|
+
Object.defineProperty(exports, "SessionState", { enumerable: true, get: function () { return core_1.SessionState; } });
|
|
32
|
+
Object.defineProperty(exports, "SUPPORTED_TOKENS", { enumerable: true, get: function () { return core_1.SUPPORTED_TOKENS; } });
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Express middleware for Gist Plus protocol
|
|
3
|
+
*/
|
|
4
|
+
import { Request, Response, NextFunction } from 'express';
|
|
5
|
+
import { Intent, Session } from '@gistplus/core';
|
|
6
|
+
import { GistProvider, GistProviderConfig } from './provider';
|
|
7
|
+
declare global {
|
|
8
|
+
namespace Express {
|
|
9
|
+
interface Request {
|
|
10
|
+
gistSession?: Session;
|
|
11
|
+
gistIntent?: Intent;
|
|
12
|
+
gistRequestStartTime?: number;
|
|
13
|
+
}
|
|
14
|
+
interface Response {
|
|
15
|
+
gistReceipt?: (data: any) => Response;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Create Gist Plus middleware for Express
|
|
21
|
+
*
|
|
22
|
+
* This middleware handles:
|
|
23
|
+
* 1. Detecting requests without sessions (402 responses with Offers)
|
|
24
|
+
* 2. Validating session headers
|
|
25
|
+
* 3. Attaching session info to request
|
|
26
|
+
* 4. Generating and attaching Receipts to responses
|
|
27
|
+
*
|
|
28
|
+
* @param config - Provider configuration
|
|
29
|
+
* @returns Express middleware
|
|
30
|
+
*/
|
|
31
|
+
export declare function gistMiddleware(config: GistProviderConfig): (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Middleware specifically for session creation endpoint
|
|
34
|
+
*/
|
|
35
|
+
export declare function sessionCreationHandler(provider: GistProvider): (req: Request, res: Response) => Promise<void>;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Express middleware for Gist Plus protocol
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.gistMiddleware = gistMiddleware;
|
|
7
|
+
exports.sessionCreationHandler = sessionCreationHandler;
|
|
8
|
+
const core_1 = require("@gistplus/core");
|
|
9
|
+
const provider_1 = require("./provider");
|
|
10
|
+
/**
|
|
11
|
+
* Create Gist Plus middleware for Express
|
|
12
|
+
*
|
|
13
|
+
* This middleware handles:
|
|
14
|
+
* 1. Detecting requests without sessions (402 responses with Offers)
|
|
15
|
+
* 2. Validating session headers
|
|
16
|
+
* 3. Attaching session info to request
|
|
17
|
+
* 4. Generating and attaching Receipts to responses
|
|
18
|
+
*
|
|
19
|
+
* @param config - Provider configuration
|
|
20
|
+
* @returns Express middleware
|
|
21
|
+
*/
|
|
22
|
+
function gistMiddleware(config) {
|
|
23
|
+
const provider = new provider_1.GistProvider(config);
|
|
24
|
+
return async (req, res, next) => {
|
|
25
|
+
req.gistRequestStartTime = Date.now();
|
|
26
|
+
try {
|
|
27
|
+
// Check for session header
|
|
28
|
+
const sessionId = req.headers[core_1.HEADERS.SESSION_ID.toLowerCase()];
|
|
29
|
+
if (sessionId) {
|
|
30
|
+
// Request has session - validate and attach
|
|
31
|
+
await handleSessionRequest(req, res, next, provider, sessionId);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
// No session - check for Intent and respond with Offer
|
|
35
|
+
await handleIntentRequest(req, res, next, provider);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
console.error('Gist Plus middleware error:', error);
|
|
40
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Handle request with Intent (no session yet)
|
|
46
|
+
*/
|
|
47
|
+
async function handleIntentRequest(req, res, next, provider) {
|
|
48
|
+
// Check for Intent header
|
|
49
|
+
const intentHeader = req.headers[core_1.HEADERS.INTENT.toLowerCase()];
|
|
50
|
+
if (!intentHeader) {
|
|
51
|
+
// No Intent, no Session -> 402 Quote Required
|
|
52
|
+
res.status(core_1.HTTP_STATUS.QUOTE_REQUIRED).json({
|
|
53
|
+
error: 'Payment Required',
|
|
54
|
+
message: 'This endpoint requires Gist Plus payment. Send an Intent to negotiate.',
|
|
55
|
+
protocol: 'Gist Plus',
|
|
56
|
+
});
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
// Parse and validate Intent
|
|
61
|
+
const intent = JSON.parse(intentHeader);
|
|
62
|
+
(0, core_1.validateIntent)(intent);
|
|
63
|
+
// Create Offer
|
|
64
|
+
const offer = await provider.createOfferForIntent(intent);
|
|
65
|
+
// Respond with 402 and Offer in header
|
|
66
|
+
res.setHeader(core_1.HEADERS.OFFER, JSON.stringify(offer));
|
|
67
|
+
res.status(core_1.HTTP_STATUS.QUOTE_REQUIRED).json({
|
|
68
|
+
message: 'Offer provided',
|
|
69
|
+
offerId: offer.offerId,
|
|
70
|
+
pricePerRequest: offer.pricePerRequest,
|
|
71
|
+
token: offer.token,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
res.status(core_1.HTTP_STATUS.INVALID_OFFER).json({
|
|
76
|
+
error: 'Invalid Intent',
|
|
77
|
+
message: String(error),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Handle request with existing Session
|
|
83
|
+
*/
|
|
84
|
+
async function handleSessionRequest(req, res, next, provider, sessionId) {
|
|
85
|
+
try {
|
|
86
|
+
// Get session
|
|
87
|
+
const session = provider.getSession(sessionId);
|
|
88
|
+
if (!session) {
|
|
89
|
+
res.status(core_1.HTTP_STATUS.SESSION_EXPIRED).json({
|
|
90
|
+
error: 'Session not found',
|
|
91
|
+
message: `Session ${sessionId} does not exist or has expired`,
|
|
92
|
+
});
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
// Validate session is active
|
|
96
|
+
// (This would check expiration, balance, etc.)
|
|
97
|
+
// Attach session to request
|
|
98
|
+
req.gistSession = session;
|
|
99
|
+
// Create Receipt helper function
|
|
100
|
+
res.gistReceipt = (data) => {
|
|
101
|
+
// This will be called by the route handler
|
|
102
|
+
provider
|
|
103
|
+
.createReceiptForRequest(sessionId, req.body, data, req.gistRequestStartTime)
|
|
104
|
+
.then((receipt) => {
|
|
105
|
+
// Attach receipt to response header
|
|
106
|
+
res.setHeader(core_1.HEADERS.RECEIPT, JSON.stringify(receipt));
|
|
107
|
+
res.setHeader(core_1.HEADERS.SLA_STATUS, receipt.slaVerification.met ? 'met' : 'breached');
|
|
108
|
+
res.json(data);
|
|
109
|
+
})
|
|
110
|
+
.catch((error) => {
|
|
111
|
+
res.status(500).json({
|
|
112
|
+
error: 'Failed to generate receipt',
|
|
113
|
+
message: String(error),
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
return res;
|
|
117
|
+
};
|
|
118
|
+
// Continue to route handler
|
|
119
|
+
next();
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
res.status(500).json({
|
|
123
|
+
error: 'Session error',
|
|
124
|
+
message: String(error),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Middleware specifically for session creation endpoint
|
|
130
|
+
*/
|
|
131
|
+
function sessionCreationHandler(provider) {
|
|
132
|
+
return async (req, res) => {
|
|
133
|
+
try {
|
|
134
|
+
const { offerId, depositAmount, txSignature } = req.body;
|
|
135
|
+
if (!offerId || !depositAmount || !txSignature) {
|
|
136
|
+
res.status(400).json({
|
|
137
|
+
error: 'Missing required fields',
|
|
138
|
+
required: ['offerId', 'depositAmount', 'txSignature'],
|
|
139
|
+
});
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
// For MVP, we'll need to reconstruct or store the offer
|
|
143
|
+
// In production, you'd store offers temporarily
|
|
144
|
+
// Create session (this includes payment verification)
|
|
145
|
+
const agentPubkey = req.body.agentPubkey; // Should be derived from tx
|
|
146
|
+
// This is a simplified version - in production you'd:
|
|
147
|
+
// 1. Retrieve the stored offer by offerId
|
|
148
|
+
// 2. Verify the offer hasn't expired
|
|
149
|
+
// 3. Create the session
|
|
150
|
+
res.status(core_1.HTTP_STATUS.SESSION_STARTED).json({
|
|
151
|
+
message: 'Session creation endpoint - implement offer storage',
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
res.status(500).json({
|
|
156
|
+
error: 'Failed to create session',
|
|
157
|
+
message: String(error),
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pricing Strategies for Gist Plus providers
|
|
3
|
+
*/
|
|
4
|
+
import { Intent, SupportedToken } from '@gistplus/core';
|
|
5
|
+
/**
|
|
6
|
+
* PricingStrategy interface
|
|
7
|
+
*
|
|
8
|
+
* Allows providers to implement dynamic pricing based on:
|
|
9
|
+
* - Intent requirements
|
|
10
|
+
* - Current load
|
|
11
|
+
* - Token type
|
|
12
|
+
* - Time of day
|
|
13
|
+
* - Agent reputation
|
|
14
|
+
*/
|
|
15
|
+
export interface PricingStrategy {
|
|
16
|
+
getPrice(intent: Intent): Promise<number> | number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Static pricing - same price for all requests
|
|
20
|
+
*/
|
|
21
|
+
export declare class StaticPricingStrategy implements PricingStrategy {
|
|
22
|
+
private basePrice;
|
|
23
|
+
private token;
|
|
24
|
+
constructor(basePrice: number, token: SupportedToken);
|
|
25
|
+
getPrice(intent: Intent): number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Load-based pricing - price increases with load
|
|
29
|
+
*/
|
|
30
|
+
export declare class LoadBasedPricingStrategy implements PricingStrategy {
|
|
31
|
+
private basePrice;
|
|
32
|
+
private token;
|
|
33
|
+
private getLoadFactor;
|
|
34
|
+
constructor(basePrice: number, token: SupportedToken, getLoadFactor: () => number);
|
|
35
|
+
getPrice(intent: Intent): number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Time-based pricing - different prices for different times
|
|
39
|
+
*/
|
|
40
|
+
export declare class TimeBasedPricingStrategy implements PricingStrategy {
|
|
41
|
+
private peakPrice;
|
|
42
|
+
private offPeakPrice;
|
|
43
|
+
private peakHours;
|
|
44
|
+
private token;
|
|
45
|
+
constructor(peakPrice: number, offPeakPrice: number, peakHours: {
|
|
46
|
+
start: number;
|
|
47
|
+
end: number;
|
|
48
|
+
}, // 0-23
|
|
49
|
+
token: SupportedToken);
|
|
50
|
+
getPrice(intent: Intent): number;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* SLA-based pricing - higher price for stricter SLAs
|
|
54
|
+
*/
|
|
55
|
+
export declare class SLABasedPricingStrategy implements PricingStrategy {
|
|
56
|
+
private basePrice;
|
|
57
|
+
private token;
|
|
58
|
+
constructor(basePrice: number, token: SupportedToken);
|
|
59
|
+
getPrice(intent: Intent): number;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Custom pricing strategy - combine multiple strategies
|
|
63
|
+
*/
|
|
64
|
+
export declare class CompositePricingStrategy implements PricingStrategy {
|
|
65
|
+
private strategies;
|
|
66
|
+
constructor(strategies: PricingStrategy[]);
|
|
67
|
+
getPrice(intent: Intent): Promise<number>;
|
|
68
|
+
}
|
package/dist/pricing.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Pricing Strategies for Gist Plus providers
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.CompositePricingStrategy = exports.SLABasedPricingStrategy = exports.TimeBasedPricingStrategy = exports.LoadBasedPricingStrategy = exports.StaticPricingStrategy = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* Static pricing - same price for all requests
|
|
9
|
+
*/
|
|
10
|
+
class StaticPricingStrategy {
|
|
11
|
+
constructor(basePrice, token) {
|
|
12
|
+
this.basePrice = basePrice;
|
|
13
|
+
this.token = token;
|
|
14
|
+
}
|
|
15
|
+
getPrice(intent) {
|
|
16
|
+
return this.basePrice;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
exports.StaticPricingStrategy = StaticPricingStrategy;
|
|
20
|
+
/**
|
|
21
|
+
* Load-based pricing - price increases with load
|
|
22
|
+
*/
|
|
23
|
+
class LoadBasedPricingStrategy {
|
|
24
|
+
constructor(basePrice, token, getLoadFactor // Returns 0-1, multiplies price
|
|
25
|
+
) {
|
|
26
|
+
this.basePrice = basePrice;
|
|
27
|
+
this.token = token;
|
|
28
|
+
this.getLoadFactor = getLoadFactor;
|
|
29
|
+
}
|
|
30
|
+
getPrice(intent) {
|
|
31
|
+
const loadFactor = this.getLoadFactor();
|
|
32
|
+
const multiplier = 1 + loadFactor;
|
|
33
|
+
return this.basePrice * multiplier;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
exports.LoadBasedPricingStrategy = LoadBasedPricingStrategy;
|
|
37
|
+
/**
|
|
38
|
+
* Time-based pricing - different prices for different times
|
|
39
|
+
*/
|
|
40
|
+
class TimeBasedPricingStrategy {
|
|
41
|
+
constructor(peakPrice, offPeakPrice, peakHours, // 0-23
|
|
42
|
+
token) {
|
|
43
|
+
this.peakPrice = peakPrice;
|
|
44
|
+
this.offPeakPrice = offPeakPrice;
|
|
45
|
+
this.peakHours = peakHours;
|
|
46
|
+
this.token = token;
|
|
47
|
+
}
|
|
48
|
+
getPrice(intent) {
|
|
49
|
+
const hour = new Date().getHours();
|
|
50
|
+
const isPeak = hour >= this.peakHours.start && hour <= this.peakHours.end;
|
|
51
|
+
return isPeak ? this.peakPrice : this.offPeakPrice;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
exports.TimeBasedPricingStrategy = TimeBasedPricingStrategy;
|
|
55
|
+
/**
|
|
56
|
+
* SLA-based pricing - higher price for stricter SLAs
|
|
57
|
+
*/
|
|
58
|
+
class SLABasedPricingStrategy {
|
|
59
|
+
constructor(basePrice, token) {
|
|
60
|
+
this.basePrice = basePrice;
|
|
61
|
+
this.token = token;
|
|
62
|
+
}
|
|
63
|
+
getPrice(intent) {
|
|
64
|
+
let price = this.basePrice;
|
|
65
|
+
// Charge more for stricter latency requirements
|
|
66
|
+
if (intent.sla?.maxLatencyMs) {
|
|
67
|
+
if (intent.sla.maxLatencyMs < 1000) {
|
|
68
|
+
price *= 1.5; // 50% premium for sub-second latency
|
|
69
|
+
}
|
|
70
|
+
else if (intent.sla.maxLatencyMs < 2000) {
|
|
71
|
+
price *= 1.25; // 25% premium for sub-2s latency
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Charge more for higher uptime guarantees
|
|
75
|
+
if (intent.sla?.minUptimePercent && intent.sla.minUptimePercent > 99.5) {
|
|
76
|
+
price *= 1.3; // 30% premium for 99.5%+ uptime
|
|
77
|
+
}
|
|
78
|
+
return price;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
exports.SLABasedPricingStrategy = SLABasedPricingStrategy;
|
|
82
|
+
/**
|
|
83
|
+
* Custom pricing strategy - combine multiple strategies
|
|
84
|
+
*/
|
|
85
|
+
class CompositePricingStrategy {
|
|
86
|
+
constructor(strategies) {
|
|
87
|
+
this.strategies = strategies;
|
|
88
|
+
}
|
|
89
|
+
async getPrice(intent) {
|
|
90
|
+
const prices = await Promise.all(this.strategies.map(strategy => strategy.getPrice(intent)));
|
|
91
|
+
// Return average, max, or custom combination
|
|
92
|
+
return Math.max(...prices);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
exports.CompositePricingStrategy = CompositePricingStrategy;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GistProvider - Main provider interface for Gist Plus server
|
|
3
|
+
*/
|
|
4
|
+
import { Connection, Keypair } from '@solana/web3.js';
|
|
5
|
+
import { Intent, Offer, Session, Receipt, SLA, SupportedToken } from '@gistplus/core';
|
|
6
|
+
import { PricingStrategy } from './pricing';
|
|
7
|
+
export interface GistProviderConfig {
|
|
8
|
+
/** Solana connection */
|
|
9
|
+
connection: Connection;
|
|
10
|
+
/** Provider's keypair for signing Offers and Receipts */
|
|
11
|
+
wallet: Keypair;
|
|
12
|
+
/** Provider's API endpoint base URL */
|
|
13
|
+
endpoint: string;
|
|
14
|
+
/** Pricing strategy */
|
|
15
|
+
pricing: PricingStrategy | {
|
|
16
|
+
basePrice: number;
|
|
17
|
+
token: SupportedToken;
|
|
18
|
+
};
|
|
19
|
+
/** SLA guarantees */
|
|
20
|
+
sla: SLA;
|
|
21
|
+
/** Session duration in milliseconds */
|
|
22
|
+
sessionDurationMs?: number;
|
|
23
|
+
/** Offer expiration in milliseconds */
|
|
24
|
+
offerExpirationMs?: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* GistProvider - Server-side provider implementation
|
|
28
|
+
*
|
|
29
|
+
* Enables API providers to:
|
|
30
|
+
* 1. Respond to Intents with Offers
|
|
31
|
+
* 2. Create and manage Sessions
|
|
32
|
+
* 3. Generate signed Receipts
|
|
33
|
+
* 4. Verify payments on Solana
|
|
34
|
+
*/
|
|
35
|
+
export declare class GistProvider {
|
|
36
|
+
private connection;
|
|
37
|
+
private wallet;
|
|
38
|
+
private endpoint;
|
|
39
|
+
private pricing;
|
|
40
|
+
private sla;
|
|
41
|
+
private sessionDurationMs;
|
|
42
|
+
private offerExpirationMs;
|
|
43
|
+
private sessionStore;
|
|
44
|
+
constructor(config: GistProviderConfig);
|
|
45
|
+
/**
|
|
46
|
+
* Create an Offer in response to an Intent
|
|
47
|
+
*
|
|
48
|
+
* @param intent - Agent's Intent
|
|
49
|
+
* @returns Signed Offer
|
|
50
|
+
*/
|
|
51
|
+
createOfferForIntent(intent: Intent): Promise<Offer>;
|
|
52
|
+
/**
|
|
53
|
+
* Create a Session from an accepted Offer
|
|
54
|
+
*
|
|
55
|
+
* @param offer - Accepted Offer
|
|
56
|
+
* @param agentPubkey - Agent's public key
|
|
57
|
+
* @param depositAmount - Amount deposited
|
|
58
|
+
* @param txSignature - Solana transaction signature for payment
|
|
59
|
+
* @returns Active Session
|
|
60
|
+
*/
|
|
61
|
+
createSessionFromOffer(offer: Offer, agentPubkey: string, depositAmount: number, txSignature: string): Promise<Session>;
|
|
62
|
+
/**
|
|
63
|
+
* Get a session by ID
|
|
64
|
+
*/
|
|
65
|
+
getSession(sessionId: string): Session | undefined;
|
|
66
|
+
/**
|
|
67
|
+
* Create a Receipt for a completed request
|
|
68
|
+
*
|
|
69
|
+
* @param sessionId - Session ID
|
|
70
|
+
* @param inputData - Request input data
|
|
71
|
+
* @param outputData - Request output data
|
|
72
|
+
* @param requestStartedAt - Request start timestamp
|
|
73
|
+
* @returns Signed Receipt
|
|
74
|
+
*/
|
|
75
|
+
createReceiptForRequest(sessionId: string, inputData: any, outputData: any, requestStartedAt: number): Promise<Receipt>;
|
|
76
|
+
/**
|
|
77
|
+
* Close a session and process refund
|
|
78
|
+
*/
|
|
79
|
+
closeSession(sessionId: string): Promise<{
|
|
80
|
+
refundAmount: number;
|
|
81
|
+
txSignature?: string;
|
|
82
|
+
}>;
|
|
83
|
+
/**
|
|
84
|
+
* Verify payment on Solana blockchain
|
|
85
|
+
*/
|
|
86
|
+
private verifyPayment;
|
|
87
|
+
/**
|
|
88
|
+
* Get provider's public key
|
|
89
|
+
*/
|
|
90
|
+
get publicKey(): import("@solana/web3.js").PublicKey;
|
|
91
|
+
}
|
package/dist/provider.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* GistProvider - Main provider interface for Gist Plus server
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.GistProvider = void 0;
|
|
7
|
+
const core_1 = require("@gistplus/core");
|
|
8
|
+
const session_store_1 = require("./session-store");
|
|
9
|
+
const pricing_1 = require("./pricing");
|
|
10
|
+
/**
|
|
11
|
+
* GistProvider - Server-side provider implementation
|
|
12
|
+
*
|
|
13
|
+
* Enables API providers to:
|
|
14
|
+
* 1. Respond to Intents with Offers
|
|
15
|
+
* 2. Create and manage Sessions
|
|
16
|
+
* 3. Generate signed Receipts
|
|
17
|
+
* 4. Verify payments on Solana
|
|
18
|
+
*/
|
|
19
|
+
class GistProvider {
|
|
20
|
+
constructor(config) {
|
|
21
|
+
this.connection = config.connection;
|
|
22
|
+
this.wallet = config.wallet;
|
|
23
|
+
this.endpoint = config.endpoint;
|
|
24
|
+
this.sla = config.sla;
|
|
25
|
+
this.sessionDurationMs = config.sessionDurationMs || 600000; // 10 minutes default
|
|
26
|
+
this.offerExpirationMs = config.offerExpirationMs || 60000; // 1 minute default
|
|
27
|
+
// Initialize pricing strategy
|
|
28
|
+
if ('getPrice' in config.pricing) {
|
|
29
|
+
this.pricing = config.pricing;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
this.pricing = new pricing_1.StaticPricingStrategy(config.pricing.basePrice, config.pricing.token);
|
|
33
|
+
}
|
|
34
|
+
// Initialize session store
|
|
35
|
+
this.sessionStore = new session_store_1.SessionStore(this.connection, this.wallet);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Create an Offer in response to an Intent
|
|
39
|
+
*
|
|
40
|
+
* @param intent - Agent's Intent
|
|
41
|
+
* @returns Signed Offer
|
|
42
|
+
*/
|
|
43
|
+
async createOfferForIntent(intent) {
|
|
44
|
+
// Validate intent
|
|
45
|
+
(0, core_1.validateIntent)(intent);
|
|
46
|
+
// Get dynamic price based on intent
|
|
47
|
+
const price = await this.pricing.getPrice(intent);
|
|
48
|
+
// Create offer
|
|
49
|
+
const offer = (0, core_1.createOffer)({
|
|
50
|
+
intent,
|
|
51
|
+
providerPubkey: this.wallet.publicKey,
|
|
52
|
+
pricePerRequest: price,
|
|
53
|
+
sla: this.sla,
|
|
54
|
+
sessionDurationMs: intent.sessionDurationMs || this.sessionDurationMs,
|
|
55
|
+
endpoint: this.endpoint,
|
|
56
|
+
expirationMs: this.offerExpirationMs,
|
|
57
|
+
}, this.wallet);
|
|
58
|
+
return offer;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Create a Session from an accepted Offer
|
|
62
|
+
*
|
|
63
|
+
* @param offer - Accepted Offer
|
|
64
|
+
* @param agentPubkey - Agent's public key
|
|
65
|
+
* @param depositAmount - Amount deposited
|
|
66
|
+
* @param txSignature - Solana transaction signature for payment
|
|
67
|
+
* @returns Active Session
|
|
68
|
+
*/
|
|
69
|
+
async createSessionFromOffer(offer, agentPubkey, depositAmount, txSignature) {
|
|
70
|
+
// Verify payment on-chain
|
|
71
|
+
await this.verifyPayment(txSignature, agentPubkey, depositAmount);
|
|
72
|
+
// Create session
|
|
73
|
+
const session = (0, core_1.createSession)({
|
|
74
|
+
offer,
|
|
75
|
+
agentPubkey,
|
|
76
|
+
depositAmount,
|
|
77
|
+
creationTxSignature: txSignature,
|
|
78
|
+
});
|
|
79
|
+
// Store session
|
|
80
|
+
this.sessionStore.saveSession(session);
|
|
81
|
+
return session;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Get a session by ID
|
|
85
|
+
*/
|
|
86
|
+
getSession(sessionId) {
|
|
87
|
+
return this.sessionStore.getSession(sessionId);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Create a Receipt for a completed request
|
|
91
|
+
*
|
|
92
|
+
* @param sessionId - Session ID
|
|
93
|
+
* @param inputData - Request input data
|
|
94
|
+
* @param outputData - Request output data
|
|
95
|
+
* @param requestStartedAt - Request start timestamp
|
|
96
|
+
* @returns Signed Receipt
|
|
97
|
+
*/
|
|
98
|
+
async createReceiptForRequest(sessionId, inputData, outputData, requestStartedAt) {
|
|
99
|
+
const session = this.sessionStore.getSession(sessionId);
|
|
100
|
+
if (!session) {
|
|
101
|
+
throw new Error(`Session ${sessionId} not found`);
|
|
102
|
+
}
|
|
103
|
+
const requestCompletedAt = Date.now();
|
|
104
|
+
// Create receipt
|
|
105
|
+
const receipt = (0, core_1.createReceipt)({
|
|
106
|
+
session,
|
|
107
|
+
requestNumber: session.requestCount + 1,
|
|
108
|
+
inputData,
|
|
109
|
+
outputData,
|
|
110
|
+
requestStartedAt,
|
|
111
|
+
requestCompletedAt,
|
|
112
|
+
amountCharged: session.pricePerRequest,
|
|
113
|
+
}, this.wallet);
|
|
114
|
+
// Update session
|
|
115
|
+
this.sessionStore.updateSessionAfterRequest(sessionId, receipt);
|
|
116
|
+
return receipt;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Close a session and process refund
|
|
120
|
+
*/
|
|
121
|
+
async closeSession(sessionId) {
|
|
122
|
+
const session = this.sessionStore.getSession(sessionId);
|
|
123
|
+
if (!session) {
|
|
124
|
+
throw new Error(`Session ${sessionId} not found`);
|
|
125
|
+
}
|
|
126
|
+
const refundAmount = session.remainingBalance;
|
|
127
|
+
if (refundAmount > 0) {
|
|
128
|
+
// TODO: Process refund on-chain
|
|
129
|
+
// const txSignature = await this.processRefund(session.agentPubkey, refundAmount);
|
|
130
|
+
// return { refundAmount, txSignature };
|
|
131
|
+
}
|
|
132
|
+
// Mark session as closed
|
|
133
|
+
this.sessionStore.closeSession(sessionId);
|
|
134
|
+
return { refundAmount };
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Verify payment on Solana blockchain
|
|
138
|
+
*/
|
|
139
|
+
async verifyPayment(txSignature, expectedPayer, expectedAmount) {
|
|
140
|
+
try {
|
|
141
|
+
const transaction = await this.connection.getTransaction(txSignature, {
|
|
142
|
+
commitment: 'confirmed',
|
|
143
|
+
});
|
|
144
|
+
if (!transaction) {
|
|
145
|
+
throw new Error('Transaction not found');
|
|
146
|
+
}
|
|
147
|
+
// TODO: Verify transaction details match expected values
|
|
148
|
+
// This would check:
|
|
149
|
+
// 1. Payer matches expectedPayer
|
|
150
|
+
// 2. Recipient is this provider
|
|
151
|
+
// 3. Amount matches expectedAmount
|
|
152
|
+
// 4. Transaction is successful
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
throw new Error(`Payment verification failed: ${error}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Get provider's public key
|
|
160
|
+
*/
|
|
161
|
+
get publicKey() {
|
|
162
|
+
return this.wallet.publicKey;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
exports.GistProvider = GistProvider;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SessionStore - In-memory session storage for providers
|
|
3
|
+
*/
|
|
4
|
+
import { Connection, Keypair } from '@solana/web3.js';
|
|
5
|
+
import { Session, Receipt } from '@gistplus/core';
|
|
6
|
+
/**
|
|
7
|
+
* SessionStore manages active sessions for a provider
|
|
8
|
+
*
|
|
9
|
+
* In production, this would be backed by Redis, PostgreSQL, or similar.
|
|
10
|
+
* For MVP, we use an in-memory store.
|
|
11
|
+
*/
|
|
12
|
+
export declare class SessionStore {
|
|
13
|
+
private sessions;
|
|
14
|
+
private connection;
|
|
15
|
+
private wallet;
|
|
16
|
+
constructor(connection: Connection, wallet: Keypair);
|
|
17
|
+
/**
|
|
18
|
+
* Save a new session
|
|
19
|
+
*/
|
|
20
|
+
saveSession(session: Session): void;
|
|
21
|
+
/**
|
|
22
|
+
* Get a session by ID
|
|
23
|
+
*/
|
|
24
|
+
getSession(sessionId: string): Session | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Update session after a request
|
|
27
|
+
*/
|
|
28
|
+
updateSessionAfterRequest(sessionId: string, receipt: Receipt): void;
|
|
29
|
+
/**
|
|
30
|
+
* Close a session
|
|
31
|
+
*/
|
|
32
|
+
closeSession(sessionId: string): void;
|
|
33
|
+
/**
|
|
34
|
+
* Get all active sessions
|
|
35
|
+
*/
|
|
36
|
+
getActiveSessions(): Session[];
|
|
37
|
+
/**
|
|
38
|
+
* Clean up expired sessions
|
|
39
|
+
*/
|
|
40
|
+
private cleanup;
|
|
41
|
+
/**
|
|
42
|
+
* Start background cleanup task
|
|
43
|
+
*/
|
|
44
|
+
private startCleanupTask;
|
|
45
|
+
/**
|
|
46
|
+
* Get session count
|
|
47
|
+
*/
|
|
48
|
+
getSessionCount(): number;
|
|
49
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SessionStore - In-memory session storage for providers
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SessionStore = void 0;
|
|
7
|
+
const core_1 = require("@gistplus/core");
|
|
8
|
+
/**
|
|
9
|
+
* SessionStore manages active sessions for a provider
|
|
10
|
+
*
|
|
11
|
+
* In production, this would be backed by Redis, PostgreSQL, or similar.
|
|
12
|
+
* For MVP, we use an in-memory store.
|
|
13
|
+
*/
|
|
14
|
+
class SessionStore {
|
|
15
|
+
constructor(connection, wallet) {
|
|
16
|
+
this.sessions = new Map();
|
|
17
|
+
this.connection = connection;
|
|
18
|
+
this.wallet = wallet;
|
|
19
|
+
// Start background cleanup
|
|
20
|
+
this.startCleanupTask();
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Save a new session
|
|
24
|
+
*/
|
|
25
|
+
saveSession(session) {
|
|
26
|
+
this.sessions.set(session.sessionId, session);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Get a session by ID
|
|
30
|
+
*/
|
|
31
|
+
getSession(sessionId) {
|
|
32
|
+
const session = this.sessions.get(sessionId);
|
|
33
|
+
if (!session) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
// Check if expired
|
|
37
|
+
if (!(0, core_1.isSessionActive)(session) && session.state === core_1.SessionState.ACTIVE) {
|
|
38
|
+
const expiredSession = (0, core_1.expireSession)(session);
|
|
39
|
+
this.sessions.set(sessionId, expiredSession);
|
|
40
|
+
return expiredSession;
|
|
41
|
+
}
|
|
42
|
+
return session;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Update session after a request
|
|
46
|
+
*/
|
|
47
|
+
updateSessionAfterRequest(sessionId, receipt) {
|
|
48
|
+
const session = this.sessions.get(sessionId);
|
|
49
|
+
if (!session) {
|
|
50
|
+
throw new Error(`Session ${sessionId} not found`);
|
|
51
|
+
}
|
|
52
|
+
const updatedSession = (0, core_1.deductFromSession)(session, receipt.amountCharged);
|
|
53
|
+
this.sessions.set(sessionId, updatedSession);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Close a session
|
|
57
|
+
*/
|
|
58
|
+
closeSession(sessionId) {
|
|
59
|
+
this.sessions.delete(sessionId);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Get all active sessions
|
|
63
|
+
*/
|
|
64
|
+
getActiveSessions() {
|
|
65
|
+
return Array.from(this.sessions.values()).filter(core_1.isSessionActive);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Clean up expired sessions
|
|
69
|
+
*/
|
|
70
|
+
cleanup() {
|
|
71
|
+
const now = Date.now();
|
|
72
|
+
const graceperiod = 3600000; // 1 hour
|
|
73
|
+
for (const [sessionId, session] of this.sessions.entries()) {
|
|
74
|
+
if (now > session.expiresAt + graceperiod) {
|
|
75
|
+
this.sessions.delete(sessionId);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Start background cleanup task
|
|
81
|
+
*/
|
|
82
|
+
startCleanupTask() {
|
|
83
|
+
setInterval(() => {
|
|
84
|
+
this.cleanup();
|
|
85
|
+
}, 300000); // Every 5 minutes
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Get session count
|
|
89
|
+
*/
|
|
90
|
+
getSessionCount() {
|
|
91
|
+
return this.sessions.size;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
exports.SessionStore = SessionStore;
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gistplus/server",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Server middleware for Gist Plus - enables API providers to monetize endpoints with automatic negotiation and payment verification",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"test": "jest",
|
|
17
|
+
"clean": "rimraf dist"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"gistplus",
|
|
21
|
+
"gist",
|
|
22
|
+
"server",
|
|
23
|
+
"middleware",
|
|
24
|
+
"express",
|
|
25
|
+
"api-monetization"
|
|
26
|
+
],
|
|
27
|
+
"author": "Gist Plus",
|
|
28
|
+
"license": "Apache-2.0",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "https://github.com/gistplus/gistplus"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://github.com/gistplus/gistplus#readme",
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/gistplus/gistplus/issues"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@gistplus/core": "^0.1.0",
|
|
39
|
+
"@solana/web3.js": "^1.87.6",
|
|
40
|
+
"@solana/spl-token": "^0.3.9",
|
|
41
|
+
"express": "^4.18.2"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"typescript": "^5.3.3",
|
|
45
|
+
"@types/node": "^20.10.6",
|
|
46
|
+
"@types/express": "^4.17.21",
|
|
47
|
+
"jest": "^29.7.0",
|
|
48
|
+
"@types/jest": "^29.5.11",
|
|
49
|
+
"rimraf": "^5.0.5"
|
|
50
|
+
},
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"express": "^4.18.0"
|
|
53
|
+
}
|
|
54
|
+
}
|