@ottocode/server 0.1.265 → 0.1.267

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.
Files changed (74) hide show
  1. package/package.json +3 -3
  2. package/src/routes/auth/copilot.ts +699 -0
  3. package/src/routes/auth/oauth.ts +578 -0
  4. package/src/routes/auth/onboarding.ts +45 -0
  5. package/src/routes/auth/providers.ts +189 -0
  6. package/src/routes/auth/service.ts +167 -0
  7. package/src/routes/auth/state.ts +23 -0
  8. package/src/routes/auth/status.ts +203 -0
  9. package/src/routes/auth/wallet.ts +229 -0
  10. package/src/routes/auth.ts +12 -2080
  11. package/src/routes/config/models-service.ts +411 -0
  12. package/src/routes/config/models.ts +6 -426
  13. package/src/routes/config/providers-service.ts +237 -0
  14. package/src/routes/config/providers.ts +10 -242
  15. package/src/routes/files/handlers.ts +297 -0
  16. package/src/routes/files/service.ts +313 -0
  17. package/src/routes/files.ts +12 -608
  18. package/src/routes/git/commit-service.ts +207 -0
  19. package/src/routes/git/commit.ts +6 -220
  20. package/src/routes/git/remote-service.ts +116 -0
  21. package/src/routes/git/remote.ts +8 -115
  22. package/src/routes/git/staging-service.ts +111 -0
  23. package/src/routes/git/staging.ts +10 -205
  24. package/src/routes/mcp/auth.ts +338 -0
  25. package/src/routes/mcp/lifecycle.ts +263 -0
  26. package/src/routes/mcp/servers.ts +212 -0
  27. package/src/routes/mcp/service.ts +664 -0
  28. package/src/routes/mcp/state.ts +13 -0
  29. package/src/routes/mcp.ts +6 -1233
  30. package/src/routes/ottorouter/billing.ts +593 -0
  31. package/src/routes/ottorouter/service.ts +92 -0
  32. package/src/routes/ottorouter/topup.ts +301 -0
  33. package/src/routes/ottorouter/wallet.ts +370 -0
  34. package/src/routes/ottorouter.ts +6 -1319
  35. package/src/routes/research/service.ts +339 -0
  36. package/src/routes/research.ts +12 -390
  37. package/src/routes/sessions/crud.ts +563 -0
  38. package/src/routes/sessions/queue.ts +242 -0
  39. package/src/routes/sessions/retry.ts +121 -0
  40. package/src/routes/sessions/service.ts +768 -0
  41. package/src/routes/sessions/share.ts +434 -0
  42. package/src/routes/sessions.ts +8 -1977
  43. package/src/routes/skills/service.ts +221 -0
  44. package/src/routes/skills/spec.ts +309 -0
  45. package/src/routes/skills.ts +31 -909
  46. package/src/routes/terminals/service.ts +326 -0
  47. package/src/routes/terminals.ts +19 -295
  48. package/src/routes/tunnel/service.ts +217 -0
  49. package/src/routes/tunnel.ts +29 -219
  50. package/src/runtime/agent/registry-prompts.ts +147 -0
  51. package/src/runtime/agent/registry.ts +6 -124
  52. package/src/runtime/agent/runner-errors.ts +116 -0
  53. package/src/runtime/agent/runner-reminders.ts +45 -0
  54. package/src/runtime/agent/runner-setup-model.ts +75 -0
  55. package/src/runtime/agent/runner-setup-prompt.ts +185 -0
  56. package/src/runtime/agent/runner-setup-tools.ts +103 -0
  57. package/src/runtime/agent/runner-setup-utils.ts +21 -0
  58. package/src/runtime/agent/runner-setup.ts +54 -288
  59. package/src/runtime/agent/runner-telemetry.ts +112 -0
  60. package/src/runtime/agent/runner-text.ts +108 -0
  61. package/src/runtime/agent/runner-tool-observer.ts +86 -0
  62. package/src/runtime/agent/runner.ts +79 -378
  63. package/src/runtime/prompt/builder.ts +5 -1
  64. package/src/runtime/prompt/capabilities.ts +13 -8
  65. package/src/runtime/provider/custom.ts +73 -0
  66. package/src/runtime/provider/index.ts +2 -85
  67. package/src/runtime/provider/reasoning-builders.ts +280 -0
  68. package/src/runtime/provider/reasoning.ts +67 -264
  69. package/src/tools/adapter/events.ts +116 -0
  70. package/src/tools/adapter/execution.ts +160 -0
  71. package/src/tools/adapter/pending.ts +37 -0
  72. package/src/tools/adapter/persistence.ts +166 -0
  73. package/src/tools/adapter/results.ts +97 -0
  74. 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
+ }