@sip-protocol/api 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/dist/server.js ADDED
@@ -0,0 +1,483 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/server.ts
31
+ var server_exports = {};
32
+ __export(server_exports, {
33
+ default: () => server_default
34
+ });
35
+ module.exports = __toCommonJS(server_exports);
36
+ var import_express7 = __toESM(require("express"));
37
+ var import_cors = __toESM(require("cors"));
38
+ var import_helmet = __toESM(require("helmet"));
39
+ var import_compression = __toESM(require("compression"));
40
+ var import_morgan = __toESM(require("morgan"));
41
+
42
+ // src/routes/index.ts
43
+ var import_express6 = require("express");
44
+
45
+ // src/routes/health.ts
46
+ var import_express = require("express");
47
+ var router = (0, import_express.Router)();
48
+ var startTime = Date.now();
49
+ router.get("/", (req, res) => {
50
+ const response = {
51
+ success: true,
52
+ data: {
53
+ status: "healthy",
54
+ version: process.env.npm_package_version || "0.1.0",
55
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
56
+ uptime: Math.floor((Date.now() - startTime) / 1e3)
57
+ }
58
+ };
59
+ res.json(response);
60
+ });
61
+ var health_default = router;
62
+
63
+ // src/routes/stealth.ts
64
+ var import_express2 = require("express");
65
+ var import_sdk2 = require("@sip-protocol/sdk");
66
+
67
+ // src/middleware/error-handler.ts
68
+ var import_sdk = require("@sip-protocol/sdk");
69
+ function errorHandler(err, req, res, next) {
70
+ console.error("[API Error]", {
71
+ path: req.path,
72
+ method: req.method,
73
+ error: err.message,
74
+ stack: err.stack
75
+ });
76
+ if ((0, import_sdk.isSIPError)(err)) {
77
+ const sipError = err;
78
+ return res.status(400).json({
79
+ success: false,
80
+ error: {
81
+ code: sipError.code,
82
+ message: sipError.message,
83
+ details: {
84
+ field: sipError.field,
85
+ expected: sipError.expected
86
+ }
87
+ }
88
+ });
89
+ }
90
+ res.status(500).json({
91
+ success: false,
92
+ error: {
93
+ code: "INTERNAL_SERVER_ERROR",
94
+ message: "An unexpected error occurred",
95
+ details: process.env.NODE_ENV === "development" ? err.message : void 0
96
+ }
97
+ });
98
+ }
99
+ function notFoundHandler(req, res) {
100
+ res.status(404).json({
101
+ success: false,
102
+ error: {
103
+ code: "NOT_FOUND",
104
+ message: `Route ${req.method} ${req.path} not found`
105
+ }
106
+ });
107
+ }
108
+
109
+ // src/middleware/validation.ts
110
+ var import_zod = require("zod");
111
+ function validateRequest(schema) {
112
+ return async (req, res, next) => {
113
+ try {
114
+ if (schema.body) {
115
+ req.body = await schema.body.parseAsync(req.body);
116
+ }
117
+ if (schema.query) {
118
+ req.query = await schema.query.parseAsync(req.query);
119
+ }
120
+ if (schema.params) {
121
+ req.params = await schema.params.parseAsync(req.params);
122
+ }
123
+ next();
124
+ } catch (error) {
125
+ if (error instanceof import_zod.z.ZodError) {
126
+ return res.status(400).json({
127
+ success: false,
128
+ error: {
129
+ code: "VALIDATION_ERROR",
130
+ message: "Invalid request data",
131
+ details: error.errors
132
+ }
133
+ });
134
+ }
135
+ next(error);
136
+ }
137
+ };
138
+ }
139
+ var schemas = {
140
+ generateStealth: import_zod.z.object({
141
+ chain: import_zod.z.enum(["solana", "ethereum", "near", "zcash", "polygon", "arbitrum", "optimism", "base", "bitcoin", "aptos", "sui", "cosmos", "osmosis", "injective", "celestia", "sei", "dydx"]),
142
+ recipientMetaAddress: import_zod.z.object({
143
+ spendingKey: import_zod.z.string().regex(/^0x[0-9a-fA-F]+$/),
144
+ viewingKey: import_zod.z.string().regex(/^0x[0-9a-fA-F]+$/),
145
+ chain: import_zod.z.enum(["solana", "ethereum", "near", "zcash", "polygon", "arbitrum", "optimism", "base", "bitcoin", "aptos", "sui", "cosmos", "osmosis", "injective", "celestia", "sei", "dydx"]),
146
+ label: import_zod.z.string().optional()
147
+ })
148
+ }),
149
+ createCommitment: import_zod.z.object({
150
+ value: import_zod.z.string().regex(/^\d+$/),
151
+ // bigint as string
152
+ blindingFactor: import_zod.z.string().regex(/^0x[0-9a-fA-F]+$/).optional()
153
+ }),
154
+ generateFundingProof: import_zod.z.object({
155
+ balance: import_zod.z.string().regex(/^\d+$/),
156
+ minRequired: import_zod.z.string().regex(/^\d+$/),
157
+ balanceBlinding: import_zod.z.string().regex(/^0x[0-9a-fA-F]+$/)
158
+ }),
159
+ getQuote: import_zod.z.object({
160
+ inputChain: import_zod.z.enum(["solana", "ethereum", "near", "zcash", "polygon", "arbitrum", "optimism", "base", "bitcoin", "aptos", "sui", "cosmos", "osmosis", "injective", "celestia", "sei", "dydx"]),
161
+ inputToken: import_zod.z.string().min(1),
162
+ inputAmount: import_zod.z.string().regex(/^\d+$/),
163
+ outputChain: import_zod.z.enum(["solana", "ethereum", "near", "zcash", "polygon", "arbitrum", "optimism", "base", "bitcoin", "aptos", "sui", "cosmos", "osmosis", "injective", "celestia", "sei", "dydx"]),
164
+ outputToken: import_zod.z.string().min(1),
165
+ slippageTolerance: import_zod.z.number().min(0).max(100).optional()
166
+ }),
167
+ executeSwap: import_zod.z.object({
168
+ intentId: import_zod.z.string().min(1),
169
+ quoteId: import_zod.z.string().min(1),
170
+ privacy: import_zod.z.enum(["transparent", "shielded", "compliant"]).optional(),
171
+ viewingKey: import_zod.z.string().regex(/^0x[0-9a-fA-F]+$/).optional()
172
+ }),
173
+ swapStatus: import_zod.z.object({
174
+ id: import_zod.z.string().min(1)
175
+ })
176
+ };
177
+
178
+ // src/routes/stealth.ts
179
+ var router2 = (0, import_express2.Router)();
180
+ router2.post(
181
+ "/generate",
182
+ validateRequest({ body: schemas.generateStealth }),
183
+ async (req, res) => {
184
+ const { chain, recipientMetaAddress } = req.body;
185
+ const result = (0, import_sdk2.generateStealthAddress)(recipientMetaAddress);
186
+ const response = {
187
+ success: true,
188
+ data: {
189
+ stealthAddress: {
190
+ address: result.stealthAddress.address,
191
+ ephemeralPublicKey: result.stealthAddress.ephemeralPublicKey,
192
+ viewTag: result.stealthAddress.viewTag
193
+ }
194
+ }
195
+ };
196
+ res.json(response);
197
+ }
198
+ );
199
+ var stealth_default = router2;
200
+
201
+ // src/routes/commitment.ts
202
+ var import_express3 = require("express");
203
+ var import_sdk3 = require("@sip-protocol/sdk");
204
+
205
+ // ../../../../node_modules/@noble/hashes/esm/utils.js
206
+ var hasHexBuiltin = /* @__PURE__ */ (() => (
207
+ // @ts-ignore
208
+ typeof Uint8Array.from([]).toHex === "function" && typeof Uint8Array.fromHex === "function"
209
+ ))();
210
+ var asciis = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 };
211
+ function asciiToBase16(ch) {
212
+ if (ch >= asciis._0 && ch <= asciis._9)
213
+ return ch - asciis._0;
214
+ if (ch >= asciis.A && ch <= asciis.F)
215
+ return ch - (asciis.A - 10);
216
+ if (ch >= asciis.a && ch <= asciis.f)
217
+ return ch - (asciis.a - 10);
218
+ return;
219
+ }
220
+ function hexToBytes(hex) {
221
+ if (typeof hex !== "string")
222
+ throw new Error("hex string expected, got " + typeof hex);
223
+ if (hasHexBuiltin)
224
+ return Uint8Array.fromHex(hex);
225
+ const hl = hex.length;
226
+ const al = hl / 2;
227
+ if (hl % 2)
228
+ throw new Error("hex string expected, got unpadded hex of length " + hl);
229
+ const array = new Uint8Array(al);
230
+ for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
231
+ const n1 = asciiToBase16(hex.charCodeAt(hi));
232
+ const n2 = asciiToBase16(hex.charCodeAt(hi + 1));
233
+ if (n1 === void 0 || n2 === void 0) {
234
+ const char = hex[hi] + hex[hi + 1];
235
+ throw new Error('hex string expected, got non-hex character "' + char + '" at index ' + hi);
236
+ }
237
+ array[ai] = n1 * 16 + n2;
238
+ }
239
+ return array;
240
+ }
241
+
242
+ // src/routes/commitment.ts
243
+ var router3 = (0, import_express3.Router)();
244
+ router3.post(
245
+ "/create",
246
+ validateRequest({ body: schemas.createCommitment }),
247
+ async (req, res) => {
248
+ const { value, blindingFactor } = req.body;
249
+ const valueBigInt = BigInt(value);
250
+ const blindingBytes = blindingFactor ? hexToBytes(blindingFactor) : void 0;
251
+ const result = (0, import_sdk3.commit)(valueBigInt, blindingBytes);
252
+ const response = {
253
+ success: true,
254
+ data: {
255
+ commitment: result.commitment,
256
+ blindingFactor: result.blinding
257
+ }
258
+ };
259
+ res.json(response);
260
+ }
261
+ );
262
+ var commitment_default = router3;
263
+
264
+ // src/routes/proof.ts
265
+ var import_express4 = require("express");
266
+ var import_sdk4 = require("@sip-protocol/sdk");
267
+ var router4 = (0, import_express4.Router)();
268
+ var proofProvider = new import_sdk4.MockProofProvider();
269
+ router4.post(
270
+ "/funding",
271
+ validateRequest({ body: schemas.generateFundingProof }),
272
+ async (req, res) => {
273
+ const { balance, minRequired, balanceBlinding } = req.body;
274
+ const balanceBigInt = BigInt(balance);
275
+ const minRequiredBigInt = BigInt(minRequired);
276
+ const balanceBlindingBytes = hexToBytes(balanceBlinding);
277
+ const result = await proofProvider.generateFundingProof({
278
+ balance: balanceBigInt,
279
+ minimumRequired: minRequiredBigInt,
280
+ blindingFactor: balanceBlindingBytes,
281
+ assetId: "SOL",
282
+ // Default asset
283
+ userAddress: "0x0000000000000000000000000000000000000000",
284
+ ownershipSignature: new Uint8Array(64)
285
+ });
286
+ const response = {
287
+ success: true,
288
+ data: {
289
+ proof: result.proof.proof,
290
+ publicInputs: result.proof.publicInputs
291
+ }
292
+ };
293
+ res.json(response);
294
+ }
295
+ );
296
+ var proof_default = router4;
297
+
298
+ // src/routes/swap.ts
299
+ var import_express5 = require("express");
300
+ var import_sdk5 = require("@sip-protocol/sdk");
301
+ var router5 = (0, import_express5.Router)();
302
+ var sip = new import_sdk5.SIP({ network: "testnet" });
303
+ var swaps = /* @__PURE__ */ new Map();
304
+ router5.post(
305
+ "/",
306
+ validateRequest({ body: schemas.getQuote }),
307
+ async (req, res) => {
308
+ const {
309
+ inputChain,
310
+ inputToken,
311
+ inputAmount,
312
+ outputChain,
313
+ outputToken,
314
+ slippageTolerance
315
+ } = req.body;
316
+ const intent = await sip.createIntent({
317
+ input: {
318
+ asset: {
319
+ chain: inputChain,
320
+ address: null,
321
+ // Native token
322
+ symbol: inputToken,
323
+ decimals: 9
324
+ },
325
+ amount: BigInt(inputAmount)
326
+ },
327
+ output: {
328
+ asset: {
329
+ chain: outputChain,
330
+ address: null,
331
+ // Native token
332
+ symbol: outputToken,
333
+ decimals: 9
334
+ },
335
+ minAmount: BigInt(inputAmount) * 95n / 100n,
336
+ // 5% slippage
337
+ maxSlippage: (slippageTolerance || 1) / 100
338
+ },
339
+ privacy: import_sdk5.PrivacyLevel.TRANSPARENT
340
+ // Default to transparent for quote
341
+ });
342
+ const mockQuote = {
343
+ quoteId: `quote-${Date.now()}`,
344
+ inputAmount,
345
+ outputAmount: (BigInt(inputAmount) * 95n / 100n).toString(),
346
+ // Mock 5% fee
347
+ rate: "0.95",
348
+ estimatedTime: 30,
349
+ fees: {
350
+ network: "0.001",
351
+ protocol: "0.003"
352
+ },
353
+ route: {
354
+ steps: [
355
+ {
356
+ chain: inputChain,
357
+ protocol: "NEAR Intents",
358
+ fromToken: inputToken,
359
+ toToken: outputToken
360
+ }
361
+ ]
362
+ }
363
+ };
364
+ const response = {
365
+ success: true,
366
+ data: mockQuote
367
+ };
368
+ res.json(response);
369
+ }
370
+ );
371
+ router5.post(
372
+ "/swap",
373
+ validateRequest({ body: schemas.executeSwap }),
374
+ async (req, res) => {
375
+ const { intentId, quoteId, privacy, viewingKey } = req.body;
376
+ const swapId = `swap-${Date.now()}`;
377
+ const swap = {
378
+ id: swapId,
379
+ status: "pending",
380
+ inputAmount: "1000000000",
381
+ // Mock value
382
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
383
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
384
+ };
385
+ swaps.set(swapId, swap);
386
+ const response = {
387
+ success: true,
388
+ data: {
389
+ swapId,
390
+ status: "pending",
391
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
392
+ }
393
+ };
394
+ res.json(response);
395
+ }
396
+ );
397
+ router5.get(
398
+ "/:id/status",
399
+ validateRequest({ params: schemas.swapStatus }),
400
+ async (req, res) => {
401
+ const { id } = req.params;
402
+ const swap = swaps.get(id);
403
+ if (!swap) {
404
+ return res.status(404).json({
405
+ success: false,
406
+ error: {
407
+ code: "SWAP_NOT_FOUND",
408
+ message: `Swap ${id} not found`
409
+ }
410
+ });
411
+ }
412
+ const response = {
413
+ success: true,
414
+ data: swap
415
+ };
416
+ res.json(response);
417
+ }
418
+ );
419
+ var swap_default = router5;
420
+
421
+ // src/routes/index.ts
422
+ var router6 = (0, import_express6.Router)();
423
+ router6.use("/health", health_default);
424
+ router6.use("/stealth", stealth_default);
425
+ router6.use("/commitment", commitment_default);
426
+ router6.use("/proof", proof_default);
427
+ router6.use("/quote", swap_default);
428
+ router6.use("/swap", swap_default);
429
+ var routes_default = router6;
430
+
431
+ // src/server.ts
432
+ var app = (0, import_express7.default)();
433
+ var PORT = process.env.PORT || 3e3;
434
+ var NODE_ENV = process.env.NODE_ENV || "development";
435
+ var CORS_ORIGIN = process.env.CORS_ORIGIN || "*";
436
+ app.use((0, import_helmet.default)());
437
+ app.use((0, import_cors.default)({
438
+ origin: CORS_ORIGIN,
439
+ credentials: true
440
+ }));
441
+ app.use(import_express7.default.json({ limit: "1mb" }));
442
+ app.use(import_express7.default.urlencoded({ extended: true, limit: "1mb" }));
443
+ app.use((0, import_compression.default)());
444
+ app.use((0, import_morgan.default)(NODE_ENV === "development" ? "dev" : "combined"));
445
+ app.use("/api/v1", routes_default);
446
+ app.get("/", (req, res) => {
447
+ res.json({
448
+ name: "@sip-protocol/api",
449
+ version: "0.1.0",
450
+ description: "REST API service for SIP Protocol SDK",
451
+ documentation: "/api/v1/health",
452
+ endpoints: {
453
+ health: "GET /api/v1/health",
454
+ stealth: "POST /api/v1/stealth/generate",
455
+ commitment: "POST /api/v1/commitment/create",
456
+ proof: "POST /api/v1/proof/funding",
457
+ quote: "POST /api/v1/quote",
458
+ swap: "POST /api/v1/swap",
459
+ swapStatus: "GET /api/v1/swap/:id/status"
460
+ }
461
+ });
462
+ });
463
+ app.use(notFoundHandler);
464
+ app.use(errorHandler);
465
+ if (require.main === module) {
466
+ app.listen(PORT, () => {
467
+ console.log(`
468
+ \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
469
+ \u2551 SIP Protocol REST API \u2551
470
+ \u2551 Version: 0.1.0 \u2551
471
+ \u2551 Port: ${PORT} \u2551
472
+ \u2551 Environment: ${NODE_ENV} \u2551
473
+ \u2551 Documentation: http://localhost:${PORT}/ \u2551
474
+ \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
475
+ `);
476
+ });
477
+ }
478
+ var server_default = app;
479
+ /*! Bundled license information:
480
+
481
+ @noble/hashes/esm/utils.js:
482
+ (*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
483
+ */
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@sip-protocol/api",
3
+ "version": "0.1.0",
4
+ "description": "REST API service for SIP Protocol SDK - Optional wrapper for non-JS backends",
5
+ "author": "SIP Protocol <hello@sip-protocol.org>",
6
+ "homepage": "https://sip-protocol.org",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/sip-protocol/sip-protocol.git",
10
+ "directory": "packages/api"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/sip-protocol/sip-protocol/issues"
14
+ },
15
+ "main": "./dist/server.js",
16
+ "types": "./dist/server.d.ts",
17
+ "files": [
18
+ "dist",
19
+ "src"
20
+ ],
21
+ "dependencies": {
22
+ "@sip-protocol/sdk": "^0.6.0",
23
+ "@sip-protocol/types": "^0.2.0",
24
+ "compression": "^1.8.1",
25
+ "cors": "^2.8.5",
26
+ "express": "^4.22.1",
27
+ "helmet": "^7.2.0",
28
+ "morgan": "^1.10.1",
29
+ "zod": "^3.25.76"
30
+ },
31
+ "devDependencies": {
32
+ "@types/compression": "^1.8.1",
33
+ "@types/cors": "^2.8.19",
34
+ "@types/express": "^4.17.25",
35
+ "@types/morgan": "^1.9.10",
36
+ "tsup": "^8.5.1",
37
+ "tsx": "^4.21.0",
38
+ "typescript": "^5.3.0",
39
+ "vitest": "^1.1.0"
40
+ },
41
+ "keywords": [
42
+ "sip",
43
+ "privacy",
44
+ "rest-api",
45
+ "stealth-addresses",
46
+ "commitments",
47
+ "api"
48
+ ],
49
+ "license": "MIT",
50
+ "scripts": {
51
+ "start": "node dist/server.js",
52
+ "dev": "tsx watch src/server.ts",
53
+ "build": "tsup src/server.ts src/routes/index.ts --format cjs --dts",
54
+ "lint": "eslint --ext .ts src/",
55
+ "typecheck": "tsc --noEmit",
56
+ "clean": "rm -rf dist",
57
+ "test": "vitest"
58
+ }
59
+ }
@@ -0,0 +1,59 @@
1
+ import { Request, Response, NextFunction } from 'express'
2
+ import { SIPError, isSIPError } from '@sip-protocol/sdk'
3
+
4
+ /**
5
+ * Global error handler middleware
6
+ */
7
+ export function errorHandler(
8
+ err: Error,
9
+ req: Request,
10
+ res: Response,
11
+ next: NextFunction
12
+ ) {
13
+ // Log error for debugging
14
+ console.error('[API Error]', {
15
+ path: req.path,
16
+ method: req.method,
17
+ error: err.message,
18
+ stack: err.stack,
19
+ })
20
+
21
+ // Handle SIP SDK errors
22
+ if (isSIPError(err)) {
23
+ const sipError = err as SIPError
24
+ return res.status(400).json({
25
+ success: false,
26
+ error: {
27
+ code: sipError.code,
28
+ message: sipError.message,
29
+ details: {
30
+ field: (sipError as any).field,
31
+ expected: (sipError as any).expected,
32
+ },
33
+ },
34
+ })
35
+ }
36
+
37
+ // Handle generic errors
38
+ res.status(500).json({
39
+ success: false,
40
+ error: {
41
+ code: 'INTERNAL_SERVER_ERROR',
42
+ message: 'An unexpected error occurred',
43
+ details: process.env.NODE_ENV === 'development' ? err.message : undefined,
44
+ },
45
+ })
46
+ }
47
+
48
+ /**
49
+ * 404 handler
50
+ */
51
+ export function notFoundHandler(req: Request, res: Response) {
52
+ res.status(404).json({
53
+ success: false,
54
+ error: {
55
+ code: 'NOT_FOUND',
56
+ message: `Route ${req.method} ${req.path} not found`,
57
+ },
58
+ })
59
+ }
@@ -0,0 +1,2 @@
1
+ export { errorHandler, notFoundHandler } from './error-handler'
2
+ export { validateRequest, schemas } from './validation'
@@ -0,0 +1,84 @@
1
+ import { Request, Response, NextFunction } from 'express'
2
+ import { z, ZodSchema } from 'zod'
3
+
4
+ /**
5
+ * Zod schema validation middleware factory
6
+ */
7
+ export function validateRequest(schema: {
8
+ body?: ZodSchema
9
+ query?: ZodSchema
10
+ params?: ZodSchema
11
+ }) {
12
+ return async (req: Request, res: Response, next: NextFunction) => {
13
+ try {
14
+ if (schema.body) {
15
+ req.body = await schema.body.parseAsync(req.body)
16
+ }
17
+ if (schema.query) {
18
+ req.query = await schema.query.parseAsync(req.query)
19
+ }
20
+ if (schema.params) {
21
+ req.params = await schema.params.parseAsync(req.params)
22
+ }
23
+ next()
24
+ } catch (error) {
25
+ if (error instanceof z.ZodError) {
26
+ return res.status(400).json({
27
+ success: false,
28
+ error: {
29
+ code: 'VALIDATION_ERROR',
30
+ message: 'Invalid request data',
31
+ details: error.errors,
32
+ },
33
+ })
34
+ }
35
+ next(error)
36
+ }
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Validation schemas
42
+ */
43
+ export const schemas = {
44
+ generateStealth: z.object({
45
+ chain: z.enum(['solana', 'ethereum', 'near', 'zcash', 'polygon', 'arbitrum', 'optimism', 'base', 'bitcoin', 'aptos', 'sui', 'cosmos', 'osmosis', 'injective', 'celestia', 'sei', 'dydx']),
46
+ recipientMetaAddress: z.object({
47
+ spendingKey: z.string().regex(/^0x[0-9a-fA-F]+$/),
48
+ viewingKey: z.string().regex(/^0x[0-9a-fA-F]+$/),
49
+ chain: z.enum(['solana', 'ethereum', 'near', 'zcash', 'polygon', 'arbitrum', 'optimism', 'base', 'bitcoin', 'aptos', 'sui', 'cosmos', 'osmosis', 'injective', 'celestia', 'sei', 'dydx']),
50
+ label: z.string().optional(),
51
+ }),
52
+ }),
53
+
54
+ createCommitment: z.object({
55
+ value: z.string().regex(/^\d+$/), // bigint as string
56
+ blindingFactor: z.string().regex(/^0x[0-9a-fA-F]+$/).optional(),
57
+ }),
58
+
59
+ generateFundingProof: z.object({
60
+ balance: z.string().regex(/^\d+$/),
61
+ minRequired: z.string().regex(/^\d+$/),
62
+ balanceBlinding: z.string().regex(/^0x[0-9a-fA-F]+$/),
63
+ }),
64
+
65
+ getQuote: z.object({
66
+ inputChain: z.enum(['solana', 'ethereum', 'near', 'zcash', 'polygon', 'arbitrum', 'optimism', 'base', 'bitcoin', 'aptos', 'sui', 'cosmos', 'osmosis', 'injective', 'celestia', 'sei', 'dydx']),
67
+ inputToken: z.string().min(1),
68
+ inputAmount: z.string().regex(/^\d+$/),
69
+ outputChain: z.enum(['solana', 'ethereum', 'near', 'zcash', 'polygon', 'arbitrum', 'optimism', 'base', 'bitcoin', 'aptos', 'sui', 'cosmos', 'osmosis', 'injective', 'celestia', 'sei', 'dydx']),
70
+ outputToken: z.string().min(1),
71
+ slippageTolerance: z.number().min(0).max(100).optional(),
72
+ }),
73
+
74
+ executeSwap: z.object({
75
+ intentId: z.string().min(1),
76
+ quoteId: z.string().min(1),
77
+ privacy: z.enum(['transparent', 'shielded', 'compliant']).optional(),
78
+ viewingKey: z.string().regex(/^0x[0-9a-fA-F]+$/).optional(),
79
+ }),
80
+
81
+ swapStatus: z.object({
82
+ id: z.string().min(1),
83
+ }),
84
+ }