@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.
- package/client/index.d.ts +6 -0
- package/client/index.d.ts.map +1 -1
- package/client/index.js +7 -0
- package/client/index.js.map +1 -1
- package/lib/error.d.ts +14 -0
- package/lib/error.d.ts.map +1 -0
- package/lib/error.js +38 -0
- package/lib/error.js.map +1 -0
- package/lib/resolver.d.ts +159 -60
- package/lib/resolver.d.ts.map +1 -1
- package/lib/resolver.js +3519 -384
- package/lib/resolver.js.map +1 -1
- package/lib/utils/brand.d.ts +12 -0
- package/lib/utils/brand.d.ts.map +1 -0
- package/lib/utils/brand.js +2 -0
- package/lib/utils/brand.js.map +1 -0
- package/lib/utils/never.d.ts +4 -0
- package/lib/utils/never.d.ts.map +1 -1
- package/lib/utils/never.js.map +1 -1
- package/lib/utils/signing.d.ts +17 -0
- package/lib/utils/signing.d.ts.map +1 -0
- package/lib/utils/signing.js +73 -0
- package/lib/utils/signing.js.map +1 -0
- package/lib/utils/url.d.ts +2 -0
- package/lib/utils/url.d.ts.map +1 -0
- package/lib/utils/url.js +8 -0
- package/lib/utils/url.js.map +1 -0
- package/npm-shrinkwrap.json +21 -14
- package/package.json +6 -4
- package/services/fx/client.d.ts +137 -0
- package/services/fx/client.d.ts.map +1 -0
- package/services/fx/client.js +519 -0
- package/services/fx/client.js.map +1 -0
- package/services/fx/common.d.ts +133 -0
- package/services/fx/common.d.ts.map +1 -0
- package/services/fx/common.js +45 -0
- package/services/fx/common.js.map +1 -0
- package/services/fx/server.d.ts +77 -0
- package/services/fx/server.d.ts.map +1 -0
- package/services/fx/server.js +693 -0
- package/services/fx/server.js.map +1 -0
- package/services/kyc/client.d.ts +1 -1
- package/services/kyc/client.d.ts.map +1 -1
- package/services/kyc/client.js +7 -53
- 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
|