@ottocode/server 0.1.265 → 0.1.266
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/package.json +3 -3
- package/src/routes/auth/copilot.ts +699 -0
- package/src/routes/auth/oauth.ts +578 -0
- package/src/routes/auth/onboarding.ts +45 -0
- package/src/routes/auth/providers.ts +189 -0
- package/src/routes/auth/service.ts +167 -0
- package/src/routes/auth/state.ts +23 -0
- package/src/routes/auth/status.ts +203 -0
- package/src/routes/auth/wallet.ts +229 -0
- package/src/routes/auth.ts +12 -2080
- package/src/routes/config/models-service.ts +411 -0
- package/src/routes/config/models.ts +6 -426
- package/src/routes/config/providers-service.ts +237 -0
- package/src/routes/config/providers.ts +10 -242
- package/src/routes/files/handlers.ts +297 -0
- package/src/routes/files/service.ts +313 -0
- package/src/routes/files.ts +12 -608
- package/src/routes/git/commit-service.ts +207 -0
- package/src/routes/git/commit.ts +6 -220
- package/src/routes/git/remote-service.ts +116 -0
- package/src/routes/git/remote.ts +8 -115
- package/src/routes/git/staging-service.ts +111 -0
- package/src/routes/git/staging.ts +10 -205
- package/src/routes/mcp/auth.ts +338 -0
- package/src/routes/mcp/lifecycle.ts +263 -0
- package/src/routes/mcp/servers.ts +212 -0
- package/src/routes/mcp/service.ts +664 -0
- package/src/routes/mcp/state.ts +13 -0
- package/src/routes/mcp.ts +6 -1233
- package/src/routes/ottorouter/billing.ts +593 -0
- package/src/routes/ottorouter/service.ts +92 -0
- package/src/routes/ottorouter/topup.ts +301 -0
- package/src/routes/ottorouter/wallet.ts +370 -0
- package/src/routes/ottorouter.ts +6 -1319
- package/src/routes/research/service.ts +339 -0
- package/src/routes/research.ts +12 -390
- package/src/routes/sessions/crud.ts +563 -0
- package/src/routes/sessions/queue.ts +242 -0
- package/src/routes/sessions/retry.ts +121 -0
- package/src/routes/sessions/service.ts +768 -0
- package/src/routes/sessions/share.ts +434 -0
- package/src/routes/sessions.ts +8 -1977
- package/src/routes/skills/service.ts +221 -0
- package/src/routes/skills/spec.ts +309 -0
- package/src/routes/skills.ts +31 -909
- package/src/routes/terminals/service.ts +326 -0
- package/src/routes/terminals.ts +19 -295
- package/src/routes/tunnel/service.ts +217 -0
- package/src/routes/tunnel.ts +29 -219
- package/src/runtime/agent/registry-prompts.ts +147 -0
- package/src/runtime/agent/registry.ts +6 -124
- package/src/runtime/agent/runner-errors.ts +116 -0
- package/src/runtime/agent/runner-reminders.ts +45 -0
- package/src/runtime/agent/runner-setup-model.ts +75 -0
- package/src/runtime/agent/runner-setup-prompt.ts +185 -0
- package/src/runtime/agent/runner-setup-tools.ts +103 -0
- package/src/runtime/agent/runner-setup-utils.ts +21 -0
- package/src/runtime/agent/runner-setup.ts +54 -288
- package/src/runtime/agent/runner-telemetry.ts +112 -0
- package/src/runtime/agent/runner-text.ts +108 -0
- package/src/runtime/agent/runner-tool-observer.ts +86 -0
- package/src/runtime/agent/runner.ts +79 -378
- package/src/runtime/provider/custom.ts +73 -0
- package/src/runtime/provider/index.ts +2 -85
- package/src/runtime/provider/reasoning-builders.ts +280 -0
- package/src/runtime/provider/reasoning.ts +67 -264
- package/src/tools/adapter/events.ts +116 -0
- package/src/tools/adapter/execution.ts +160 -0
- package/src/tools/adapter/pending.ts +37 -0
- package/src/tools/adapter/persistence.ts +166 -0
- package/src/tools/adapter/results.ts +97 -0
- package/src/tools/adapter.ts +124 -451
|
@@ -0,0 +1,593 @@
|
|
|
1
|
+
import type { Hono } from 'hono';
|
|
2
|
+
import { logger } from '@ottocode/sdk';
|
|
3
|
+
import { openApiRoute } from '../../openapi/route.ts';
|
|
4
|
+
import { serializeError } from '../../runtime/errors/api-error.ts';
|
|
5
|
+
import {
|
|
6
|
+
buildWalletHeaders,
|
|
7
|
+
getOttoRouterBaseUrl,
|
|
8
|
+
getOttoRouterPrivateKey,
|
|
9
|
+
} from './service.ts';
|
|
10
|
+
|
|
11
|
+
export function registerOttoRouterBillingRoutes(app: Hono) {
|
|
12
|
+
openApiRoute(
|
|
13
|
+
app,
|
|
14
|
+
{
|
|
15
|
+
method: 'get',
|
|
16
|
+
path: '/v1/ottorouter/topup/polar/estimate',
|
|
17
|
+
tags: ['ottorouter'],
|
|
18
|
+
operationId: 'getPolarTopupEstimate',
|
|
19
|
+
summary: 'Get estimated fees for a Polar topup',
|
|
20
|
+
parameters: [
|
|
21
|
+
{
|
|
22
|
+
in: 'query',
|
|
23
|
+
name: 'amount',
|
|
24
|
+
required: true,
|
|
25
|
+
schema: {
|
|
26
|
+
type: 'number',
|
|
27
|
+
},
|
|
28
|
+
description: 'Amount in USD',
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
responses: {
|
|
32
|
+
'200': {
|
|
33
|
+
description: 'OK',
|
|
34
|
+
content: {
|
|
35
|
+
'application/json': {
|
|
36
|
+
schema: {
|
|
37
|
+
type: 'object',
|
|
38
|
+
properties: {
|
|
39
|
+
creditAmount: {
|
|
40
|
+
type: 'number',
|
|
41
|
+
},
|
|
42
|
+
chargeAmount: {
|
|
43
|
+
type: 'number',
|
|
44
|
+
},
|
|
45
|
+
feeAmount: {
|
|
46
|
+
type: 'number',
|
|
47
|
+
},
|
|
48
|
+
feeBreakdown: {
|
|
49
|
+
type: 'object',
|
|
50
|
+
properties: {
|
|
51
|
+
basePercent: {
|
|
52
|
+
type: 'number',
|
|
53
|
+
},
|
|
54
|
+
internationalPercent: {
|
|
55
|
+
type: 'number',
|
|
56
|
+
},
|
|
57
|
+
fixedCents: {
|
|
58
|
+
type: 'number',
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
async (c) => {
|
|
70
|
+
try {
|
|
71
|
+
const amount = c.req.query('amount');
|
|
72
|
+
if (!amount) {
|
|
73
|
+
return c.json({ error: 'Missing amount parameter' }, 400);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const baseUrl = getOttoRouterBaseUrl();
|
|
77
|
+
const response = await fetch(
|
|
78
|
+
`${baseUrl}/v1/topup/polar/estimate?amount=${amount}`,
|
|
79
|
+
{
|
|
80
|
+
method: 'GET',
|
|
81
|
+
headers: { 'Content-Type': 'application/json' },
|
|
82
|
+
},
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const data = await response.json();
|
|
86
|
+
if (!response.ok) {
|
|
87
|
+
return c.json(data, response.status as 400 | 500);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return c.json(data);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
logger.error('Failed to get Polar estimate', error);
|
|
93
|
+
const errorResponse = serializeError(error);
|
|
94
|
+
return c.json(errorResponse, errorResponse.error.status || 500);
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
openApiRoute(
|
|
100
|
+
app,
|
|
101
|
+
{
|
|
102
|
+
method: 'post',
|
|
103
|
+
path: '/v1/ottorouter/topup/polar',
|
|
104
|
+
tags: ['ottorouter'],
|
|
105
|
+
operationId: 'createPolarCheckout',
|
|
106
|
+
summary: 'Create a Polar checkout for topping up',
|
|
107
|
+
requestBody: {
|
|
108
|
+
required: true,
|
|
109
|
+
content: {
|
|
110
|
+
'application/json': {
|
|
111
|
+
schema: {
|
|
112
|
+
type: 'object',
|
|
113
|
+
properties: {
|
|
114
|
+
amount: {
|
|
115
|
+
type: 'number',
|
|
116
|
+
},
|
|
117
|
+
successUrl: {
|
|
118
|
+
type: 'string',
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
required: ['amount', 'successUrl'],
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
responses: {
|
|
127
|
+
'200': {
|
|
128
|
+
description: 'OK',
|
|
129
|
+
content: {
|
|
130
|
+
'application/json': {
|
|
131
|
+
schema: {
|
|
132
|
+
type: 'object',
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
'401': {
|
|
138
|
+
description: 'Wallet not configured',
|
|
139
|
+
content: {
|
|
140
|
+
'application/json': {
|
|
141
|
+
schema: {
|
|
142
|
+
type: 'object',
|
|
143
|
+
properties: {
|
|
144
|
+
error: {
|
|
145
|
+
type: 'string',
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
required: ['error'],
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
async (c) => {
|
|
156
|
+
try {
|
|
157
|
+
const privateKey = await getOttoRouterPrivateKey();
|
|
158
|
+
if (!privateKey) {
|
|
159
|
+
return c.json({ error: 'OttoRouter wallet not configured' }, 401);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const body = await c.req.json();
|
|
163
|
+
const { amount, successUrl } = body as {
|
|
164
|
+
amount: number;
|
|
165
|
+
successUrl: string;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
if (!amount || typeof amount !== 'number') {
|
|
169
|
+
return c.json({ error: 'Invalid amount' }, 400);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (!successUrl || typeof successUrl !== 'string') {
|
|
173
|
+
return c.json({ error: 'Missing successUrl' }, 400);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const walletHeaders = buildWalletHeaders(privateKey);
|
|
177
|
+
const baseUrl = getOttoRouterBaseUrl();
|
|
178
|
+
|
|
179
|
+
const response = await fetch(`${baseUrl}/v1/topup/polar`, {
|
|
180
|
+
method: 'POST',
|
|
181
|
+
headers: {
|
|
182
|
+
'Content-Type': 'application/json',
|
|
183
|
+
...walletHeaders,
|
|
184
|
+
},
|
|
185
|
+
body: JSON.stringify({ amount, successUrl }),
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const data = await response.json();
|
|
189
|
+
if (!response.ok) {
|
|
190
|
+
return c.json(data, response.status as 400 | 500);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return c.json(data);
|
|
194
|
+
} catch (error) {
|
|
195
|
+
logger.error('Failed to create Polar checkout', error);
|
|
196
|
+
const errorResponse = serializeError(error);
|
|
197
|
+
return c.json(errorResponse, errorResponse.error.status || 500);
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
openApiRoute(
|
|
203
|
+
app,
|
|
204
|
+
{
|
|
205
|
+
method: 'get',
|
|
206
|
+
path: '/v1/ottorouter/topup/polar/status',
|
|
207
|
+
tags: ['ottorouter'],
|
|
208
|
+
operationId: 'getPolarTopupStatus',
|
|
209
|
+
summary: 'Get status of a Polar checkout',
|
|
210
|
+
parameters: [
|
|
211
|
+
{
|
|
212
|
+
in: 'query',
|
|
213
|
+
name: 'checkoutId',
|
|
214
|
+
required: true,
|
|
215
|
+
schema: {
|
|
216
|
+
type: 'string',
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
],
|
|
220
|
+
responses: {
|
|
221
|
+
'200': {
|
|
222
|
+
description: 'OK',
|
|
223
|
+
content: {
|
|
224
|
+
'application/json': {
|
|
225
|
+
schema: {
|
|
226
|
+
type: 'object',
|
|
227
|
+
properties: {
|
|
228
|
+
checkoutId: {
|
|
229
|
+
type: 'string',
|
|
230
|
+
},
|
|
231
|
+
confirmed: {
|
|
232
|
+
type: 'boolean',
|
|
233
|
+
},
|
|
234
|
+
amountUsd: {
|
|
235
|
+
type: 'number',
|
|
236
|
+
nullable: true,
|
|
237
|
+
},
|
|
238
|
+
confirmedAt: {
|
|
239
|
+
type: 'string',
|
|
240
|
+
nullable: true,
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
async (c) => {
|
|
250
|
+
try {
|
|
251
|
+
const checkoutId = c.req.query('checkoutId');
|
|
252
|
+
if (!checkoutId) {
|
|
253
|
+
return c.json({ error: 'Missing checkoutId parameter' }, 400);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const baseUrl = getOttoRouterBaseUrl();
|
|
257
|
+
const response = await fetch(
|
|
258
|
+
`${baseUrl}/v1/topup/polar/status?checkoutId=${checkoutId}`,
|
|
259
|
+
{
|
|
260
|
+
method: 'GET',
|
|
261
|
+
headers: { 'Content-Type': 'application/json' },
|
|
262
|
+
},
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
const data = await response.json();
|
|
266
|
+
if (!response.ok) {
|
|
267
|
+
return c.json(data, response.status as 400 | 500);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return c.json(data);
|
|
271
|
+
} catch (error) {
|
|
272
|
+
logger.error('Failed to check Polar status', error);
|
|
273
|
+
const errorResponse = serializeError(error);
|
|
274
|
+
return c.json(errorResponse, errorResponse.error.status || 500);
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
openApiRoute(
|
|
280
|
+
app,
|
|
281
|
+
{
|
|
282
|
+
method: 'get',
|
|
283
|
+
path: '/v1/ottorouter/topup/razorpay/estimate',
|
|
284
|
+
tags: ['ottorouter'],
|
|
285
|
+
operationId: 'getRazorpayTopupEstimate',
|
|
286
|
+
summary: 'Get estimated fees for a Razorpay topup',
|
|
287
|
+
parameters: [
|
|
288
|
+
{
|
|
289
|
+
in: 'query',
|
|
290
|
+
name: 'amount',
|
|
291
|
+
required: true,
|
|
292
|
+
schema: {
|
|
293
|
+
type: 'number',
|
|
294
|
+
},
|
|
295
|
+
description: 'Amount in USD',
|
|
296
|
+
},
|
|
297
|
+
],
|
|
298
|
+
responses: {
|
|
299
|
+
'200': {
|
|
300
|
+
description: 'OK',
|
|
301
|
+
content: {
|
|
302
|
+
'application/json': {
|
|
303
|
+
schema: {
|
|
304
|
+
type: 'object',
|
|
305
|
+
properties: {
|
|
306
|
+
creditAmountUsd: {
|
|
307
|
+
type: 'number',
|
|
308
|
+
},
|
|
309
|
+
chargeAmountInr: {
|
|
310
|
+
type: 'number',
|
|
311
|
+
},
|
|
312
|
+
feeAmountInr: {
|
|
313
|
+
type: 'number',
|
|
314
|
+
},
|
|
315
|
+
currency: {
|
|
316
|
+
type: 'string',
|
|
317
|
+
},
|
|
318
|
+
exchangeRate: {
|
|
319
|
+
type: 'number',
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
async (c) => {
|
|
329
|
+
try {
|
|
330
|
+
const amount = c.req.query('amount');
|
|
331
|
+
if (!amount) {
|
|
332
|
+
return c.json({ error: 'Missing amount parameter' }, 400);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const baseUrl = getOttoRouterBaseUrl();
|
|
336
|
+
const response = await fetch(
|
|
337
|
+
`${baseUrl}/v1/topup/razorpay/estimate?amount=${amount}`,
|
|
338
|
+
{
|
|
339
|
+
method: 'GET',
|
|
340
|
+
headers: { 'Content-Type': 'application/json' },
|
|
341
|
+
},
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
const data = await response.json();
|
|
345
|
+
if (!response.ok) {
|
|
346
|
+
return c.json(data, response.status as 400 | 500);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return c.json(data);
|
|
350
|
+
} catch (error) {
|
|
351
|
+
logger.error('Failed to get Razorpay estimate', error);
|
|
352
|
+
const errorResponse = serializeError(error);
|
|
353
|
+
return c.json(errorResponse, errorResponse.error.status || 500);
|
|
354
|
+
}
|
|
355
|
+
},
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
openApiRoute(
|
|
359
|
+
app,
|
|
360
|
+
{
|
|
361
|
+
method: 'post',
|
|
362
|
+
path: '/v1/ottorouter/topup/razorpay',
|
|
363
|
+
tags: ['ottorouter'],
|
|
364
|
+
operationId: 'createRazorpayOrder',
|
|
365
|
+
summary: 'Create a Razorpay order for topping up',
|
|
366
|
+
requestBody: {
|
|
367
|
+
required: true,
|
|
368
|
+
content: {
|
|
369
|
+
'application/json': {
|
|
370
|
+
schema: {
|
|
371
|
+
type: 'object',
|
|
372
|
+
properties: {
|
|
373
|
+
amount: {
|
|
374
|
+
type: 'number',
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
required: ['amount'],
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
},
|
|
381
|
+
},
|
|
382
|
+
responses: {
|
|
383
|
+
'200': {
|
|
384
|
+
description: 'OK',
|
|
385
|
+
content: {
|
|
386
|
+
'application/json': {
|
|
387
|
+
schema: {
|
|
388
|
+
type: 'object',
|
|
389
|
+
properties: {
|
|
390
|
+
success: {
|
|
391
|
+
type: 'boolean',
|
|
392
|
+
},
|
|
393
|
+
orderId: {
|
|
394
|
+
type: 'string',
|
|
395
|
+
},
|
|
396
|
+
amount: {
|
|
397
|
+
type: 'number',
|
|
398
|
+
},
|
|
399
|
+
currency: {
|
|
400
|
+
type: 'string',
|
|
401
|
+
},
|
|
402
|
+
creditAmountUsd: {
|
|
403
|
+
type: 'number',
|
|
404
|
+
},
|
|
405
|
+
keyId: {
|
|
406
|
+
type: 'string',
|
|
407
|
+
},
|
|
408
|
+
},
|
|
409
|
+
},
|
|
410
|
+
},
|
|
411
|
+
},
|
|
412
|
+
},
|
|
413
|
+
'401': {
|
|
414
|
+
description: 'Wallet not configured',
|
|
415
|
+
content: {
|
|
416
|
+
'application/json': {
|
|
417
|
+
schema: {
|
|
418
|
+
type: 'object',
|
|
419
|
+
properties: {
|
|
420
|
+
error: {
|
|
421
|
+
type: 'string',
|
|
422
|
+
},
|
|
423
|
+
},
|
|
424
|
+
required: ['error'],
|
|
425
|
+
},
|
|
426
|
+
},
|
|
427
|
+
},
|
|
428
|
+
},
|
|
429
|
+
},
|
|
430
|
+
},
|
|
431
|
+
async (c) => {
|
|
432
|
+
try {
|
|
433
|
+
const privateKey = await getOttoRouterPrivateKey();
|
|
434
|
+
if (!privateKey) {
|
|
435
|
+
return c.json({ error: 'OttoRouter wallet not configured' }, 401);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const body = await c.req.json();
|
|
439
|
+
const { amount } = body as { amount: number };
|
|
440
|
+
|
|
441
|
+
if (!amount || typeof amount !== 'number') {
|
|
442
|
+
return c.json({ error: 'Invalid amount' }, 400);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const walletHeaders = buildWalletHeaders(privateKey);
|
|
446
|
+
const baseUrl = getOttoRouterBaseUrl();
|
|
447
|
+
|
|
448
|
+
const response = await fetch(`${baseUrl}/v1/topup/razorpay`, {
|
|
449
|
+
method: 'POST',
|
|
450
|
+
headers: {
|
|
451
|
+
'Content-Type': 'application/json',
|
|
452
|
+
...walletHeaders,
|
|
453
|
+
},
|
|
454
|
+
body: JSON.stringify({ amount }),
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
const data = await response.json();
|
|
458
|
+
if (!response.ok) {
|
|
459
|
+
return c.json(data, response.status as 400 | 500);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return c.json(data);
|
|
463
|
+
} catch (error) {
|
|
464
|
+
logger.error('Failed to create Razorpay order', error);
|
|
465
|
+
const errorResponse = serializeError(error);
|
|
466
|
+
return c.json(errorResponse, errorResponse.error.status || 500);
|
|
467
|
+
}
|
|
468
|
+
},
|
|
469
|
+
);
|
|
470
|
+
|
|
471
|
+
openApiRoute(
|
|
472
|
+
app,
|
|
473
|
+
{
|
|
474
|
+
method: 'post',
|
|
475
|
+
path: '/v1/ottorouter/topup/razorpay/verify',
|
|
476
|
+
tags: ['ottorouter'],
|
|
477
|
+
operationId: 'verifyRazorpayPayment',
|
|
478
|
+
summary: 'Verify Razorpay payment and credit balance',
|
|
479
|
+
requestBody: {
|
|
480
|
+
required: true,
|
|
481
|
+
content: {
|
|
482
|
+
'application/json': {
|
|
483
|
+
schema: {
|
|
484
|
+
type: 'object',
|
|
485
|
+
properties: {
|
|
486
|
+
razorpay_order_id: {
|
|
487
|
+
type: 'string',
|
|
488
|
+
},
|
|
489
|
+
razorpay_payment_id: {
|
|
490
|
+
type: 'string',
|
|
491
|
+
},
|
|
492
|
+
razorpay_signature: {
|
|
493
|
+
type: 'string',
|
|
494
|
+
},
|
|
495
|
+
},
|
|
496
|
+
required: [
|
|
497
|
+
'razorpay_order_id',
|
|
498
|
+
'razorpay_payment_id',
|
|
499
|
+
'razorpay_signature',
|
|
500
|
+
],
|
|
501
|
+
},
|
|
502
|
+
},
|
|
503
|
+
},
|
|
504
|
+
},
|
|
505
|
+
responses: {
|
|
506
|
+
'200': {
|
|
507
|
+
description: 'OK',
|
|
508
|
+
content: {
|
|
509
|
+
'application/json': {
|
|
510
|
+
schema: {
|
|
511
|
+
type: 'object',
|
|
512
|
+
properties: {
|
|
513
|
+
success: {
|
|
514
|
+
type: 'boolean',
|
|
515
|
+
},
|
|
516
|
+
credited: {
|
|
517
|
+
type: 'number',
|
|
518
|
+
},
|
|
519
|
+
newBalance: {
|
|
520
|
+
type: 'number',
|
|
521
|
+
},
|
|
522
|
+
},
|
|
523
|
+
},
|
|
524
|
+
},
|
|
525
|
+
},
|
|
526
|
+
},
|
|
527
|
+
'401': {
|
|
528
|
+
description: 'Wallet not configured',
|
|
529
|
+
content: {
|
|
530
|
+
'application/json': {
|
|
531
|
+
schema: {
|
|
532
|
+
type: 'object',
|
|
533
|
+
properties: {
|
|
534
|
+
error: {
|
|
535
|
+
type: 'string',
|
|
536
|
+
},
|
|
537
|
+
},
|
|
538
|
+
required: ['error'],
|
|
539
|
+
},
|
|
540
|
+
},
|
|
541
|
+
},
|
|
542
|
+
},
|
|
543
|
+
},
|
|
544
|
+
},
|
|
545
|
+
async (c) => {
|
|
546
|
+
try {
|
|
547
|
+
const privateKey = await getOttoRouterPrivateKey();
|
|
548
|
+
if (!privateKey) {
|
|
549
|
+
return c.json({ error: 'OttoRouter wallet not configured' }, 401);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const body = await c.req.json();
|
|
553
|
+
const { razorpay_order_id, razorpay_payment_id, razorpay_signature } =
|
|
554
|
+
body as {
|
|
555
|
+
razorpay_order_id: string;
|
|
556
|
+
razorpay_payment_id: string;
|
|
557
|
+
razorpay_signature: string;
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
if (!razorpay_order_id || !razorpay_payment_id || !razorpay_signature) {
|
|
561
|
+
return c.json({ error: 'Missing payment details' }, 400);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const walletHeaders = buildWalletHeaders(privateKey);
|
|
565
|
+
const baseUrl = getOttoRouterBaseUrl();
|
|
566
|
+
|
|
567
|
+
const response = await fetch(`${baseUrl}/v1/topup/razorpay/verify`, {
|
|
568
|
+
method: 'POST',
|
|
569
|
+
headers: {
|
|
570
|
+
'Content-Type': 'application/json',
|
|
571
|
+
...walletHeaders,
|
|
572
|
+
},
|
|
573
|
+
body: JSON.stringify({
|
|
574
|
+
razorpay_order_id,
|
|
575
|
+
razorpay_payment_id,
|
|
576
|
+
razorpay_signature,
|
|
577
|
+
}),
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
const data = await response.json();
|
|
581
|
+
if (!response.ok) {
|
|
582
|
+
return c.json(data, response.status as 400 | 500);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
return c.json(data);
|
|
586
|
+
} catch (error) {
|
|
587
|
+
logger.error('Failed to verify Razorpay payment', error);
|
|
588
|
+
const errorResponse = serializeError(error);
|
|
589
|
+
return c.json(errorResponse, errorResponse.error.status || 500);
|
|
590
|
+
}
|
|
591
|
+
},
|
|
592
|
+
);
|
|
593
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Keypair } from '@solana/web3.js';
|
|
2
|
+
import {
|
|
3
|
+
fetchOttoRouterBalance,
|
|
4
|
+
getAuth,
|
|
5
|
+
getPublicKeyFromPrivate,
|
|
6
|
+
loadConfig,
|
|
7
|
+
} from '@ottocode/sdk';
|
|
8
|
+
import bs58 from 'bs58';
|
|
9
|
+
import nacl from 'tweetnacl';
|
|
10
|
+
|
|
11
|
+
const OTTOROUTER_BASE_URL =
|
|
12
|
+
process.env.OTTOROUTER_BASE_URL || 'https://api.ottorouter.org';
|
|
13
|
+
|
|
14
|
+
export function getOttoRouterBaseUrl(): string {
|
|
15
|
+
return OTTOROUTER_BASE_URL.endsWith('/')
|
|
16
|
+
? OTTOROUTER_BASE_URL.slice(0, -1)
|
|
17
|
+
: OTTOROUTER_BASE_URL;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function getOttoRouterPrivateKey(): Promise<string | null> {
|
|
21
|
+
if (process.env.OTTOROUTER_PRIVATE_KEY) {
|
|
22
|
+
return process.env.OTTOROUTER_PRIVATE_KEY;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const cfg = await loadConfig(process.cwd());
|
|
27
|
+
const auth = await getAuth('ottorouter', cfg.projectRoot);
|
|
28
|
+
if (auth?.type === 'wallet' && auth.secret) {
|
|
29
|
+
return auth.secret;
|
|
30
|
+
}
|
|
31
|
+
} catch {}
|
|
32
|
+
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function signNonce(nonce: string, privateKeyBytes: Uint8Array): string {
|
|
37
|
+
const data = new TextEncoder().encode(nonce);
|
|
38
|
+
const signature = nacl.sign.detached(data, privateKeyBytes);
|
|
39
|
+
return bs58.encode(signature);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function buildWalletHeaders(privateKey: string): Record<string, string> {
|
|
43
|
+
const privateKeyBytes = bs58.decode(privateKey);
|
|
44
|
+
const keypair = Keypair.fromSecretKey(privateKeyBytes);
|
|
45
|
+
const walletAddress = keypair.publicKey.toBase58();
|
|
46
|
+
const nonce = Date.now().toString();
|
|
47
|
+
const signature = signNonce(nonce, privateKeyBytes);
|
|
48
|
+
return {
|
|
49
|
+
'x-wallet-address': walletAddress,
|
|
50
|
+
'x-wallet-nonce': nonce,
|
|
51
|
+
'x-wallet-signature': signature,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function getOttoRouterBalance() {
|
|
56
|
+
const privateKey = await getOttoRouterPrivateKey();
|
|
57
|
+
if (!privateKey) {
|
|
58
|
+
return {
|
|
59
|
+
ok: false as const,
|
|
60
|
+
body: { error: 'OttoRouter wallet not configured' },
|
|
61
|
+
status: 401 as const,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const balance = await fetchOttoRouterBalance({ privateKey });
|
|
66
|
+
if (!balance) {
|
|
67
|
+
return {
|
|
68
|
+
ok: false as const,
|
|
69
|
+
body: { error: 'Failed to fetch balance from OttoRouter' },
|
|
70
|
+
status: 502 as const,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return { ok: true as const, body: balance };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function getOttoRouterWalletInfo() {
|
|
78
|
+
const privateKey = await getOttoRouterPrivateKey();
|
|
79
|
+
if (!privateKey) {
|
|
80
|
+
return {
|
|
81
|
+
configured: false,
|
|
82
|
+
error: 'OttoRouter wallet not configured',
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const publicKey = getPublicKeyFromPrivate(privateKey);
|
|
87
|
+
if (!publicKey) {
|
|
88
|
+
return { configured: false, error: 'Invalid private key' };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return { configured: true, publicKey };
|
|
92
|
+
}
|