@keetanetwork/anchor 0.0.5 → 0.0.6
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/lib/http-server.d.ts +59 -0
- package/lib/http-server.d.ts.map +1 -0
- package/lib/http-server.js +383 -0
- package/lib/http-server.js.map +1 -0
- package/npm-shrinkwrap.json +526 -16214
- package/package.json +31 -42
- package/services/fx/server.d.ts +4 -23
- package/services/fx/server.d.ts.map +1 -1
- package/services/fx/server.js +131 -475
- package/services/fx/server.js.map +1 -1
package/services/fx/server.js
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
import * as __typia_transform__assertGuard from "typia/lib/internal/_assertGuard.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as KeetaAnchorHTTPServer from '../../lib/http-server.js';
|
|
3
3
|
import KeetaNet from '@keetanetwork/keetanet-client';
|
|
4
4
|
import { createAssert } from 'typia';
|
|
5
5
|
import { KeetaAnchorUserError } from '../../lib/error.js';
|
|
6
6
|
import { acceptSwapRequest } from './common.js';
|
|
7
7
|
import * as Signing from '../../lib/utils/signing.js';
|
|
8
|
-
import { Log } from '../../lib/log/index.js';
|
|
9
|
-
/**
|
|
10
|
-
* The maximum size of a request (128KiB)
|
|
11
|
-
*/
|
|
12
|
-
const MAX_REQUEST_SIZE = 1024 * 128;
|
|
13
8
|
const assertConversionInputCanonical = (() => { const _io0 = input => "string" === typeof input.from && (RegExp(/^keeta_am(.*)/).test(input.from) || RegExp(/^keeta_an(.*)/).test(input.from) || RegExp(/^keeta_ao(.*)/).test(input.from) || RegExp(/^keeta_ap(.*)/).test(input.from) || RegExp(/^tyblocks_am(.*)/).test(input.from) || RegExp(/^tyblocks_an(.*)/).test(input.from) || RegExp(/^tyblocks_ao(.*)/).test(input.from) || RegExp(/^tyblocks_ap(.*)/).test(input.from)) && ("string" === typeof input.to && (RegExp(/^keeta_am(.*)/).test(input.to) || RegExp(/^keeta_an(.*)/).test(input.to) || RegExp(/^keeta_ao(.*)/).test(input.to) || RegExp(/^keeta_ap(.*)/).test(input.to) || RegExp(/^tyblocks_am(.*)/).test(input.to) || RegExp(/^tyblocks_an(.*)/).test(input.to) || RegExp(/^tyblocks_ao(.*)/).test(input.to) || RegExp(/^tyblocks_ap(.*)/).test(input.to))) && "string" === typeof input.amount && ("from" === input.affinity || "to" === input.affinity); const _ao0 = (input, _path, _exceptionable = true) => ("string" === typeof input.from && (RegExp(/^keeta_am(.*)/).test(input.from) || RegExp(/^keeta_an(.*)/).test(input.from) || RegExp(/^keeta_ao(.*)/).test(input.from) || RegExp(/^keeta_ap(.*)/).test(input.from) || RegExp(/^tyblocks_am(.*)/).test(input.from) || RegExp(/^tyblocks_an(.*)/).test(input.from) || RegExp(/^tyblocks_ao(.*)/).test(input.from) || RegExp(/^tyblocks_ap(.*)/).test(input.from)) || __typia_transform__assertGuard._assertGuard(_exceptionable, {
|
|
14
9
|
method: "createAssert",
|
|
15
10
|
path: _path + ".from",
|
|
@@ -149,38 +144,6 @@ const assertConversionQuote = (() => { const _io0 = input => "object" === typeof
|
|
|
149
144
|
}
|
|
150
145
|
return input;
|
|
151
146
|
}; })();
|
|
152
|
-
const assertErrorData = (() => { const _io0 = input => "string" === typeof input.error && (undefined === input.statusCode || "number" === typeof input.statusCode) && (undefined === input.contentType || "string" === typeof input.contentType); const _ao0 = (input, _path, _exceptionable = true) => ("string" === typeof input.error || __typia_transform__assertGuard._assertGuard(_exceptionable, {
|
|
153
|
-
method: "createAssert",
|
|
154
|
-
path: _path + ".error",
|
|
155
|
-
expected: "string",
|
|
156
|
-
value: input.error
|
|
157
|
-
}, _errorFactory)) && (undefined === input.statusCode || "number" === typeof input.statusCode || __typia_transform__assertGuard._assertGuard(_exceptionable, {
|
|
158
|
-
method: "createAssert",
|
|
159
|
-
path: _path + ".statusCode",
|
|
160
|
-
expected: "(number | undefined)",
|
|
161
|
-
value: input.statusCode
|
|
162
|
-
}, _errorFactory)) && (undefined === input.contentType || "string" === typeof input.contentType || __typia_transform__assertGuard._assertGuard(_exceptionable, {
|
|
163
|
-
method: "createAssert",
|
|
164
|
-
path: _path + ".contentType",
|
|
165
|
-
expected: "(string | undefined)",
|
|
166
|
-
value: input.contentType
|
|
167
|
-
}, _errorFactory)); const __is = input => "object" === typeof input && null !== input && _io0(input); let _errorFactory; return (input, errorFactory) => {
|
|
168
|
-
if (false === __is(input)) {
|
|
169
|
-
_errorFactory = errorFactory;
|
|
170
|
-
((input, _path, _exceptionable = true) => ("object" === typeof input && null !== input || __typia_transform__assertGuard._assertGuard(true, {
|
|
171
|
-
method: "createAssert",
|
|
172
|
-
path: _path + "",
|
|
173
|
-
expected: "__type",
|
|
174
|
-
value: input
|
|
175
|
-
}, _errorFactory)) && _ao0(input, _path + "", true) || __typia_transform__assertGuard._assertGuard(true, {
|
|
176
|
-
method: "createAssert",
|
|
177
|
-
path: _path + "",
|
|
178
|
-
expected: "__type",
|
|
179
|
-
value: input
|
|
180
|
-
}, _errorFactory))(input, "$input", true);
|
|
181
|
-
}
|
|
182
|
-
return input;
|
|
183
|
-
}; })();
|
|
184
147
|
;
|
|
185
148
|
async function formatQuoteSignable(unsignedQuote) {
|
|
186
149
|
const retval = [
|
|
@@ -218,476 +181,169 @@ async function requestToAccounts(config, request) {
|
|
|
218
181
|
account: account.assertAccount()
|
|
219
182
|
});
|
|
220
183
|
}
|
|
221
|
-
|
|
222
|
-
const routes = {};
|
|
223
|
-
/**
|
|
224
|
-
* If a homepage is provided, setup the route for it
|
|
225
|
-
*/
|
|
226
|
-
if ('homepage' in config) {
|
|
227
|
-
routes['GET /'] = async function () {
|
|
228
|
-
let homepageData;
|
|
229
|
-
if (typeof config.homepage === 'string') {
|
|
230
|
-
homepageData = config.homepage;
|
|
231
|
-
}
|
|
232
|
-
else {
|
|
233
|
-
if (!config.homepage) {
|
|
234
|
-
throw (new Error('internal error: No homepage function provided'));
|
|
235
|
-
}
|
|
236
|
-
homepageData = await config.homepage();
|
|
237
|
-
}
|
|
238
|
-
return ({
|
|
239
|
-
output: homepageData,
|
|
240
|
-
contentType: 'text/html'
|
|
241
|
-
});
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
/**
|
|
245
|
-
* Setup the request handler for an estimate request
|
|
246
|
-
*/
|
|
247
|
-
routes['POST /api/getEstimate'] = async function (_ignore_params, postData) {
|
|
248
|
-
if (!postData || typeof postData !== 'object') {
|
|
249
|
-
throw (new Error('No POST data provided'));
|
|
250
|
-
}
|
|
251
|
-
if (!('request' in postData)) {
|
|
252
|
-
throw (new Error('POST data missing request'));
|
|
253
|
-
}
|
|
254
|
-
const conversion = assertConversionInputCanonical(postData.request);
|
|
255
|
-
const rateAndFee = await config.fx.getConversionRateAndFee(conversion);
|
|
256
|
-
const estimateResponse = {
|
|
257
|
-
ok: true,
|
|
258
|
-
estimate: {
|
|
259
|
-
request: conversion,
|
|
260
|
-
convertedAmount: rateAndFee.convertedAmount,
|
|
261
|
-
expectedCost: {
|
|
262
|
-
min: rateAndFee.cost.amount,
|
|
263
|
-
max: rateAndFee.cost.amount,
|
|
264
|
-
token: rateAndFee.cost.token
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
};
|
|
268
|
-
return ({
|
|
269
|
-
output: JSON.stringify(estimateResponse)
|
|
270
|
-
});
|
|
271
|
-
};
|
|
272
|
-
routes['POST /api/getQuote'] = async function (_ignore_params, postData) {
|
|
273
|
-
if (!postData || typeof postData !== 'object') {
|
|
274
|
-
throw (new Error('No POST data provided'));
|
|
275
|
-
}
|
|
276
|
-
if (!('request' in postData)) {
|
|
277
|
-
throw (new Error('POST data missing request'));
|
|
278
|
-
}
|
|
279
|
-
const conversion = assertConversionInputCanonical(postData.request);
|
|
280
|
-
const rateAndFee = await config.fx.getConversionRateAndFee(conversion);
|
|
281
|
-
const unsignedQuote = {
|
|
282
|
-
request: conversion,
|
|
283
|
-
...rateAndFee
|
|
284
|
-
};
|
|
285
|
-
const signedQuote = await generateSignedQuote(config.quoteSigner, unsignedQuote);
|
|
286
|
-
const quoteResponse = {
|
|
287
|
-
ok: true,
|
|
288
|
-
quote: signedQuote
|
|
289
|
-
};
|
|
290
|
-
return ({
|
|
291
|
-
output: JSON.stringify(quoteResponse)
|
|
292
|
-
});
|
|
293
|
-
};
|
|
294
|
-
routes['POST /api/createExchange'] = async function (_ignore_params, postData) {
|
|
295
|
-
if (!postData || typeof postData !== 'object') {
|
|
296
|
-
throw (new Error('No POST data provided'));
|
|
297
|
-
}
|
|
298
|
-
if (!('request' in postData)) {
|
|
299
|
-
throw (new Error('POST data missing request'));
|
|
300
|
-
}
|
|
301
|
-
const request = postData.request;
|
|
302
|
-
if (!request || typeof request !== 'object') {
|
|
303
|
-
throw (new Error('Request is not an object'));
|
|
304
|
-
}
|
|
305
|
-
if (!('quote' in request)) {
|
|
306
|
-
throw (new Error('Quote is missing from request'));
|
|
307
|
-
}
|
|
308
|
-
if (!('block' in request) || typeof request.block !== 'string') {
|
|
309
|
-
throw (new Error('Block was not provided in exchange request'));
|
|
310
|
-
}
|
|
311
|
-
const quote = assertConversionQuote(request.quote);
|
|
312
|
-
const isValidQuote = await verifySignedData(config.quoteSigner, quote);
|
|
313
|
-
if (!isValidQuote) {
|
|
314
|
-
throw (new Error('Invalid quote signature'));
|
|
315
|
-
}
|
|
316
|
-
const block = new KeetaNet.lib.Block(request.block);
|
|
317
|
-
let userClient;
|
|
318
|
-
if (KeetaNet.UserClient.isInstance(config.client)) {
|
|
319
|
-
userClient = config.client;
|
|
320
|
-
}
|
|
321
|
-
else {
|
|
322
|
-
const { account, signer } = await requestToAccounts(config, quote.request);
|
|
323
|
-
userClient = new KeetaNet.UserClient({
|
|
324
|
-
client: config.client.client,
|
|
325
|
-
network: config.client.network,
|
|
326
|
-
networkAlias: config.client.networkAlias,
|
|
327
|
-
account: account,
|
|
328
|
-
signer: signer
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
|
-
const expectedToken = KeetaNet.lib.Account.fromPublicKeyString(quote.request.from);
|
|
332
|
-
const expectedAmount = quote.request.affinity === 'from' ? quote.request.amount : quote.convertedAmount;
|
|
333
|
-
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
334
|
-
const swapBlocks = await acceptSwapRequest(userClient, block, { token: expectedToken, amount: BigInt(expectedAmount) });
|
|
335
|
-
const publishResult = await userClient.client.transmit(swapBlocks);
|
|
336
|
-
if (!publishResult.publish) {
|
|
337
|
-
throw (new Error('Exchange Publish Failed'));
|
|
338
|
-
}
|
|
339
|
-
const exchangeResponse = {
|
|
340
|
-
ok: true,
|
|
341
|
-
exchangeID: block.hash.toString()
|
|
342
|
-
};
|
|
343
|
-
return ({
|
|
344
|
-
output: JSON.stringify(exchangeResponse)
|
|
345
|
-
});
|
|
346
|
-
};
|
|
347
|
-
routes['GET /api/getExchangeStatus/:id'] = async function (params) {
|
|
348
|
-
if (params === undefined || params === null) {
|
|
349
|
-
throw (new KeetaAnchorUserError('Expected params'));
|
|
350
|
-
}
|
|
351
|
-
const exchangeID = params.get('id');
|
|
352
|
-
if (typeof exchangeID !== 'string') {
|
|
353
|
-
throw (new Error('Missing exchangeID in params'));
|
|
354
|
-
}
|
|
355
|
-
const blockLookup = await config.client.client.getVoteStaple(exchangeID);
|
|
356
|
-
if (blockLookup === null) {
|
|
357
|
-
throw (new Error('Block Not Found'));
|
|
358
|
-
}
|
|
359
|
-
const exchangeResponse = {
|
|
360
|
-
ok: true,
|
|
361
|
-
exchangeID: exchangeID
|
|
362
|
-
};
|
|
363
|
-
return ({
|
|
364
|
-
output: JSON.stringify(exchangeResponse)
|
|
365
|
-
});
|
|
366
|
-
};
|
|
367
|
-
routes['ERROR'] = async function (_ignore_params, postData) {
|
|
368
|
-
const errorInfo = assertErrorData(postData);
|
|
369
|
-
const retval = {
|
|
370
|
-
output: errorInfo.error,
|
|
371
|
-
statusCode: errorInfo.statusCode ?? 400,
|
|
372
|
-
contentType: errorInfo.contentType ?? 'text/plain'
|
|
373
|
-
};
|
|
374
|
-
return (retval);
|
|
375
|
-
};
|
|
376
|
-
return (routes);
|
|
377
|
-
}
|
|
378
|
-
export class KeetaNetFXAnchorHTTPServer {
|
|
379
|
-
port;
|
|
184
|
+
export class KeetaNetFXAnchorHTTPServer extends KeetaAnchorHTTPServer.KeetaNetAnchorHTTPServer {
|
|
380
185
|
homepage;
|
|
381
186
|
client;
|
|
382
|
-
logger;
|
|
383
187
|
account;
|
|
384
188
|
signer;
|
|
385
189
|
quoteSigner;
|
|
386
190
|
fx;
|
|
387
|
-
#serverPromise;
|
|
388
|
-
#server;
|
|
389
191
|
constructor(config) {
|
|
192
|
+
super(config);
|
|
390
193
|
this.homepage = config.homepage ?? '';
|
|
391
|
-
this.port = config.port ?? 0;
|
|
392
194
|
this.client = config.client;
|
|
393
195
|
this.fx = config.fx;
|
|
394
196
|
this.account = config.account;
|
|
395
197
|
this.signer = config.signer ?? config.account;
|
|
396
198
|
this.quoteSigner = config.quoteSigner;
|
|
397
|
-
this.logger = config.logger ?? new Log();
|
|
398
|
-
}
|
|
399
|
-
static routeMatch(requestURL, routeURL) {
|
|
400
|
-
const requestURLPaths = requestURL.pathname.split('/');
|
|
401
|
-
const routeURLPaths = routeURL.pathname.split('/');
|
|
402
|
-
if (requestURLPaths.length !== routeURLPaths.length) {
|
|
403
|
-
return ({ match: false });
|
|
404
|
-
}
|
|
405
|
-
const params = new Map();
|
|
406
|
-
for (let partIndex = 0; partIndex < requestURLPaths.length; partIndex++) {
|
|
407
|
-
const requestPath = requestURLPaths[partIndex];
|
|
408
|
-
const routePath = routeURLPaths[partIndex];
|
|
409
|
-
if (routePath === undefined || requestPath === undefined) {
|
|
410
|
-
return ({ match: false });
|
|
411
|
-
}
|
|
412
|
-
if (routePath.startsWith(':')) {
|
|
413
|
-
params.set(routePath.slice(1), requestPath);
|
|
414
|
-
}
|
|
415
|
-
else if (requestPath !== routePath) {
|
|
416
|
-
return ({ match: false });
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
return ({ match: true, params: params });
|
|
420
199
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
200
|
+
async initRoutes(config) {
|
|
201
|
+
const routes = {};
|
|
202
|
+
/**
|
|
203
|
+
* If a homepage is provided, setup the route for it
|
|
204
|
+
*/
|
|
205
|
+
if ('homepage' in config) {
|
|
206
|
+
routes['GET /'] = async function () {
|
|
207
|
+
let homepageData;
|
|
208
|
+
if (typeof config.homepage === 'string') {
|
|
209
|
+
homepageData = config.homepage;
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
if (!config.homepage) {
|
|
213
|
+
throw (new Error('internal error: No homepage function provided'));
|
|
214
|
+
}
|
|
215
|
+
homepageData = await config.homepage();
|
|
216
|
+
}
|
|
435
217
|
return ({
|
|
436
|
-
|
|
437
|
-
|
|
218
|
+
output: homepageData,
|
|
219
|
+
contentType: 'text/html'
|
|
438
220
|
});
|
|
439
|
-
}
|
|
221
|
+
};
|
|
440
222
|
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
for (const routeKey in routes) {
|
|
448
|
-
const methodAndPath = routeKey.split(' ');
|
|
449
|
-
const method = methodAndPath[0];
|
|
450
|
-
const path = methodAndPath.slice(1).join(' ');
|
|
451
|
-
if (method === undefined || path === undefined) {
|
|
452
|
-
continue;
|
|
223
|
+
/**
|
|
224
|
+
* Setup the request handler for an estimate request
|
|
225
|
+
*/
|
|
226
|
+
routes['POST /api/getEstimate'] = async function (_ignore_params, postData) {
|
|
227
|
+
if (!postData || typeof postData !== 'object') {
|
|
228
|
+
throw (new Error('No POST data provided'));
|
|
453
229
|
}
|
|
454
|
-
if (!
|
|
455
|
-
|
|
230
|
+
if (!('request' in postData)) {
|
|
231
|
+
throw (new Error('POST data missing request'));
|
|
456
232
|
}
|
|
457
|
-
|
|
458
|
-
|
|
233
|
+
const conversion = assertConversionInputCanonical(postData.request);
|
|
234
|
+
const rateAndFee = await config.fx.getConversionRateAndFee(conversion);
|
|
235
|
+
const estimateResponse = {
|
|
236
|
+
ok: true,
|
|
237
|
+
estimate: {
|
|
238
|
+
request: conversion,
|
|
239
|
+
convertedAmount: rateAndFee.convertedAmount,
|
|
240
|
+
expectedCost: {
|
|
241
|
+
min: rateAndFee.cost.amount,
|
|
242
|
+
max: rateAndFee.cost.amount,
|
|
243
|
+
token: rateAndFee.cost.token
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
return ({
|
|
248
|
+
output: JSON.stringify(estimateResponse)
|
|
249
|
+
});
|
|
250
|
+
};
|
|
251
|
+
routes['POST /api/getQuote'] = async function (_ignore_params, postData) {
|
|
252
|
+
if (!postData || typeof postData !== 'object') {
|
|
253
|
+
throw (new Error('No POST data provided'));
|
|
459
254
|
}
|
|
460
|
-
if (
|
|
461
|
-
throw (new Error(
|
|
255
|
+
if (!('request' in postData)) {
|
|
256
|
+
throw (new Error('POST data missing request'));
|
|
462
257
|
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
const
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
|
|
258
|
+
const conversion = assertConversionInputCanonical(postData.request);
|
|
259
|
+
const rateAndFee = await config.fx.getConversionRateAndFee(conversion);
|
|
260
|
+
const unsignedQuote = {
|
|
261
|
+
request: conversion,
|
|
262
|
+
...rateAndFee
|
|
263
|
+
};
|
|
264
|
+
const signedQuote = await generateSignedQuote(config.quoteSigner, unsignedQuote);
|
|
265
|
+
const quoteResponse = {
|
|
266
|
+
ok: true,
|
|
267
|
+
quote: signedQuote
|
|
268
|
+
};
|
|
269
|
+
return ({
|
|
270
|
+
output: JSON.stringify(quoteResponse)
|
|
271
|
+
});
|
|
272
|
+
};
|
|
273
|
+
routes['POST /api/createExchange'] = async function (_ignore_params, postData) {
|
|
274
|
+
if (!postData || typeof postData !== 'object') {
|
|
275
|
+
throw (new Error('No POST data provided'));
|
|
473
276
|
}
|
|
474
|
-
if (
|
|
475
|
-
|
|
476
|
-
newRoutes[routeKey] = routeHandler;
|
|
477
|
-
continue;
|
|
478
|
-
}
|
|
479
|
-
if (!validMethods.has(method)) {
|
|
480
|
-
newRoutes[routeKey] = routeHandler;
|
|
481
|
-
continue;
|
|
482
|
-
}
|
|
277
|
+
if (!('request' in postData)) {
|
|
278
|
+
throw (new Error('POST data missing request'));
|
|
483
279
|
}
|
|
484
|
-
const
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
validMethodsForPath.add('OPTIONS');
|
|
488
|
-
validMethodsForPathParts = Array.from(validMethodsForPath);
|
|
280
|
+
const request = postData.request;
|
|
281
|
+
if (!request || typeof request !== 'object') {
|
|
282
|
+
throw (new Error('Request is not an object'));
|
|
489
283
|
}
|
|
490
|
-
|
|
491
|
-
|
|
284
|
+
if (!('quote' in request)) {
|
|
285
|
+
throw (new Error('Quote is missing from request'));
|
|
492
286
|
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
/* Add CORS headers to the response for the original route handler */
|
|
496
|
-
if (retval.contentType === 'application/json' || retval.contentType === undefined) {
|
|
497
|
-
if (!('headers' in retval) || retval.headers === undefined) {
|
|
498
|
-
retval.headers = {};
|
|
499
|
-
}
|
|
500
|
-
retval.headers['Access-Control-Allow-Origin'] = '*';
|
|
501
|
-
}
|
|
502
|
-
return (retval);
|
|
503
|
-
};
|
|
504
|
-
if (!seenPaths.has(path) && path !== '' && path !== undefined) {
|
|
505
|
-
const corsRouteKey = `OPTIONS ${path}`;
|
|
506
|
-
newRoutes[corsRouteKey] = async function () {
|
|
507
|
-
return ({
|
|
508
|
-
output: '',
|
|
509
|
-
statusCode: 204,
|
|
510
|
-
contentType: 'text/plain',
|
|
511
|
-
headers: {
|
|
512
|
-
'Access-Control-Allow-Origin': '*',
|
|
513
|
-
'Access-Control-Allow-Methods': validMethodsForPathParts.join(', '),
|
|
514
|
-
'Access-Control-Allow-Headers': 'Content-Type',
|
|
515
|
-
'Access-Control-Max-Age': '86400'
|
|
516
|
-
}
|
|
517
|
-
});
|
|
518
|
-
};
|
|
519
|
-
seenPaths.add(path);
|
|
287
|
+
if (!('block' in request) || typeof request.block !== 'string') {
|
|
288
|
+
throw (new Error('Block was not provided in exchange request'));
|
|
520
289
|
}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
this.logger?.debug('KeetaAnchorFX.Server', 'Starting HTTP server...');
|
|
526
|
-
const port = this.port;
|
|
527
|
-
const routes = KeetaNetFXAnchorHTTPServer.addCORS(await initRoutes(this));
|
|
528
|
-
const server = new http.Server(async (request, response) => {
|
|
529
|
-
const url = new URL(request.url ?? '/', `http://${request.headers.host ?? 'localhost'}`);
|
|
530
|
-
const method = request.method ?? 'GET';
|
|
531
|
-
/*
|
|
532
|
-
* Lookup the route based on the request
|
|
533
|
-
*/
|
|
534
|
-
const requestedRouteAndParams = KeetaNetFXAnchorHTTPServer.routeFind(method, url, routes);
|
|
535
|
-
if (requestedRouteAndParams === null) {
|
|
536
|
-
response.statusCode = 404;
|
|
537
|
-
response.setHeader('Content-Type', 'text/plain');
|
|
538
|
-
response.write('Not Found');
|
|
539
|
-
response.end();
|
|
540
|
-
return;
|
|
290
|
+
const quote = assertConversionQuote(request.quote);
|
|
291
|
+
const isValidQuote = await verifySignedData(config.quoteSigner, quote);
|
|
292
|
+
if (!isValidQuote) {
|
|
293
|
+
throw (new Error('Invalid quote signature'));
|
|
541
294
|
}
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
const { route, params } = requestedRouteAndParams;
|
|
547
|
-
/**
|
|
548
|
-
* Attempt to run the route, catch any errors
|
|
549
|
-
*/
|
|
550
|
-
let result, generatedResult = false;
|
|
551
|
-
try {
|
|
552
|
-
/**
|
|
553
|
-
* If POST'ing, read and parse the POST data
|
|
554
|
-
*/
|
|
555
|
-
let postData;
|
|
556
|
-
if (request.method === 'POST') {
|
|
557
|
-
const data = await request.map(function (chunk) {
|
|
558
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
559
|
-
return (Buffer.from(chunk));
|
|
560
|
-
}).reduce(function (prev, curr) {
|
|
561
|
-
if (prev.length > MAX_REQUEST_SIZE) {
|
|
562
|
-
throw (new Error('Request too large'));
|
|
563
|
-
}
|
|
564
|
-
if (!Buffer.isBuffer(curr)) {
|
|
565
|
-
throw (new Error(`internal error: Current item is not a buffer -- ${typeof curr}`));
|
|
566
|
-
}
|
|
567
|
-
return (Buffer.concat([prev, curr]));
|
|
568
|
-
}, Buffer.from(''));
|
|
569
|
-
if (request.headers['content-type'] === 'application/json') {
|
|
570
|
-
try {
|
|
571
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
572
|
-
postData = JSON.parse(data.toString('utf-8'));
|
|
573
|
-
}
|
|
574
|
-
catch {
|
|
575
|
-
throw (new Error('Invalid JSON data'));
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
else {
|
|
579
|
-
throw (new KeetaAnchorUserError('Unsupported content type'));
|
|
580
|
-
}
|
|
581
|
-
/**
|
|
582
|
-
* Call the route handler
|
|
583
|
-
*/
|
|
584
|
-
result = await route(params, postData);
|
|
585
|
-
}
|
|
586
|
-
else {
|
|
587
|
-
result = await route(params, undefined);
|
|
588
|
-
}
|
|
589
|
-
generatedResult = true;
|
|
590
|
-
}
|
|
591
|
-
catch (err) {
|
|
592
|
-
/**
|
|
593
|
-
* If an error occurs, log it and return an error page
|
|
594
|
-
*/
|
|
595
|
-
this.logger?.error('KeetaAnchorFX.Server', err);
|
|
596
|
-
/**
|
|
597
|
-
* If it is a user error, provide a user-friendly error page
|
|
598
|
-
*/
|
|
599
|
-
const errorHandlerRoute = routes['ERROR'];
|
|
600
|
-
if (errorHandlerRoute !== undefined) {
|
|
601
|
-
if (KeetaAnchorUserError.isInstance(err)) {
|
|
602
|
-
result = await errorHandlerRoute(new Map(), err.asErrorResponse('application/json'));
|
|
603
|
-
generatedResult = true;
|
|
604
|
-
}
|
|
605
|
-
else {
|
|
606
|
-
result = await errorHandlerRoute(new Map(), {
|
|
607
|
-
error: JSON.stringify({ ok: false, error: 'Internal Server Error' }),
|
|
608
|
-
statusCode: 500,
|
|
609
|
-
contentType: 'application/json'
|
|
610
|
-
});
|
|
611
|
-
generatedResult = true;
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
if (!generatedResult) {
|
|
615
|
-
/**
|
|
616
|
-
* Otherwise provide a generic error page
|
|
617
|
-
*/
|
|
618
|
-
response.statusCode = 500;
|
|
619
|
-
response.setHeader('Content-Type', 'text/plain');
|
|
620
|
-
response.write('Internal Server Error');
|
|
621
|
-
response.end();
|
|
622
|
-
return;
|
|
623
|
-
}
|
|
295
|
+
const block = new KeetaNet.lib.Block(request.block);
|
|
296
|
+
let userClient;
|
|
297
|
+
if (KeetaNet.UserClient.isInstance(config.client)) {
|
|
298
|
+
userClient = config.client;
|
|
624
299
|
}
|
|
625
|
-
|
|
626
|
-
|
|
300
|
+
else {
|
|
301
|
+
const { account, signer } = await requestToAccounts(config, quote.request);
|
|
302
|
+
userClient = new KeetaNet.UserClient({
|
|
303
|
+
client: config.client.client,
|
|
304
|
+
network: config.client.network,
|
|
305
|
+
networkAlias: config.client.networkAlias,
|
|
306
|
+
account: account,
|
|
307
|
+
signer: signer
|
|
308
|
+
});
|
|
627
309
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
response.setHeader(headerKey, headerValue);
|
|
636
|
-
}
|
|
310
|
+
const expectedToken = KeetaNet.lib.Account.fromPublicKeyString(quote.request.from);
|
|
311
|
+
const expectedAmount = quote.request.affinity === 'from' ? quote.request.amount : quote.convertedAmount;
|
|
312
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
313
|
+
const swapBlocks = await acceptSwapRequest(userClient, block, { token: expectedToken, amount: BigInt(expectedAmount) });
|
|
314
|
+
const publishResult = await userClient.client.transmit(swapBlocks);
|
|
315
|
+
if (!publishResult.publish) {
|
|
316
|
+
throw (new Error('Exchange Publish Failed'));
|
|
637
317
|
}
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
* Create a promise to wait for the server to close
|
|
645
|
-
*/
|
|
646
|
-
const waiter = new Promise((resolve) => {
|
|
647
|
-
server.on('close', () => {
|
|
648
|
-
this.logger?.debug('KeetaAnchorFX.Server', 'Server closed');
|
|
649
|
-
resolve();
|
|
318
|
+
const exchangeResponse = {
|
|
319
|
+
ok: true,
|
|
320
|
+
exchangeID: block.hash.toString()
|
|
321
|
+
};
|
|
322
|
+
return ({
|
|
323
|
+
output: JSON.stringify(exchangeResponse)
|
|
650
324
|
});
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
server.listen(port, () => {
|
|
656
|
-
const address = server.address();
|
|
657
|
-
if (address !== null && typeof address === 'object') {
|
|
658
|
-
// @ts-ignore
|
|
659
|
-
this.port = address.port;
|
|
660
|
-
onSetPort?.(this.port);
|
|
325
|
+
};
|
|
326
|
+
routes['GET /api/getExchangeStatus/:id'] = async function (params) {
|
|
327
|
+
if (params === undefined || params === null) {
|
|
328
|
+
throw (new KeetaAnchorUserError('Expected params'));
|
|
661
329
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
330
|
+
const exchangeID = params.get('id');
|
|
331
|
+
if (typeof exchangeID !== 'string') {
|
|
332
|
+
throw (new Error('Missing exchangeID in params'));
|
|
333
|
+
}
|
|
334
|
+
const blockLookup = await config.client.client.getVoteStaple(exchangeID);
|
|
335
|
+
if (blockLookup === null) {
|
|
336
|
+
throw (new Error('Block Not Found'));
|
|
337
|
+
}
|
|
338
|
+
const exchangeResponse = {
|
|
339
|
+
ok: true,
|
|
340
|
+
exchangeID: exchangeID
|
|
341
|
+
};
|
|
342
|
+
return ({
|
|
343
|
+
output: JSON.stringify(exchangeResponse)
|
|
673
344
|
});
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
async wait() {
|
|
677
|
-
await this.#serverPromise;
|
|
678
|
-
}
|
|
679
|
-
async stop() {
|
|
680
|
-
this.#server?.close();
|
|
681
|
-
await this.wait();
|
|
682
|
-
}
|
|
683
|
-
get url() {
|
|
684
|
-
if (this.port === 0 || this.#server === undefined) {
|
|
685
|
-
throw (new Error('Server not started'));
|
|
686
|
-
}
|
|
687
|
-
return (`http://localhost:${this.port}`);
|
|
688
|
-
}
|
|
689
|
-
[Symbol.asyncDispose]() {
|
|
690
|
-
return (this.stop());
|
|
345
|
+
};
|
|
346
|
+
return (routes);
|
|
691
347
|
}
|
|
692
348
|
}
|
|
693
349
|
//# sourceMappingURL=server.js.map
|