@keetanetwork/anchor 0.0.2 → 0.0.5

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 (45) hide show
  1. package/client/index.d.ts +6 -0
  2. package/client/index.d.ts.map +1 -1
  3. package/client/index.js +7 -0
  4. package/client/index.js.map +1 -1
  5. package/lib/error.d.ts +14 -0
  6. package/lib/error.d.ts.map +1 -0
  7. package/lib/error.js +38 -0
  8. package/lib/error.js.map +1 -0
  9. package/lib/resolver.d.ts +159 -60
  10. package/lib/resolver.d.ts.map +1 -1
  11. package/lib/resolver.js +3519 -384
  12. package/lib/resolver.js.map +1 -1
  13. package/lib/utils/brand.d.ts +12 -0
  14. package/lib/utils/brand.d.ts.map +1 -0
  15. package/lib/utils/brand.js +2 -0
  16. package/lib/utils/brand.js.map +1 -0
  17. package/lib/utils/never.d.ts +4 -0
  18. package/lib/utils/never.d.ts.map +1 -1
  19. package/lib/utils/never.js.map +1 -1
  20. package/lib/utils/signing.d.ts +17 -0
  21. package/lib/utils/signing.d.ts.map +1 -0
  22. package/lib/utils/signing.js +73 -0
  23. package/lib/utils/signing.js.map +1 -0
  24. package/lib/utils/url.d.ts +2 -0
  25. package/lib/utils/url.d.ts.map +1 -0
  26. package/lib/utils/url.js +8 -0
  27. package/lib/utils/url.js.map +1 -0
  28. package/npm-shrinkwrap.json +21 -14
  29. package/package.json +6 -4
  30. package/services/fx/client.d.ts +137 -0
  31. package/services/fx/client.d.ts.map +1 -0
  32. package/services/fx/client.js +519 -0
  33. package/services/fx/client.js.map +1 -0
  34. package/services/fx/common.d.ts +133 -0
  35. package/services/fx/common.d.ts.map +1 -0
  36. package/services/fx/common.js +45 -0
  37. package/services/fx/common.js.map +1 -0
  38. package/services/fx/server.d.ts +77 -0
  39. package/services/fx/server.d.ts.map +1 -0
  40. package/services/fx/server.js +693 -0
  41. package/services/fx/server.js.map +1 -0
  42. package/services/kyc/client.d.ts +1 -1
  43. package/services/kyc/client.d.ts.map +1 -1
  44. package/services/kyc/client.js +7 -53
  45. package/services/kyc/client.js.map +1 -1
@@ -0,0 +1,693 @@
1
+ import * as __typia_transform__assertGuard from "typia/lib/internal/_assertGuard.js";
2
+ import * as http from 'http';
3
+ import KeetaNet from '@keetanetwork/keetanet-client';
4
+ import { createAssert } from 'typia';
5
+ import { KeetaAnchorUserError } from '../../lib/error.js';
6
+ import { acceptSwapRequest } from './common.js';
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
+ 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
+ method: "createAssert",
15
+ path: _path + ".from",
16
+ 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}`)",
17
+ value: input.from
18
+ }, _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, {
19
+ method: "createAssert",
20
+ path: _path + ".to",
21
+ 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}`)",
22
+ value: input.to
23
+ }, _errorFactory)) && ("string" === typeof input.amount || __typia_transform__assertGuard._assertGuard(_exceptionable, {
24
+ method: "createAssert",
25
+ path: _path + ".amount",
26
+ expected: "string",
27
+ value: input.amount
28
+ }, _errorFactory)) && ("from" === input.affinity || "to" === input.affinity || __typia_transform__assertGuard._assertGuard(_exceptionable, {
29
+ method: "createAssert",
30
+ path: _path + ".affinity",
31
+ expected: "(\"from\" | \"to\")",
32
+ value: input.affinity
33
+ }, _errorFactory)); const __is = input => "object" === typeof input && null !== input && _io0(input); let _errorFactory; return (input, errorFactory) => {
34
+ if (false === __is(input)) {
35
+ _errorFactory = errorFactory;
36
+ ((input, _path, _exceptionable = true) => ("object" === typeof input && null !== input || __typia_transform__assertGuard._assertGuard(true, {
37
+ method: "createAssert",
38
+ path: _path + "",
39
+ expected: "ConversionInputCanonical",
40
+ value: input
41
+ }, _errorFactory)) && _ao0(input, _path + "", true) || __typia_transform__assertGuard._assertGuard(true, {
42
+ method: "createAssert",
43
+ path: _path + "",
44
+ expected: "ConversionInputCanonical",
45
+ value: input
46
+ }, _errorFactory))(input, "$input", true);
47
+ }
48
+ return input;
49
+ }; })();
50
+ const assertConversionQuote = (() => { const _io0 = input => "object" === typeof input.request && null !== input.request && _io1(input.request) && "string" === typeof input.account && "string" === typeof input.convertedAmount && ("object" === typeof input.cost && null !== input.cost && _io2(input.cost)) && ("object" === typeof input.signed && null !== input.signed && _io3(input.signed)); 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.amount && ("string" === typeof input.token && (RegExp(/^keeta_am(.*)/).test(input.token) || RegExp(/^keeta_an(.*)/).test(input.token) || RegExp(/^keeta_ao(.*)/).test(input.token) || RegExp(/^keeta_ap(.*)/).test(input.token) || RegExp(/^tyblocks_am(.*)/).test(input.token) || RegExp(/^tyblocks_an(.*)/).test(input.token) || RegExp(/^tyblocks_ao(.*)/).test(input.token) || RegExp(/^tyblocks_ap(.*)/).test(input.token))); const _io3 = input => "string" === typeof input.nonce && "string" === typeof input.timestamp && "string" === typeof input.signature; const _ao0 = (input, _path, _exceptionable = true) => (("object" === typeof input.request && null !== input.request || __typia_transform__assertGuard._assertGuard(_exceptionable, {
51
+ method: "createAssert",
52
+ path: _path + ".request",
53
+ expected: "ConversionInputCanonical",
54
+ value: input.request
55
+ }, _errorFactory)) && _ao1(input.request, _path + ".request", true && _exceptionable) || __typia_transform__assertGuard._assertGuard(_exceptionable, {
56
+ method: "createAssert",
57
+ path: _path + ".request",
58
+ expected: "ConversionInputCanonical",
59
+ value: input.request
60
+ }, _errorFactory)) && ("string" === typeof input.account || __typia_transform__assertGuard._assertGuard(_exceptionable, {
61
+ method: "createAssert",
62
+ path: _path + ".account",
63
+ expected: "string",
64
+ value: input.account
65
+ }, _errorFactory)) && ("string" === typeof input.convertedAmount || __typia_transform__assertGuard._assertGuard(_exceptionable, {
66
+ method: "createAssert",
67
+ path: _path + ".convertedAmount",
68
+ expected: "string",
69
+ value: input.convertedAmount
70
+ }, _errorFactory)) && (("object" === typeof input.cost && null !== input.cost || __typia_transform__assertGuard._assertGuard(_exceptionable, {
71
+ method: "createAssert",
72
+ path: _path + ".cost",
73
+ expected: "__type",
74
+ value: input.cost
75
+ }, _errorFactory)) && _ao2(input.cost, _path + ".cost", true && _exceptionable) || __typia_transform__assertGuard._assertGuard(_exceptionable, {
76
+ method: "createAssert",
77
+ path: _path + ".cost",
78
+ expected: "__type",
79
+ value: input.cost
80
+ }, _errorFactory)) && (("object" === typeof input.signed && null !== input.signed || __typia_transform__assertGuard._assertGuard(_exceptionable, {
81
+ method: "createAssert",
82
+ path: _path + ".signed",
83
+ expected: "__type.o1",
84
+ value: input.signed
85
+ }, _errorFactory)) && _ao3(input.signed, _path + ".signed", true && _exceptionable) || __typia_transform__assertGuard._assertGuard(_exceptionable, {
86
+ method: "createAssert",
87
+ path: _path + ".signed",
88
+ expected: "__type.o1",
89
+ value: input.signed
90
+ }, _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, {
91
+ method: "createAssert",
92
+ path: _path + ".from",
93
+ 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}`)",
94
+ value: input.from
95
+ }, _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, {
96
+ method: "createAssert",
97
+ path: _path + ".to",
98
+ 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}`)",
99
+ value: input.to
100
+ }, _errorFactory)) && ("string" === typeof input.amount || __typia_transform__assertGuard._assertGuard(_exceptionable, {
101
+ method: "createAssert",
102
+ path: _path + ".amount",
103
+ expected: "string",
104
+ value: input.amount
105
+ }, _errorFactory)) && ("from" === input.affinity || "to" === input.affinity || __typia_transform__assertGuard._assertGuard(_exceptionable, {
106
+ method: "createAssert",
107
+ path: _path + ".affinity",
108
+ expected: "(\"from\" | \"to\")",
109
+ value: input.affinity
110
+ }, _errorFactory)); const _ao2 = (input, _path, _exceptionable = true) => ("string" === typeof input.amount || __typia_transform__assertGuard._assertGuard(_exceptionable, {
111
+ method: "createAssert",
112
+ path: _path + ".amount",
113
+ expected: "string",
114
+ value: input.amount
115
+ }, _errorFactory)) && ("string" === typeof input.token && (RegExp(/^keeta_am(.*)/).test(input.token) || RegExp(/^keeta_an(.*)/).test(input.token) || RegExp(/^keeta_ao(.*)/).test(input.token) || RegExp(/^keeta_ap(.*)/).test(input.token) || RegExp(/^tyblocks_am(.*)/).test(input.token) || RegExp(/^tyblocks_an(.*)/).test(input.token) || RegExp(/^tyblocks_ao(.*)/).test(input.token) || RegExp(/^tyblocks_ap(.*)/).test(input.token)) || __typia_transform__assertGuard._assertGuard(_exceptionable, {
116
+ method: "createAssert",
117
+ path: _path + ".token",
118
+ 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}`)",
119
+ value: input.token
120
+ }, _errorFactory)); const _ao3 = (input, _path, _exceptionable = true) => ("string" === typeof input.nonce || __typia_transform__assertGuard._assertGuard(_exceptionable, {
121
+ method: "createAssert",
122
+ path: _path + ".nonce",
123
+ expected: "string",
124
+ value: input.nonce
125
+ }, _errorFactory)) && ("string" === typeof input.timestamp || __typia_transform__assertGuard._assertGuard(_exceptionable, {
126
+ method: "createAssert",
127
+ path: _path + ".timestamp",
128
+ expected: "string",
129
+ value: input.timestamp
130
+ }, _errorFactory)) && ("string" === typeof input.signature || __typia_transform__assertGuard._assertGuard(_exceptionable, {
131
+ method: "createAssert",
132
+ path: _path + ".signature",
133
+ expected: "string",
134
+ value: input.signature
135
+ }, _errorFactory)); const __is = input => "object" === typeof input && null !== input && _io0(input); let _errorFactory; return (input, errorFactory) => {
136
+ if (false === __is(input)) {
137
+ _errorFactory = errorFactory;
138
+ ((input, _path, _exceptionable = true) => ("object" === typeof input && null !== input || __typia_transform__assertGuard._assertGuard(true, {
139
+ method: "createAssert",
140
+ path: _path + "",
141
+ expected: "KeetaFXAnchorQuote",
142
+ value: input
143
+ }, _errorFactory)) && _ao0(input, _path + "", true) || __typia_transform__assertGuard._assertGuard(true, {
144
+ method: "createAssert",
145
+ path: _path + "",
146
+ expected: "KeetaFXAnchorQuote",
147
+ value: input
148
+ }, _errorFactory))(input, "$input", true);
149
+ }
150
+ return input;
151
+ }; })();
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
+ ;
185
+ async function formatQuoteSignable(unsignedQuote) {
186
+ const retval = [
187
+ unsignedQuote.request.from,
188
+ unsignedQuote.request.to,
189
+ unsignedQuote.request.amount,
190
+ unsignedQuote.request.affinity,
191
+ unsignedQuote.account,
192
+ unsignedQuote.convertedAmount,
193
+ unsignedQuote.cost.token,
194
+ unsignedQuote.cost.amount
195
+ ];
196
+ return (retval);
197
+ }
198
+ async function generateSignedQuote(signer, unsignedQuote) {
199
+ const signableQuote = await formatQuoteSignable(unsignedQuote);
200
+ const signed = await Signing.SignData(signer, signableQuote);
201
+ return ({
202
+ ...unsignedQuote,
203
+ signed: signed
204
+ });
205
+ }
206
+ async function verifySignedData(signedBy, quote) {
207
+ const signableQuote = await formatQuoteSignable(quote);
208
+ return (await Signing.VerifySignedData(signedBy, signableQuote, quote.signed));
209
+ }
210
+ async function requestToAccounts(config, request) {
211
+ const account = KeetaNet.lib.Account.isInstance(config.account) ? config.account : await config.account(request);
212
+ let signer = null;
213
+ if (config.signer !== undefined) {
214
+ signer = (KeetaNet.lib.Account.isInstance(config.signer) ? config.signer : await config.signer(request)).assertAccount();
215
+ }
216
+ return ({
217
+ signer: signer ?? account.assertAccount(),
218
+ account: account.assertAccount()
219
+ });
220
+ }
221
+ async function initRoutes(config) {
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;
380
+ homepage;
381
+ client;
382
+ logger;
383
+ account;
384
+ signer;
385
+ quoteSigner;
386
+ fx;
387
+ #serverPromise;
388
+ #server;
389
+ constructor(config) {
390
+ this.homepage = config.homepage ?? '';
391
+ this.port = config.port ?? 0;
392
+ this.client = config.client;
393
+ this.fx = config.fx;
394
+ this.account = config.account;
395
+ this.signer = config.signer ?? config.account;
396
+ 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
+ }
421
+ static routeFind(method, requestURL, routes) {
422
+ for (const routeKey in routes) {
423
+ const route = routes[routeKey];
424
+ if (route === undefined) {
425
+ continue;
426
+ }
427
+ const [routeMethod, ...routePathParts] = routeKey.split(' ');
428
+ const routePath = `/${routePathParts.join(' ')}`.replace(/^\/+/, '/');
429
+ if (method !== routeMethod) {
430
+ continue;
431
+ }
432
+ const routeURL = new URL(routePath, 'http://localhost');
433
+ const matchResult = this.routeMatch(requestURL, routeURL);
434
+ if (matchResult.match) {
435
+ return ({
436
+ route: route,
437
+ params: matchResult.params
438
+ });
439
+ }
440
+ }
441
+ return (null);
442
+ }
443
+ static addCORS(routes) {
444
+ const newRoutes = {};
445
+ const validMethods = new Set(['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD']);
446
+ const methodsByPath = {};
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;
453
+ }
454
+ if (!validMethods.has(method)) {
455
+ continue;
456
+ }
457
+ if (!(path in methodsByPath)) {
458
+ methodsByPath[path] = new Set();
459
+ }
460
+ if (methodsByPath[path] === undefined) {
461
+ throw (new Error(`internal error: methodsByPath missing path for ${path}`));
462
+ }
463
+ methodsByPath[path].add(method);
464
+ }
465
+ const seenPaths = new Set();
466
+ for (const routeKey in routes) {
467
+ const methodAndPath = routeKey.split(' ');
468
+ const method = methodAndPath[0];
469
+ const path = methodAndPath.slice(1).join(' ');
470
+ const routeHandler = routes[routeKey];
471
+ if (routeHandler === undefined) {
472
+ throw (new Error(`internal error: routeHandler missing for routeKey ${routeKey}`));
473
+ }
474
+ if (method !== 'ERROR') {
475
+ if (method === undefined || path === undefined) {
476
+ newRoutes[routeKey] = routeHandler;
477
+ continue;
478
+ }
479
+ if (!validMethods.has(method)) {
480
+ newRoutes[routeKey] = routeHandler;
481
+ continue;
482
+ }
483
+ }
484
+ const validMethodsForPath = methodsByPath[path];
485
+ let validMethodsForPathParts = [];
486
+ if (validMethodsForPath !== undefined) {
487
+ validMethodsForPath.add('OPTIONS');
488
+ validMethodsForPathParts = Array.from(validMethodsForPath);
489
+ }
490
+ else {
491
+ validMethodsForPathParts = [...Array.from(validMethods), 'OPTIONS'];
492
+ }
493
+ newRoutes[routeKey] = async function (...args) {
494
+ const retval = await routeHandler(...args);
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);
520
+ }
521
+ }
522
+ return (newRoutes);
523
+ }
524
+ async main(onSetPort) {
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;
541
+ }
542
+ /*
543
+ * Extract the route handler and the parameters from
544
+ * the request
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
+ }
624
+ }
625
+ if (result === undefined) {
626
+ throw (new Error('internal error: No result'));
627
+ }
628
+ /**
629
+ * Write the response to the client
630
+ */
631
+ response.statusCode = result.statusCode ?? 200;
632
+ for (const headerKey in result.headers ?? {}) {
633
+ const headerValue = result.headers?.[headerKey];
634
+ if (headerValue !== undefined) {
635
+ response.setHeader(headerKey, headerValue);
636
+ }
637
+ }
638
+ response.setHeader('Content-Type', result.contentType ?? 'application/json');
639
+ response.write(result.output);
640
+ response.end();
641
+ });
642
+ this.#server = server;
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();
650
+ });
651
+ });
652
+ /**
653
+ * Start listening on the port
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);
661
+ }
662
+ this.logger?.debug('KeetaAnchorFX.Server', 'Listening on port:', this.port);
663
+ });
664
+ /**
665
+ * Wait for the server to close
666
+ */
667
+ await waiter;
668
+ }
669
+ async start() {
670
+ await new Promise((resolve) => {
671
+ this.#serverPromise = this.main(function () {
672
+ resolve();
673
+ });
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());
691
+ }
692
+ }
693
+ //# sourceMappingURL=server.js.map