@keetanetwork/anchor 0.0.33 → 0.0.35

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 (61) hide show
  1. package/lib/http-server/index.d.ts +7 -1
  2. package/lib/http-server/index.d.ts.map +1 -1
  3. package/lib/http-server/index.js +2 -0
  4. package/lib/http-server/index.js.map +1 -1
  5. package/lib/queue/common.d.ts +26 -0
  6. package/lib/queue/common.d.ts.map +1 -0
  7. package/lib/queue/common.js +47 -0
  8. package/lib/queue/common.js.map +1 -0
  9. package/lib/queue/drivers/queue_file.d.ts +17 -0
  10. package/lib/queue/drivers/queue_file.d.ts.map +1 -0
  11. package/lib/queue/drivers/queue_file.js +100 -0
  12. package/lib/queue/drivers/queue_file.js.map +1 -0
  13. package/lib/queue/drivers/queue_postgres.d.ts +28 -0
  14. package/lib/queue/drivers/queue_postgres.d.ts.map +1 -0
  15. package/lib/queue/drivers/queue_postgres.js +360 -0
  16. package/lib/queue/drivers/queue_postgres.js.map +1 -0
  17. package/lib/queue/drivers/queue_redis.d.ts +27 -0
  18. package/lib/queue/drivers/queue_redis.d.ts.map +1 -0
  19. package/lib/queue/drivers/queue_redis.js +359 -0
  20. package/lib/queue/drivers/queue_redis.js.map +1 -0
  21. package/lib/queue/drivers/queue_sqlite3.d.ts +28 -0
  22. package/lib/queue/drivers/queue_sqlite3.d.ts.map +1 -0
  23. package/lib/queue/drivers/queue_sqlite3.js +378 -0
  24. package/lib/queue/drivers/queue_sqlite3.js.map +1 -0
  25. package/lib/queue/index.d.ts +341 -0
  26. package/lib/queue/index.d.ts.map +1 -0
  27. package/lib/queue/index.js +946 -0
  28. package/lib/queue/index.js.map +1 -0
  29. package/lib/queue/internal.d.ts +20 -0
  30. package/lib/queue/internal.d.ts.map +1 -0
  31. package/lib/queue/internal.js +66 -0
  32. package/lib/queue/internal.js.map +1 -0
  33. package/lib/queue/pipeline.d.ts +152 -0
  34. package/lib/queue/pipeline.d.ts.map +1 -0
  35. package/lib/queue/pipeline.js +296 -0
  36. package/lib/queue/pipeline.js.map +1 -0
  37. package/lib/resolver.d.ts +1 -1
  38. package/lib/resolver.d.ts.map +1 -1
  39. package/lib/resolver.js.map +1 -1
  40. package/lib/utils/asleep.d.ts +2 -0
  41. package/lib/utils/asleep.d.ts.map +1 -0
  42. package/lib/utils/asleep.js +3 -0
  43. package/lib/utils/asleep.js.map +1 -0
  44. package/lib/utils/defer.d.ts +4 -0
  45. package/lib/utils/defer.d.ts.map +1 -0
  46. package/lib/utils/defer.js +3 -0
  47. package/lib/utils/defer.js.map +1 -0
  48. package/npm-shrinkwrap.json +2 -2
  49. package/package.json +1 -1
  50. package/services/fx/client.d.ts +1 -1
  51. package/services/fx/client.d.ts.map +1 -1
  52. package/services/fx/client.js +2 -2
  53. package/services/fx/client.js.map +1 -1
  54. package/services/fx/common.d.ts +19 -4
  55. package/services/fx/common.d.ts.map +1 -1
  56. package/services/fx/common.js +8 -5
  57. package/services/fx/common.js.map +1 -1
  58. package/services/fx/server.d.ts +105 -8
  59. package/services/fx/server.d.ts.map +1 -1
  60. package/services/fx/server.js +609 -43
  61. package/services/fx/server.js.map +1 -1
@@ -1,8 +1,24 @@
1
+ import * as __typia_transform__assertGuard from "typia/lib/internal/_assertGuard.js";
1
2
  import * as KeetaAnchorHTTPServer from '../../lib/http-server/index.js';
2
3
  import { KeetaNet } from '../../client/index.js';
3
4
  import { KeetaAnchorUserError } from '../../lib/error.js';
4
5
  import { assertConversionInputCanonicalJSON, assertConversionQuoteJSON, Errors } from './common.js';
5
6
  import * as Signing from '../../lib/utils/signing.js';
7
+ import { KeetaAnchorQueueRunner, KeetaAnchorQueueStorageDriverMemory } from '../../lib/queue/index.js';
8
+ import { KeetaAnchorQueuePipelineAdvanced } from '../../lib/queue/pipeline.js';
9
+ import { assertNever } from '../../lib/utils/never.js';
10
+ import * as typia from 'typia';
11
+ /**
12
+ * Enable additional runtime "paranoid" checks in the FX server.
13
+ *
14
+ * This may have a small performance impact but increases safety
15
+ * by ensuring that the accounts used in quotes are actually
16
+ * configured in the server.
17
+ *
18
+ * During the transition to multiple accounts this may help catch
19
+ * misconfigurations so it is enabled by default for now.
20
+ */
21
+ const PARANOID = true;
6
22
  ;
7
23
  async function formatQuoteSignable(unsignedQuote) {
8
24
  const retval = [
@@ -30,40 +46,421 @@ async function verifySignedData(signedBy, quote) {
30
46
  return (await Signing.VerifySignedData(signedBy, signableQuote, quote.signed));
31
47
  }
32
48
  async function requestToAccounts(config, request) {
33
- const account = KeetaNet.lib.Account.isInstance(config.account) ? config.account : await config.account(request);
49
+ let account;
50
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
51
+ if (config.account !== undefined) {
52
+ const rateFee = await config.fx.getConversionRateAndFee(request);
53
+ account = rateFee.account;
54
+ }
55
+ else {
56
+ account = null;
57
+ }
34
58
  let signer = null;
35
59
  if (config.signer !== undefined) {
36
60
  signer = (KeetaNet.lib.Account.isInstance(config.signer) ? config.signer : await config.signer(request)).assertAccount();
37
61
  }
38
- if (signer === null) {
39
- signer = account.assertAccount();
62
+ if (account !== null) {
63
+ if (signer === null) {
64
+ signer = account.assertAccount();
65
+ }
66
+ if (!account.isAccount() && !account.isStorage()) {
67
+ throw (new Error('FX Account should be an Account or Storage Account'));
68
+ }
40
69
  }
41
- if (!account.isAccount() && !account.isStorage()) {
42
- throw (new Error('FX Account should be an Account or Storage Account'));
70
+ else if (signer === null) {
71
+ throw (new Error('Either account or signer must be provided'));
43
72
  }
44
73
  return ({
45
- signer: signer,
46
- account: account
74
+ account: account,
75
+ signer: signer
47
76
  });
48
77
  }
78
+ class KeetaFXAnchorQueuePipelineStage1 extends KeetaAnchorQueueRunner {
79
+ serverConfig;
80
+ sequential = true;
81
+ /**
82
+ * Timeout for processing a single job -- if exceeded the job is marked as aborted
83
+ */
84
+ processTimeout = 60 * 1000;
85
+ constructor(config) {
86
+ super(config);
87
+ this.serverConfig = config.serverConfig;
88
+ this.processorAborted = this.processorStuck.bind(this);
89
+ }
90
+ /**
91
+ * Handles both stuck (no status update after a long period) and
92
+ * aborted (timeout while processing an entry) states.
93
+ *
94
+ * We just put the job back into pending because the processor
95
+ * will check the network state again.
96
+ */
97
+ async processorStuck(entry) {
98
+ return ({
99
+ status: 'pending',
100
+ output: entry.output
101
+ });
102
+ }
103
+ /**
104
+ * Process the entry, attempting to submit the swap block(s)
105
+ * to the network. Verifies the block can be submitted before
106
+ * attempting submission. Also verifies if the block is already
107
+ * on the network and marks the job as completed if so.
108
+ */
109
+ async processor(entry) {
110
+ const { block, expected, request } = entry.request;
111
+ const expectedToken = expected.token;
112
+ const expectedAmount = expected.amount;
113
+ const config = this.serverConfig;
114
+ let userClient;
115
+ if (KeetaNet.UserClient.isInstance(config.client)) {
116
+ userClient = config.client;
117
+ }
118
+ else {
119
+ const { signer, account: checkAccount } = await requestToAccounts(config, request);
120
+ if (checkAccount === null) {
121
+ if (this.serverConfig.accounts === undefined) {
122
+ throw (new Error('No accounts configured for FX server'));
123
+ }
124
+ if (!this.serverConfig.accounts.has(entry.request.account)) {
125
+ return ({
126
+ status: 'failed_permanently',
127
+ output: null,
128
+ error: `Mismatched account for FX request and configured account (no matching account found)`
129
+ });
130
+ }
131
+ }
132
+ else if (!checkAccount.comparePublicKey(entry.request.account)) {
133
+ return ({
134
+ status: 'failed_permanently',
135
+ output: null,
136
+ error: `Mismatched account for FX request and configured account (single account not matched)`
137
+ });
138
+ }
139
+ userClient = new KeetaNet.UserClient({
140
+ client: config.client.client,
141
+ network: config.client.network,
142
+ networkAlias: config.client.networkAlias,
143
+ account: entry.request.account,
144
+ signer: signer
145
+ });
146
+ }
147
+ /* Check for the block already being on the network, if so we can mark this job as completed */
148
+ const blockExists = await userClient.block(block.hash);
149
+ if (blockExists !== null) {
150
+ const existingOutput = entry.output;
151
+ const blocks = existingOutput?.blocks ?? [Buffer.from(block.toBytes()).toString('base64')];
152
+ return ({
153
+ status: 'completed',
154
+ output: {
155
+ blockhash: block.hash.toString(),
156
+ blocks: blocks
157
+ }
158
+ });
159
+ }
160
+ /* Get the current head block of the account in the block */
161
+ const accountHead = await userClient.head({ account: block.account });
162
+ let isHeadBlock = false;
163
+ if (accountHead !== null) {
164
+ isHeadBlock = accountHead.compare(block.previous);
165
+ }
166
+ else {
167
+ isHeadBlock = block['$opening'];
168
+ }
169
+ /* If the account's head block is not the block's previous, see if the block's previous exists on the network */
170
+ if (!isHeadBlock) {
171
+ let previousBlockOnNetwork = false;
172
+ if (block['$opening']) {
173
+ /* Opening block means that there is no previous block, so we are free to proceed */
174
+ previousBlockOnNetwork = true;
175
+ }
176
+ else {
177
+ const previousBlock = await userClient.block(block.previous);
178
+ if (previousBlock !== null) {
179
+ previousBlockOnNetwork = true;
180
+ }
181
+ }
182
+ let blockPreviousString;
183
+ if (block['$opening']) {
184
+ blockPreviousString = '<opening>';
185
+ }
186
+ else {
187
+ blockPreviousString = block.previous.toString();
188
+ }
189
+ /* If block.previous exists on the network, and it's not the account head block we can mark this job as failed_permanently */
190
+ if (previousBlockOnNetwork) {
191
+ /* Block's previous exists on the network, but is not the account head -- mark as failed_permanently */
192
+ return ({
193
+ status: 'failed_permanently',
194
+ output: null,
195
+ error: `Block previous (${blockPreviousString}) exists on the network but is not the account head`
196
+ });
197
+ }
198
+ else {
199
+ /* If block.previous does not exist on the network we can mark this job as failed_temporarily -- it can't process until the missing block is added */
200
+ /* Block's previous does not exist on the network -- mark as failed_temporarily */
201
+ return ({
202
+ status: 'failed_temporarily',
203
+ output: null,
204
+ error: `Block previous (${blockPreviousString}) does not exist on the network`
205
+ });
206
+ }
207
+ }
208
+ /* We are clear to attempt the swap now */
209
+ const swapBlocks = await userClient.acceptSwapRequest({ block, expected: { token: expectedToken, amount: BigInt(expectedAmount) } });
210
+ const publishOptions = {};
211
+ if (userClient.config.generateFeeBlock !== undefined) {
212
+ publishOptions.generateFeeBlock = userClient.config.generateFeeBlock;
213
+ }
214
+ const publishResult = await userClient.client.transmit(swapBlocks, publishOptions);
215
+ if (!publishResult.publish) {
216
+ throw (new Error('Exchange Publish Failed'));
217
+ }
218
+ /* Set the output and mark the job as pending so we can run the queue again and check for completion */
219
+ return ({
220
+ status: 'pending',
221
+ output: {
222
+ blockhash: block.hash.toString(),
223
+ blocks: swapBlocks.map(function (block) {
224
+ return (Buffer.from(block.toBytes()).toString('base64'));
225
+ })
226
+ }
227
+ });
228
+ }
229
+ encodeRequest(request) {
230
+ const retval = {
231
+ version: 1,
232
+ account: request.account.publicKeyString.get(),
233
+ block: Buffer.from(request.block.toBytes()).toString('base64'),
234
+ request: request.request,
235
+ expected: {
236
+ token: request.expected.token.publicKeyString.get(),
237
+ amount: request.expected.amount.toString()
238
+ }
239
+ };
240
+ return (retval);
241
+ }
242
+ encodeResponse(response) {
243
+ return (response);
244
+ }
245
+ decodeRequest(request) {
246
+ /* See note at bottom of file */
247
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
248
+ const reqJSON = assertKeetaFXAnchorQueueStage1RequestJSON(request);
249
+ if (reqJSON.version !== 1) {
250
+ throw (new Error(`Unsupported KeetaFXAnchorQueueStage1Request version ${reqJSON.version}`));
251
+ }
252
+ const retval = {
253
+ account: KeetaNet.lib.Account.fromPublicKeyString(reqJSON.account),
254
+ block: new KeetaNet.lib.Block(reqJSON.block),
255
+ request: reqJSON.request,
256
+ expected: {
257
+ token: KeetaNet.lib.Account.fromPublicKeyString(reqJSON.expected.token).assertKeyType(KeetaNet.lib.Account.AccountKeyAlgorithm.TOKEN),
258
+ amount: BigInt(reqJSON.expected.amount)
259
+ }
260
+ };
261
+ return (retval);
262
+ }
263
+ decodeResponse(response) {
264
+ /* See note at bottom of file */
265
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
266
+ return (assertKeetaFXAnchorQueueStage1ResponseOrNull(response));
267
+ }
268
+ }
269
+ class KeetaFXAnchorQueuePipeline extends KeetaAnchorQueuePipelineAdvanced {
270
+ serverConfig;
271
+ accounts;
272
+ runners = {};
273
+ constructor(options) {
274
+ super(options);
275
+ this.serverConfig = options.serverConfig;
276
+ this.accounts = options.accounts;
277
+ }
278
+ async createPipeline() {
279
+ for (const account of this.accounts) {
280
+ const queue = await this.baseQueue.partition(account.publicKeyAndTypeString);
281
+ this.queues.push(queue);
282
+ const runner = new KeetaFXAnchorQueuePipelineStage1({
283
+ id: `keeta-fx-anchor-runner-${account.publicKeyAndTypeString}`,
284
+ queue: queue,
285
+ logger: this.logger,
286
+ serverConfig: this.serverConfig
287
+ });
288
+ this.runners[account.publicKeyAndTypeString] = runner;
289
+ }
290
+ }
291
+ getStage(_ignore_stageID) {
292
+ throw (new Error('method not supported'));
293
+ }
294
+ async add(request) {
295
+ await super.init();
296
+ const account = request.account.publicKeyAndTypeString;
297
+ const runner = this.runners[account];
298
+ if (runner === undefined) {
299
+ throw (new Error(`No queue runner for account ${account}`));
300
+ }
301
+ return (await runner.add(request));
302
+ }
303
+ async get(id) {
304
+ await super.init();
305
+ for (const account of this.accounts) {
306
+ const runner = this.runners[account.publicKeyAndTypeString];
307
+ if (runner === undefined) {
308
+ continue;
309
+ }
310
+ const entry = await runner.get(id);
311
+ if (entry !== null) {
312
+ return (entry);
313
+ }
314
+ }
315
+ return (null);
316
+ }
317
+ async run(options) {
318
+ await super.init();
319
+ let retval = false;
320
+ for (const account of this.accounts) {
321
+ const runner = this.runners[account.publicKeyAndTypeString];
322
+ if (runner === undefined) {
323
+ continue;
324
+ }
325
+ const more = await runner.run(options);
326
+ if (more) {
327
+ retval = true;
328
+ }
329
+ }
330
+ return (retval);
331
+ }
332
+ async maintain() {
333
+ await super.init();
334
+ for (const account of this.accounts) {
335
+ const runner = this.runners[account.publicKeyAndTypeString];
336
+ if (runner === undefined) {
337
+ continue;
338
+ }
339
+ await runner.maintain();
340
+ }
341
+ }
342
+ async destroy() {
343
+ const logger = this.methodLogger('destroy');
344
+ if (this.destroyed) {
345
+ return;
346
+ }
347
+ this.destroyed = true;
348
+ await super.destroy();
349
+ for (const runner of Object.values(this.runners)) {
350
+ try {
351
+ await runner.destroy();
352
+ }
353
+ catch (error) {
354
+ logger?.error('Error destroying runner:', error);
355
+ }
356
+ }
357
+ }
358
+ }
49
359
  export class KeetaNetFXAnchorHTTPServer extends KeetaAnchorHTTPServer.KeetaNetAnchorHTTPServer {
50
360
  homepage;
51
361
  client;
52
- account;
362
+ accounts;
363
+ account = undefined;
53
364
  signer;
54
365
  quoteSigner;
55
366
  fx;
367
+ pipeline;
368
+ pipelineAutoRunInterval = null;
56
369
  constructor(config) {
57
370
  super(config);
58
371
  this.homepage = config.homepage ?? '';
59
372
  this.client = config.client;
60
373
  this.fx = config.fx;
61
- this.account = config.account;
62
- this.signer = config.signer ?? config.account;
63
374
  this.quoteSigner = config.quoteSigner;
375
+ /*
376
+ * Setup the accounts
377
+ */
378
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
379
+ if (config.account !== undefined && config.accounts === undefined) {
380
+ /*
381
+ * Deprecated: If a single account is provided, use that
382
+ * along with the signer to create the accounts set
383
+ */
384
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
385
+ this.accounts = new KeetaNet.lib.Account.Set([config.account]);
386
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
387
+ this.signer = config.signer ?? config.account;
388
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
389
+ }
390
+ else if (config.accounts !== undefined && config.account === undefined) {
391
+ /*
392
+ * Only allow either "account" or "accounts"+"signer" to be provided
393
+ */
394
+ if (config.signer === undefined) {
395
+ throw (new Error('If "accounts" is provided, "signer" must also be provided'));
396
+ }
397
+ this.accounts = config.accounts;
398
+ this.signer = config.signer;
399
+ }
400
+ else {
401
+ throw (new Error('Either "account" (and optional "signer") or "accounts" and "signer" must be provided, but not both "account" and "accounts"'));
402
+ }
403
+ if (this.accounts.size === 0) {
404
+ throw (new Error('No FX accounts provided'));
405
+ }
406
+ /*
407
+ * If no storage driver is provided, we default to an in-memory
408
+ * that we auto-run
409
+ */
410
+ let autorun = config.storage?.autoRun ?? false;
411
+ if (config.storage === undefined) {
412
+ autorun = true;
413
+ }
414
+ /*
415
+ * Create the pipeline to process transactions
416
+ */
417
+ this.pipeline = new KeetaFXAnchorQueuePipeline({
418
+ id: 'keeta-fx-anchor-queue-pipeline',
419
+ baseQueue: config.storage?.queue ?? new KeetaAnchorQueueStorageDriverMemory({
420
+ id: 'keeta-fx-anchor-queue-pipeline-memory-driver',
421
+ logger: this.logger
422
+ }),
423
+ accounts: this.accounts,
424
+ logger: this.logger,
425
+ serverConfig: this
426
+ });
427
+ /*
428
+ * If auto-run is enabled, setup the interval to run the pipeline
429
+ */
430
+ if (autorun) {
431
+ let running = false;
432
+ this.pipelineAutoRunInterval = setInterval(async () => {
433
+ if (running) {
434
+ return;
435
+ }
436
+ running = true;
437
+ try {
438
+ await this.pipeline.maintain();
439
+ }
440
+ catch (error) {
441
+ this.logger.error('KeetaNetFXAnchorHTTPServer::pipelineAutoRunInterval', 'Error maintaining pipeline:', error);
442
+ }
443
+ try {
444
+ await this.pipeline.run({ timeoutMs: 5000 });
445
+ }
446
+ catch (error) {
447
+ this.logger.error('KeetaNetFXAnchorHTTPServer::pipelineAutoRunInterval', 'Error running pipeline:', error);
448
+ }
449
+ finally {
450
+ running = false;
451
+ }
452
+ }, 1000);
453
+ }
64
454
  }
65
455
  async initRoutes(config) {
66
456
  const routes = {};
457
+ const logger = this.logger;
458
+ /*
459
+ * To use the instance within the route handlers, we need to
460
+ * make a local reference to it.
461
+ */
462
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
463
+ const instance = this;
67
464
  /**
68
465
  * If a homepage is provided, setup the route for it
69
466
  */
@@ -122,6 +519,12 @@ export class KeetaNetFXAnchorHTTPServer extends KeetaAnchorHTTPServer.KeetaNetAn
122
519
  }
123
520
  const conversion = assertConversionInputCanonicalJSON(postData.request);
124
521
  const rateAndFee = await config.fx.getConversionRateAndFee(conversion);
522
+ if (PARANOID) {
523
+ const quoteAccount = rateAndFee.account;
524
+ if (!instance.accounts.has(quoteAccount)) {
525
+ throw (new Error('"getConversionRateAndFee" returned an account not configured for this server'));
526
+ }
527
+ }
125
528
  const unsignedQuote = KeetaNet.lib.Utils.Conversion.toJSONSerializable({
126
529
  request: conversion,
127
530
  ...rateAndFee
@@ -165,20 +568,6 @@ export class KeetaNetFXAnchorHTTPServer extends KeetaAnchorHTTPServer.KeetaNetAn
165
568
  }
166
569
  }
167
570
  const block = new KeetaNet.lib.Block(request.block);
168
- let userClient;
169
- if (KeetaNet.UserClient.isInstance(config.client)) {
170
- userClient = config.client;
171
- }
172
- else {
173
- const { account, signer } = await requestToAccounts(config, quote.request);
174
- userClient = new KeetaNet.UserClient({
175
- client: config.client.client,
176
- network: config.client.network,
177
- networkAlias: config.client.networkAlias,
178
- account: account,
179
- signer: signer
180
- });
181
- }
182
571
  /* Get Expected Amount and Token to Verify Swap */
183
572
  const expectedToken = KeetaNet.lib.Account.fromPublicKeyString(quote.request.from);
184
573
  let expectedAmount = quote.request.affinity === 'from' ? BigInt(quote.request.amount) : BigInt(quote.convertedAmount);
@@ -206,19 +595,20 @@ export class KeetaNetFXAnchorHTTPServer extends KeetaAnchorHTTPServer.KeetaNetAn
206
595
  }
207
596
  }
208
597
  }
209
- /* Verify Request and Generate the Accept Swap Block */
210
- const swapBlocks = await userClient.acceptSwapRequest({ block, expected: { token: expectedToken, amount: BigInt(expectedAmount) } });
211
- const publishOptions = {};
212
- if (userClient.config.generateFeeBlock !== undefined) {
213
- publishOptions.generateFeeBlock = userClient.config.generateFeeBlock;
214
- }
215
- const publishResult = await userClient.client.transmit(swapBlocks, publishOptions);
216
- if (!publishResult.publish) {
217
- throw (new Error('Exchange Publish Failed'));
218
- }
598
+ /* Enqueue the exchange request */
599
+ const exchangeID = await instance.pipeline.add({
600
+ account: KeetaNet.lib.Account.fromPublicKeyString(quote.account),
601
+ block: block,
602
+ request: quote.request,
603
+ expected: {
604
+ token: expectedToken,
605
+ amount: BigInt(expectedAmount)
606
+ }
607
+ });
219
608
  const exchangeResponse = {
220
609
  ok: true,
221
- exchangeID: block.hash.toString()
610
+ exchangeID: exchangeID.toString(),
611
+ status: 'pending'
222
612
  };
223
613
  return ({
224
614
  output: JSON.stringify(exchangeResponse)
@@ -232,14 +622,57 @@ export class KeetaNetFXAnchorHTTPServer extends KeetaAnchorHTTPServer.KeetaNetAn
232
622
  if (typeof exchangeID !== 'string') {
233
623
  throw (new Error('Missing exchangeID in params'));
234
624
  }
235
- const blockLookup = await config.client.client.getVoteStaple(exchangeID);
236
- if (blockLookup === null) {
237
- throw (new Error('Block Not Found'));
238
- }
239
- const exchangeResponse = {
240
- ok: true,
241
- exchangeID: exchangeID
242
- };
625
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
626
+ const queueEntryInfo = await instance.pipeline.get(exchangeID);
627
+ const exchangeResponse = (function () {
628
+ const inputStatus = queueEntryInfo?.status;
629
+ switch (inputStatus) {
630
+ case undefined:
631
+ throw (new KeetaAnchorUserError('Exchange ID not found'));
632
+ case 'pending':
633
+ case 'processing':
634
+ case 'stuck':
635
+ case 'aborted':
636
+ case 'failed_temporarily':
637
+ return ({
638
+ ok: true,
639
+ status: 'pending',
640
+ exchangeID: exchangeID
641
+ });
642
+ case 'completed': {
643
+ const blockhash = queueEntryInfo?.output?.blockhash;
644
+ if (blockhash === undefined) {
645
+ return ({
646
+ ok: true,
647
+ status: 'pending',
648
+ exchangeID: exchangeID
649
+ });
650
+ }
651
+ else {
652
+ return ({
653
+ ok: true,
654
+ status: 'completed',
655
+ exchangeID: exchangeID,
656
+ blockhash: blockhash
657
+ });
658
+ }
659
+ }
660
+ case 'failed_permanently':
661
+ return ({
662
+ ok: false,
663
+ exchangeID: exchangeID,
664
+ status: 'failed',
665
+ error: 'Exchange failed'
666
+ });
667
+ case 'moved':
668
+ throw (new Error('Exchange ID has been moved'));
669
+ case '@internal':
670
+ throw (new Error('Invalid exchange status @internal'));
671
+ default:
672
+ assertNever(inputStatus);
673
+ }
674
+ })();
675
+ logger.debug('GET /api/getExchangeStatus/:id', 'Exchange Status for ID', exchangeID, 'is', exchangeResponse, 'based on jobInfo', queueEntryInfo);
243
676
  return ({
244
677
  output: JSON.stringify(exchangeResponse)
245
678
  });
@@ -261,5 +694,138 @@ export class KeetaNetFXAnchorHTTPServer extends KeetaAnchorHTTPServer.KeetaNetAn
261
694
  operations: operations
262
695
  });
263
696
  }
697
+ async stop() {
698
+ if (this.pipelineAutoRunInterval !== null) {
699
+ clearInterval(this.pipelineAutoRunInterval);
700
+ this.pipelineAutoRunInterval = null;
701
+ }
702
+ await this.pipeline.destroy();
703
+ await super.stop();
704
+ }
264
705
  }
706
+ /*
707
+ * These are placed at the bottom of the file because the generation
708
+ * breaks the code coverage computation; Normally, we'd place these
709
+ * in a ".generated.ts" file but for simplicity of internal types
710
+ * we keep them here.
711
+ */
712
+ const assertKeetaFXAnchorQueueStage1RequestJSON = (() => { const _io0 = input => 1 === input.version && "string" === typeof input.account && "string" === typeof input.block && ("object" === typeof input.request && null !== input.request && _io1(input.request)) && ("object" === typeof input.expected && null !== input.expected && _io2(input.expected)); const _io1 = 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 _io2 = input => "string" === typeof input.token && "string" === typeof input.amount; const _ao0 = (input, _path, _exceptionable = true) => (1 === input.version || __typia_transform__assertGuard._assertGuard(_exceptionable, {
713
+ method: "typia.createAssert",
714
+ path: _path + ".version",
715
+ expected: "1",
716
+ value: input.version
717
+ }, _errorFactory)) && ("string" === typeof input.account || __typia_transform__assertGuard._assertGuard(_exceptionable, {
718
+ method: "typia.createAssert",
719
+ path: _path + ".account",
720
+ expected: "string",
721
+ value: input.account
722
+ }, _errorFactory)) && ("string" === typeof input.block || __typia_transform__assertGuard._assertGuard(_exceptionable, {
723
+ method: "typia.createAssert",
724
+ path: _path + ".block",
725
+ expected: "string",
726
+ value: input.block
727
+ }, _errorFactory)) && (("object" === typeof input.request && null !== input.request || __typia_transform__assertGuard._assertGuard(_exceptionable, {
728
+ method: "typia.createAssert",
729
+ path: _path + ".request",
730
+ expected: "__type",
731
+ value: input.request
732
+ }, _errorFactory)) && _ao1(input.request, _path + ".request", true && _exceptionable) || __typia_transform__assertGuard._assertGuard(_exceptionable, {
733
+ method: "typia.createAssert",
734
+ path: _path + ".request",
735
+ expected: "__type",
736
+ value: input.request
737
+ }, _errorFactory)) && (("object" === typeof input.expected && null !== input.expected || __typia_transform__assertGuard._assertGuard(_exceptionable, {
738
+ method: "typia.createAssert",
739
+ path: _path + ".expected",
740
+ expected: "__type.o1",
741
+ value: input.expected
742
+ }, _errorFactory)) && _ao2(input.expected, _path + ".expected", true && _exceptionable) || __typia_transform__assertGuard._assertGuard(_exceptionable, {
743
+ method: "typia.createAssert",
744
+ path: _path + ".expected",
745
+ expected: "__type.o1",
746
+ value: input.expected
747
+ }, _errorFactory)); const _ao1 = (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, {
748
+ method: "typia.createAssert",
749
+ path: _path + ".from",
750
+ expected: "(`keeta_am${string}` | `keeta_an${string}` | `keeta_ao${string}` | `keeta_ap${string}` | `tyblocks_am${string}` | `tyblocks_an${string}` | `tyblocks_ao${string}` | `tyblocks_ap${string}`)",
751
+ value: input.from
752
+ }, _errorFactory)) && ("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)) || __typia_transform__assertGuard._assertGuard(_exceptionable, {
753
+ method: "typia.createAssert",
754
+ path: _path + ".to",
755
+ expected: "(`keeta_am${string}` | `keeta_an${string}` | `keeta_ao${string}` | `keeta_ap${string}` | `tyblocks_am${string}` | `tyblocks_an${string}` | `tyblocks_ao${string}` | `tyblocks_ap${string}`)",
756
+ value: input.to
757
+ }, _errorFactory)) && ("string" === typeof input.amount || __typia_transform__assertGuard._assertGuard(_exceptionable, {
758
+ method: "typia.createAssert",
759
+ path: _path + ".amount",
760
+ expected: "string",
761
+ value: input.amount
762
+ }, _errorFactory)) && ("from" === input.affinity || "to" === input.affinity || __typia_transform__assertGuard._assertGuard(_exceptionable, {
763
+ method: "typia.createAssert",
764
+ path: _path + ".affinity",
765
+ expected: "(\"from\" | \"to\")",
766
+ value: input.affinity
767
+ }, _errorFactory)); const _ao2 = (input, _path, _exceptionable = true) => ("string" === typeof input.token || __typia_transform__assertGuard._assertGuard(_exceptionable, {
768
+ method: "typia.createAssert",
769
+ path: _path + ".token",
770
+ expected: "string",
771
+ value: input.token
772
+ }, _errorFactory)) && ("string" === typeof input.amount || __typia_transform__assertGuard._assertGuard(_exceptionable, {
773
+ method: "typia.createAssert",
774
+ path: _path + ".amount",
775
+ expected: "string",
776
+ value: input.amount
777
+ }, _errorFactory)); const __is = input => "object" === typeof input && null !== input && _io0(input); let _errorFactory; return (input, errorFactory) => {
778
+ if (false === __is(input)) {
779
+ _errorFactory = errorFactory;
780
+ ((input, _path, _exceptionable = true) => ("object" === typeof input && null !== input || __typia_transform__assertGuard._assertGuard(true, {
781
+ method: "typia.createAssert",
782
+ path: _path + "",
783
+ expected: "KeetaFXAnchorQueueStage1RequestJSON",
784
+ value: input
785
+ }, _errorFactory)) && _ao0(input, _path + "", true) || __typia_transform__assertGuard._assertGuard(true, {
786
+ method: "typia.createAssert",
787
+ path: _path + "",
788
+ expected: "KeetaFXAnchorQueueStage1RequestJSON",
789
+ value: input
790
+ }, _errorFactory))(input, "$input", true);
791
+ }
792
+ return input;
793
+ }; })();
794
+ const assertKeetaFXAnchorQueueStage1ResponseOrNull = (() => { const _io0 = input => Array.isArray(input.blocks) && input.blocks.every(elem => "string" === typeof elem) && "string" === typeof input.blockhash; const _ao0 = (input, _path, _exceptionable = true) => ((Array.isArray(input.blocks) || __typia_transform__assertGuard._assertGuard(_exceptionable, {
795
+ method: "typia.createAssert",
796
+ path: _path + ".blocks",
797
+ expected: "Array<string>",
798
+ value: input.blocks
799
+ }, _errorFactory)) && input.blocks.every((elem, _index2) => "string" === typeof elem || __typia_transform__assertGuard._assertGuard(_exceptionable, {
800
+ method: "typia.createAssert",
801
+ path: _path + ".blocks[" + _index2 + "]",
802
+ expected: "string",
803
+ value: elem
804
+ }, _errorFactory)) || __typia_transform__assertGuard._assertGuard(_exceptionable, {
805
+ method: "typia.createAssert",
806
+ path: _path + ".blocks",
807
+ expected: "Array<string>",
808
+ value: input.blocks
809
+ }, _errorFactory)) && ("string" === typeof input.blockhash || __typia_transform__assertGuard._assertGuard(_exceptionable, {
810
+ method: "typia.createAssert",
811
+ path: _path + ".blockhash",
812
+ expected: "string",
813
+ value: input.blockhash
814
+ }, _errorFactory)); const __is = input => null === input || "object" === typeof input && null !== input && _io0(input); let _errorFactory; return (input, errorFactory) => {
815
+ if (false === __is(input)) {
816
+ _errorFactory = errorFactory;
817
+ ((input, _path, _exceptionable = true) => null === input || ("object" === typeof input && null !== input || __typia_transform__assertGuard._assertGuard(true, {
818
+ method: "typia.createAssert",
819
+ path: _path + "",
820
+ expected: "(KeetaFXAnchorQueueStage1Response | null)",
821
+ value: input
822
+ }, _errorFactory)) && _ao0(input, _path + "", true) || __typia_transform__assertGuard._assertGuard(true, {
823
+ method: "typia.createAssert",
824
+ path: _path + "",
825
+ expected: "(KeetaFXAnchorQueueStage1Response | null)",
826
+ value: input
827
+ }, _errorFactory))(input, "$input", true);
828
+ }
829
+ return input;
830
+ }; })();
265
831
  //# sourceMappingURL=server.js.map