@rhea-finance/cross-chain-aggregation-dex 1.0.6 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +277 -270
- package/dist/executors/aptos.d.mts +19 -0
- package/dist/executors/aptos.d.ts +19 -0
- package/dist/executors/aptos.js +140 -0
- package/dist/executors/aptos.js.map +1 -0
- package/dist/executors/aptos.mjs +138 -0
- package/dist/executors/aptos.mjs.map +1 -0
- package/dist/executors/bitcoin.d.mts +22 -0
- package/dist/executors/bitcoin.d.ts +22 -0
- package/dist/executors/bitcoin.js +153 -0
- package/dist/executors/bitcoin.js.map +1 -0
- package/dist/executors/bitcoin.mjs +151 -0
- package/dist/executors/bitcoin.mjs.map +1 -0
- package/dist/executors/evm.d.mts +18 -0
- package/dist/executors/evm.d.ts +18 -0
- package/dist/executors/evm.js +233 -0
- package/dist/executors/evm.js.map +1 -0
- package/dist/executors/evm.mjs +231 -0
- package/dist/executors/evm.mjs.map +1 -0
- package/dist/executors/near.d.mts +18 -0
- package/dist/executors/near.d.ts +18 -0
- package/dist/executors/near.js +162 -0
- package/dist/executors/near.js.map +1 -0
- package/dist/executors/near.mjs +160 -0
- package/dist/executors/near.mjs.map +1 -0
- package/dist/executors/solana.d.mts +20 -0
- package/dist/executors/solana.d.ts +20 -0
- package/dist/executors/solana.js +151 -0
- package/dist/executors/solana.js.map +1 -0
- package/dist/executors/solana.mjs +149 -0
- package/dist/executors/solana.mjs.map +1 -0
- package/dist/executors/sui.d.mts +19 -0
- package/dist/executors/sui.d.ts +19 -0
- package/dist/executors/sui.js +154 -0
- package/dist/executors/sui.js.map +1 -0
- package/dist/executors/sui.mjs +152 -0
- package/dist/executors/sui.mjs.map +1 -0
- package/dist/executors/tron.d.mts +24 -0
- package/dist/executors/tron.d.ts +24 -0
- package/dist/executors/tron.js +161 -0
- package/dist/executors/tron.js.map +1 -0
- package/dist/executors/tron.mjs +159 -0
- package/dist/executors/tron.mjs.map +1 -0
- package/dist/executors/zcash.d.mts +19 -0
- package/dist/executors/zcash.d.ts +19 -0
- package/dist/executors/zcash.js +158 -0
- package/dist/executors/zcash.js.map +1 -0
- package/dist/executors/zcash.mjs +156 -0
- package/dist/executors/zcash.mjs.map +1 -0
- package/dist/index.d.mts +278 -250
- package/dist/index.d.ts +278 -250
- package/dist/index.js +2369 -573
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2340 -560
- package/dist/index.mjs.map +1 -1
- package/dist/registry-DRYUqs7T.d.mts +532 -0
- package/dist/registry-DRYUqs7T.d.ts +532 -0
- package/dist/shared-BdH3hWuP.d.ts +23 -0
- package/dist/shared-BqpFeosz.d.mts +23 -0
- package/package.json +51 -17
package/dist/index.js
CHANGED
|
@@ -1,641 +1,2437 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var zod = require('zod');
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// src/core/errors.ts
|
|
6
|
+
var SwapSdkError = class extends Error {
|
|
7
|
+
constructor(code, stage, message, options = {}) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.stage = stage;
|
|
11
|
+
this.options = options;
|
|
12
|
+
this.name = "SwapSdkError";
|
|
13
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
14
|
+
}
|
|
15
|
+
get retryable() {
|
|
16
|
+
return this.options.retryable ?? false;
|
|
17
|
+
}
|
|
18
|
+
get cause() {
|
|
19
|
+
return this.options.cause;
|
|
20
|
+
}
|
|
21
|
+
get details() {
|
|
22
|
+
return this.options.details;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
function asSwapSdkError(error, stage) {
|
|
26
|
+
if (error instanceof SwapSdkError) return error;
|
|
27
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
28
|
+
return new SwapSdkError("API_ERROR", stage, message, { cause: error });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/api/ApiClient.ts
|
|
32
|
+
var DEFAULT_RETRY = {
|
|
33
|
+
maxRetries: 2,
|
|
34
|
+
baseDelayMs: 250,
|
|
35
|
+
maxDelayMs: 2e3,
|
|
36
|
+
jitter: true
|
|
37
|
+
};
|
|
38
|
+
var ApiClient = class {
|
|
39
|
+
constructor(config) {
|
|
40
|
+
this.config = config;
|
|
41
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
42
|
+
const fetchImpl = config.fetch ?? globalThis.fetch;
|
|
43
|
+
if (typeof fetchImpl !== "function") {
|
|
44
|
+
throw new SwapSdkError(
|
|
45
|
+
"INVALID_REQUEST",
|
|
46
|
+
"quote",
|
|
47
|
+
"Fetch is unavailable; inject fetch when using Node.js 16"
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
this.fetchImpl = fetchImpl.bind(globalThis);
|
|
51
|
+
}
|
|
52
|
+
quote(body, options = {}) {
|
|
53
|
+
return this.request("/api/swap/quote", "quote", {
|
|
54
|
+
...options,
|
|
55
|
+
method: "POST",
|
|
56
|
+
body,
|
|
57
|
+
retryableOperation: true
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
build(body, options = {}) {
|
|
61
|
+
return this.request("/api/swap/swap", "build", {
|
|
62
|
+
...options,
|
|
63
|
+
method: "POST",
|
|
64
|
+
body
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
submitOrder(body, options = {}) {
|
|
68
|
+
return this.request("/api/swap/order-submit", "submit", {
|
|
69
|
+
...options,
|
|
70
|
+
method: "POST",
|
|
71
|
+
body
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
getOrderStatus(params, options = {}) {
|
|
75
|
+
return this.request("/api/swap/order-status", "status", {
|
|
76
|
+
...options,
|
|
77
|
+
method: "GET",
|
|
78
|
+
retryableOperation: true,
|
|
79
|
+
query: {
|
|
80
|
+
orderId: params.orderId,
|
|
81
|
+
router: params.router,
|
|
82
|
+
chainId: params.chainId
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
report(body, options = {}) {
|
|
87
|
+
return this.request("/api/swap/report", "report", {
|
|
88
|
+
...options,
|
|
89
|
+
method: "POST",
|
|
90
|
+
body
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
getHistory(params, options = {}) {
|
|
94
|
+
return this.request("/api/swap/history", "history", {
|
|
95
|
+
...options,
|
|
96
|
+
method: "GET",
|
|
97
|
+
retryableOperation: true,
|
|
98
|
+
query: {
|
|
99
|
+
sender: params.sender,
|
|
100
|
+
pageNumber: params.pageNumber,
|
|
101
|
+
pageSize: params.pageSize
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
async request(path, stage, options) {
|
|
106
|
+
const url = this.buildUrl(path, options.query);
|
|
107
|
+
const headers = await this.buildHeaders(options.idempotencyKey);
|
|
108
|
+
const retry = this.retryConfig();
|
|
109
|
+
let attempt = 1;
|
|
110
|
+
for (; ; ) {
|
|
111
|
+
this.log({
|
|
112
|
+
level: "debug",
|
|
113
|
+
event: "api.request",
|
|
114
|
+
path,
|
|
115
|
+
stage,
|
|
116
|
+
attempt
|
|
117
|
+
});
|
|
118
|
+
try {
|
|
119
|
+
return await this.requestOnce(url, path, stage, options, headers, attempt);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
const sdkError = error instanceof SwapSdkError ? error : new SwapSdkError("HTTP_ERROR", stage, "Network request failed", {
|
|
122
|
+
cause: error,
|
|
123
|
+
retryable: true
|
|
124
|
+
});
|
|
125
|
+
const canRetry = options.retryableOperation === true && sdkError.retryable && sdkError.code !== "REQUEST_ABORTED" && attempt <= retry.maxRetries;
|
|
126
|
+
if (!canRetry) throw sdkError;
|
|
127
|
+
this.log({
|
|
128
|
+
level: "warn",
|
|
129
|
+
event: "api.retry",
|
|
130
|
+
path,
|
|
131
|
+
stage,
|
|
132
|
+
attempt,
|
|
133
|
+
code: sdkError.code
|
|
134
|
+
});
|
|
135
|
+
await waitForRetry(this.retryDelay(attempt, retry), options.signal, stage);
|
|
136
|
+
attempt += 1;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async requestOnce(url, path, stage, options, headers, attempt) {
|
|
141
|
+
if (options.signal?.aborted) {
|
|
142
|
+
throw new SwapSdkError("REQUEST_ABORTED", stage, "Request aborted", {
|
|
143
|
+
cause: options.signal.reason
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
const controller = new AbortController();
|
|
147
|
+
const timeoutMs = this.config.timeoutMs ?? 15e3;
|
|
148
|
+
let timedOut = false;
|
|
149
|
+
const startedAt = Date.now();
|
|
150
|
+
const abortFromCaller = () => controller.abort(options.signal?.reason);
|
|
151
|
+
if (options.signal?.aborted) abortFromCaller();
|
|
152
|
+
options.signal?.addEventListener("abort", abortFromCaller, { once: true });
|
|
153
|
+
const timeout = setTimeout(() => {
|
|
154
|
+
timedOut = true;
|
|
155
|
+
controller.abort();
|
|
156
|
+
}, timeoutMs);
|
|
157
|
+
try {
|
|
158
|
+
const response = await this.fetchImpl(url, {
|
|
159
|
+
method: options.method,
|
|
160
|
+
headers,
|
|
161
|
+
body: options.body === void 0 ? void 0 : JSON.stringify(options.body),
|
|
162
|
+
signal: controller.signal
|
|
163
|
+
});
|
|
164
|
+
if (controller.signal.aborted) {
|
|
165
|
+
throw new SwapSdkError(
|
|
166
|
+
timedOut ? "REQUEST_TIMEOUT" : "REQUEST_ABORTED",
|
|
167
|
+
stage,
|
|
168
|
+
timedOut ? `Request timed out after ${timeoutMs}ms` : "Request aborted",
|
|
169
|
+
{ retryable: timedOut }
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
this.log({
|
|
173
|
+
level: "debug",
|
|
174
|
+
event: "api.response",
|
|
175
|
+
path,
|
|
176
|
+
stage,
|
|
177
|
+
attempt,
|
|
178
|
+
status: response.status,
|
|
179
|
+
durationMs: Date.now() - startedAt
|
|
180
|
+
});
|
|
181
|
+
const text = await response.text();
|
|
182
|
+
const parsed = this.parseResponse(text, response.status, stage);
|
|
183
|
+
if (!response.ok) {
|
|
184
|
+
const message = this.readMessage(parsed) || `HTTP ${response.status}`;
|
|
185
|
+
if (response.status === 401 || response.status === 403) {
|
|
186
|
+
throw new SwapSdkError("AUTH_FAILED", stage, message);
|
|
187
|
+
}
|
|
188
|
+
if (response.status === 429) {
|
|
189
|
+
throw new SwapSdkError("RATE_LIMITED", stage, message, {
|
|
190
|
+
retryable: true
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
throw new SwapSdkError("HTTP_ERROR", stage, message, {
|
|
194
|
+
retryable: response.status >= 500,
|
|
195
|
+
details: { status: response.status }
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
if (!this.isEnvelope(parsed)) {
|
|
199
|
+
throw new SwapSdkError(
|
|
200
|
+
"INVALID_API_RESPONSE",
|
|
201
|
+
stage,
|
|
202
|
+
"Invalid unified swap API response envelope"
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
if (parsed.code !== 0) {
|
|
206
|
+
throw new SwapSdkError(
|
|
207
|
+
"API_ERROR",
|
|
208
|
+
stage,
|
|
209
|
+
parsed.msg || `Service returned code ${parsed.code}`,
|
|
210
|
+
{ details: { apiCode: parsed.code } }
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
return parsed.data;
|
|
214
|
+
} catch (error) {
|
|
215
|
+
if (error instanceof SwapSdkError) throw error;
|
|
216
|
+
if (controller.signal.aborted) {
|
|
217
|
+
throw new SwapSdkError(
|
|
218
|
+
timedOut ? "REQUEST_TIMEOUT" : "REQUEST_ABORTED",
|
|
219
|
+
stage,
|
|
220
|
+
timedOut ? `Request timed out after ${timeoutMs}ms` : "Request aborted",
|
|
221
|
+
{ cause: error, retryable: timedOut }
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
const details = networkFailureDetails(
|
|
225
|
+
error,
|
|
226
|
+
options.method,
|
|
227
|
+
path,
|
|
228
|
+
attempt
|
|
229
|
+
);
|
|
230
|
+
console.error("SDK network request failed", {
|
|
231
|
+
...details,
|
|
232
|
+
stage
|
|
233
|
+
});
|
|
234
|
+
throw new SwapSdkError(
|
|
235
|
+
"HTTP_ERROR",
|
|
236
|
+
stage,
|
|
237
|
+
`Network request failed: ${details.causeName}: ${details.causeMessage}`,
|
|
238
|
+
{
|
|
239
|
+
cause: error,
|
|
240
|
+
retryable: true,
|
|
241
|
+
details
|
|
242
|
+
}
|
|
243
|
+
);
|
|
244
|
+
} finally {
|
|
245
|
+
clearTimeout(timeout);
|
|
246
|
+
options.signal?.removeEventListener("abort", abortFromCaller);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
buildUrl(path, query) {
|
|
250
|
+
if (!query) return `${this.baseUrl}${path}`;
|
|
251
|
+
const search = new URLSearchParams();
|
|
252
|
+
for (const [key, value] of Object.entries(query)) {
|
|
253
|
+
if (value === void 0 || value === null) continue;
|
|
254
|
+
search.set(key, String(value));
|
|
255
|
+
}
|
|
256
|
+
const suffix = search.toString();
|
|
257
|
+
return suffix ? `${this.baseUrl}${path}?${suffix}` : `${this.baseUrl}${path}`;
|
|
258
|
+
}
|
|
259
|
+
async buildHeaders(idempotencyKey) {
|
|
260
|
+
const configured = typeof this.config.headers === "function" ? await this.config.headers() : this.config.headers ?? {};
|
|
261
|
+
const token = this.config.getAccessToken ? await this.config.getAccessToken() : this.config.apiKey;
|
|
262
|
+
return {
|
|
263
|
+
"Content-Type": "application/json",
|
|
264
|
+
...token ? { Authorization: `Bearer ${token}` } : {},
|
|
265
|
+
...idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {},
|
|
266
|
+
...configured
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
retryConfig() {
|
|
270
|
+
const configured = { ...DEFAULT_RETRY, ...this.config.retry };
|
|
271
|
+
if (!Number.isSafeInteger(configured.maxRetries) || configured.maxRetries < 0 || !Number.isFinite(configured.baseDelayMs) || configured.baseDelayMs < 0 || !Number.isFinite(configured.maxDelayMs) || configured.maxDelayMs < configured.baseDelayMs) {
|
|
272
|
+
throw new SwapSdkError(
|
|
273
|
+
"INVALID_REQUEST",
|
|
274
|
+
"quote",
|
|
275
|
+
"Invalid retry configuration"
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
return configured;
|
|
279
|
+
}
|
|
280
|
+
retryDelay(attempt, retry) {
|
|
281
|
+
const exponential = Math.min(
|
|
282
|
+
retry.maxDelayMs,
|
|
283
|
+
retry.baseDelayMs * 2 ** (attempt - 1)
|
|
284
|
+
);
|
|
285
|
+
return retry.jitter ? Math.min(retry.maxDelayMs, exponential * (0.5 + Math.random())) : exponential;
|
|
286
|
+
}
|
|
287
|
+
log(entry) {
|
|
288
|
+
try {
|
|
289
|
+
this.config.logger?.log(entry);
|
|
290
|
+
} catch {
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
parseResponse(text, status, stage) {
|
|
294
|
+
try {
|
|
295
|
+
return text ? JSON.parse(text) : {};
|
|
296
|
+
} catch (error) {
|
|
297
|
+
throw new SwapSdkError(
|
|
298
|
+
status >= 400 ? "HTTP_ERROR" : "INVALID_API_RESPONSE",
|
|
299
|
+
stage,
|
|
300
|
+
status >= 400 ? `HTTP ${status}` : "Response is not valid JSON",
|
|
301
|
+
{ cause: error, retryable: status >= 500 }
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
isEnvelope(value) {
|
|
306
|
+
return typeof value === "object" && value !== null && "code" in value && typeof value.code === "number" && "msg" in value && typeof value.msg === "string" && "data" in value;
|
|
307
|
+
}
|
|
308
|
+
readMessage(value) {
|
|
309
|
+
if (typeof value !== "object" || value === null || !("msg" in value)) {
|
|
310
|
+
return void 0;
|
|
311
|
+
}
|
|
312
|
+
return typeof value.msg === "string" && value.msg.trim() ? value.msg.trim() : void 0;
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
function networkFailureDetails(error, method, path, attempt) {
|
|
316
|
+
const causeName = error instanceof Error && error.name.trim() ? error.name.trim() : "Error";
|
|
317
|
+
const rawMessage = error instanceof Error && error.message.trim() ? error.message.trim() : String(error);
|
|
318
|
+
const causeMessage = rawMessage.replace(
|
|
319
|
+
/\b(https?:\/\/[^\s?#]+)\?[^\s#]*/gi,
|
|
320
|
+
"$1?[redacted]"
|
|
321
|
+
);
|
|
322
|
+
return {
|
|
323
|
+
method,
|
|
324
|
+
path,
|
|
325
|
+
attempt,
|
|
326
|
+
causeName,
|
|
327
|
+
causeMessage
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
function waitForRetry(delayMs, signal, stage) {
|
|
331
|
+
return new Promise((resolve, reject) => {
|
|
332
|
+
if (signal?.aborted) {
|
|
333
|
+
reject(
|
|
334
|
+
new SwapSdkError("REQUEST_ABORTED", stage, "Request aborted", {
|
|
335
|
+
cause: signal.reason
|
|
336
|
+
})
|
|
337
|
+
);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
const onAbort = () => {
|
|
341
|
+
clearTimeout(timeout);
|
|
342
|
+
reject(
|
|
343
|
+
new SwapSdkError("REQUEST_ABORTED", stage, "Request aborted", {
|
|
344
|
+
cause: signal?.reason
|
|
345
|
+
})
|
|
346
|
+
);
|
|
347
|
+
};
|
|
348
|
+
const timeout = setTimeout(() => {
|
|
349
|
+
signal?.removeEventListener("abort", onAbort);
|
|
350
|
+
resolve();
|
|
351
|
+
}, delayMs);
|
|
352
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// src/core/lifecycle.ts
|
|
357
|
+
var ORDER_STATUS_MAP = {
|
|
358
|
+
PENDING: "pending",
|
|
359
|
+
CREATED: "pending",
|
|
360
|
+
PROCESSING: "processing",
|
|
361
|
+
IN_PROGRESS: "processing",
|
|
362
|
+
SUCCESS: "completed",
|
|
363
|
+
COMPLETED: "completed",
|
|
364
|
+
FILLED: "completed",
|
|
365
|
+
FAILED: "failed",
|
|
366
|
+
REFUNDED: "refunded",
|
|
367
|
+
EXPIRED: "expired"
|
|
368
|
+
};
|
|
369
|
+
function normalizeOrderStatus(raw) {
|
|
370
|
+
const value = raw.status ?? raw.state;
|
|
371
|
+
if (typeof value !== "string") return "unknown";
|
|
372
|
+
return ORDER_STATUS_MAP[value.trim().toUpperCase()] ?? "unknown";
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// src/core/registry.ts
|
|
376
|
+
var ExecutorRegistry = class {
|
|
377
|
+
constructor(executors = []) {
|
|
378
|
+
this.executors = /* @__PURE__ */ new Map();
|
|
379
|
+
for (const executor of executors) this.register(executor);
|
|
380
|
+
}
|
|
381
|
+
register(executor, allowOverride = false) {
|
|
382
|
+
for (const kind of executor.kinds) {
|
|
383
|
+
if (!allowOverride && this.executors.has(kind)) {
|
|
384
|
+
throw new SwapSdkError(
|
|
385
|
+
"INVALID_REQUEST",
|
|
386
|
+
"build",
|
|
387
|
+
`Executor already registered for ${kind}`
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
this.executors.set(kind, executor);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
get(kind) {
|
|
394
|
+
const executor = this.executors.get(kind);
|
|
395
|
+
if (!executor) {
|
|
396
|
+
throw new SwapSdkError(
|
|
397
|
+
"EXECUTOR_NOT_FOUND",
|
|
398
|
+
"sign",
|
|
399
|
+
`No executor registered for ${kind}`
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
return executor;
|
|
403
|
+
}
|
|
404
|
+
getSigner(chain, requireSignMessage = false) {
|
|
405
|
+
const executor = Array.from(new Set(this.executors.values())).find(
|
|
406
|
+
(candidate) => candidate.signerChain === chain
|
|
407
|
+
);
|
|
408
|
+
if (!executor) {
|
|
409
|
+
throw new SwapSdkError(
|
|
410
|
+
"EXECUTOR_NOT_FOUND",
|
|
411
|
+
"sign",
|
|
412
|
+
`No executor registered for signer chain ${chain}`
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
if (!executor.getIdentityKey) {
|
|
416
|
+
throw new SwapSdkError(
|
|
417
|
+
"SIGNING_FAILED",
|
|
418
|
+
"sign",
|
|
419
|
+
`Executor for signer chain ${chain} must implement getIdentityKey`
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
if (requireSignMessage && !executor.signMessage) {
|
|
423
|
+
throw new SwapSdkError(
|
|
424
|
+
"SIGNING_FAILED",
|
|
425
|
+
"sign",
|
|
426
|
+
`Executor for signer chain ${chain} must implement signMessage`
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
return executor;
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
// src/normalizers/chain.ts
|
|
434
|
+
var CANONICAL_NON_EVM_CHAINS = /* @__PURE__ */ new Set([
|
|
435
|
+
"solana",
|
|
436
|
+
"aptos",
|
|
437
|
+
"near",
|
|
438
|
+
"tron",
|
|
439
|
+
"btc",
|
|
440
|
+
"zcash",
|
|
441
|
+
"sui"
|
|
442
|
+
]);
|
|
443
|
+
var RAW_API_ALIASES = /* @__PURE__ */ new Map([
|
|
444
|
+
["sol", "solana"],
|
|
445
|
+
["501", "solana"],
|
|
446
|
+
["637", "aptos"],
|
|
447
|
+
["trx", "tron"],
|
|
448
|
+
["bitcoin", "btc"],
|
|
449
|
+
["zec", "zcash"]
|
|
450
|
+
]);
|
|
451
|
+
var NATIVE_ADDRESSES = /* @__PURE__ */ new Map([
|
|
452
|
+
["solana", "So11111111111111111111111111111111111111112"],
|
|
453
|
+
["aptos", "0xa"],
|
|
454
|
+
["near", "wrap.near"],
|
|
455
|
+
["tron", "trx"],
|
|
456
|
+
["btc", "btc"],
|
|
457
|
+
["zcash", "nep141:zec.omft.near"],
|
|
458
|
+
["sui", "0x2::sui::SUI"]
|
|
459
|
+
]);
|
|
460
|
+
var EVM_ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
461
|
+
var DECIMAL_EVM_CHAIN_ID = /^[1-9]\d*$/;
|
|
462
|
+
function toApiChain(chain) {
|
|
463
|
+
if (DECIMAL_EVM_CHAIN_ID.test(chain) || CANONICAL_NON_EVM_CHAINS.has(chain)) {
|
|
464
|
+
return chain;
|
|
465
|
+
}
|
|
466
|
+
throw unsupportedChain(chain);
|
|
467
|
+
}
|
|
468
|
+
function fromApiChain(chain) {
|
|
469
|
+
const normalized = chain.trim().toLowerCase();
|
|
470
|
+
const alias = RAW_API_ALIASES.get(normalized);
|
|
471
|
+
if (alias) return alias;
|
|
472
|
+
if (CANONICAL_NON_EVM_CHAINS.has(normalized)) {
|
|
473
|
+
return normalized;
|
|
474
|
+
}
|
|
475
|
+
if (/^0x[0-9a-f]+$/.test(normalized)) {
|
|
476
|
+
const decimal = BigInt(normalized).toString();
|
|
477
|
+
if (decimal !== "0") return decimal;
|
|
478
|
+
}
|
|
479
|
+
if (DECIMAL_EVM_CHAIN_ID.test(normalized)) {
|
|
480
|
+
return BigInt(normalized).toString();
|
|
481
|
+
}
|
|
482
|
+
throw unsupportedChain(chain);
|
|
483
|
+
}
|
|
484
|
+
function toApiAssetAddress(asset) {
|
|
485
|
+
const explicit = asset.address.trim();
|
|
486
|
+
if (explicit) return explicit;
|
|
487
|
+
if (!asset.isNative) {
|
|
488
|
+
throw new SwapSdkError(
|
|
489
|
+
"INVALID_REQUEST",
|
|
490
|
+
"quote",
|
|
491
|
+
`Asset address is required for ${asset.chain}`
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
if (DECIMAL_EVM_CHAIN_ID.test(asset.chain)) return EVM_ZERO_ADDRESS;
|
|
495
|
+
const nativeAddress = NATIVE_ADDRESSES.get(asset.chain);
|
|
496
|
+
if (!nativeAddress) {
|
|
497
|
+
throw new SwapSdkError(
|
|
498
|
+
"UNSUPPORTED_CHAIN",
|
|
499
|
+
"quote",
|
|
500
|
+
`No native asset mapping for ${asset.chain}`
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
return nativeAddress;
|
|
504
|
+
}
|
|
505
|
+
function unsupportedChain(chain) {
|
|
506
|
+
return new SwapSdkError(
|
|
507
|
+
"UNSUPPORTED_CHAIN",
|
|
508
|
+
"quote",
|
|
509
|
+
`Unsupported chain: ${chain}`
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
var decimalStringSchema = zod.z.string().regex(/^(0|[1-9]\d*)$/);
|
|
513
|
+
var nonEmptyStringSchema = zod.z.string().trim().min(1);
|
|
514
|
+
var hexDataSchema = zod.z.string().regex(/^0x(?:[0-9a-fA-F]{2})*$/);
|
|
515
|
+
var positiveIntegerSchema = zod.z.number().int().positive();
|
|
516
|
+
var evmQuantitySchema = zod.z.string().regex(/^(?:0|[1-9]\d*|0x[0-9a-fA-F]+)$/).transform(
|
|
517
|
+
(value) => value.startsWith("0x") ? BigInt(value).toString() : value
|
|
518
|
+
);
|
|
519
|
+
var evmTxSchema = zod.z.object({
|
|
520
|
+
to: nonEmptyStringSchema,
|
|
521
|
+
data: hexDataSchema,
|
|
522
|
+
value: evmQuantitySchema,
|
|
523
|
+
gasLimit: evmQuantitySchema,
|
|
524
|
+
chainId: positiveIntegerSchema
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
// src/normalizers/build.ts
|
|
528
|
+
var tokenSchema = zod.z.object({
|
|
529
|
+
address: zod.z.string(),
|
|
530
|
+
symbol: zod.z.string(),
|
|
531
|
+
decimals: zod.z.number().int().nonnegative()
|
|
532
|
+
});
|
|
533
|
+
var commonBuildSchema = zod.z.object({
|
|
534
|
+
isCrossChain: zod.z.boolean(),
|
|
535
|
+
chainType: nonEmptyStringSchema,
|
|
536
|
+
router: nonEmptyStringSchema,
|
|
537
|
+
fromChain: nonEmptyStringSchema,
|
|
538
|
+
toChain: nonEmptyStringSchema,
|
|
539
|
+
tokenIn: tokenSchema,
|
|
540
|
+
tokenOut: tokenSchema,
|
|
541
|
+
amountIn: decimalStringSchema,
|
|
542
|
+
estimatedOut: decimalStringSchema,
|
|
543
|
+
minAmountOut: decimalStringSchema
|
|
544
|
+
});
|
|
545
|
+
var signingRequestSchema = zod.z.object({
|
|
546
|
+
type: nonEmptyStringSchema,
|
|
547
|
+
router: nonEmptyStringSchema,
|
|
548
|
+
quoteId: nonEmptyStringSchema,
|
|
549
|
+
chainId: positiveIntegerSchema,
|
|
550
|
+
signingScheme: zod.z.string().optional(),
|
|
551
|
+
typedData: zod.z.object({
|
|
552
|
+
domain: zod.z.record(zod.z.string(), zod.z.unknown()),
|
|
553
|
+
types: zod.z.record(
|
|
554
|
+
zod.z.string(),
|
|
555
|
+
zod.z.array(zod.z.object({ name: zod.z.string(), type: zod.z.string() }))
|
|
556
|
+
),
|
|
557
|
+
primaryType: nonEmptyStringSchema,
|
|
558
|
+
message: zod.z.record(zod.z.string(), zod.z.unknown())
|
|
559
|
+
}),
|
|
560
|
+
submit: zod.z.object({
|
|
561
|
+
endpoint: zod.z.string(),
|
|
562
|
+
method: zod.z.string(),
|
|
563
|
+
params: zod.z.record(zod.z.string(), zod.z.string())
|
|
564
|
+
}).optional()
|
|
565
|
+
});
|
|
566
|
+
var solanaTxSchema = zod.z.object({
|
|
567
|
+
transaction: zod.z.string(),
|
|
568
|
+
format: nonEmptyStringSchema,
|
|
569
|
+
addressLookupTableAddresses: zod.z.array(zod.z.string()).optional(),
|
|
570
|
+
recentBlockhash: zod.z.string().optional(),
|
|
571
|
+
txValidUntil: zod.z.number().optional(),
|
|
572
|
+
transactionSize: zod.z.number().optional()
|
|
573
|
+
});
|
|
574
|
+
var aptosTxSchema = zod.z.object({
|
|
575
|
+
function: nonEmptyStringSchema,
|
|
576
|
+
type_arguments: zod.z.array(zod.z.string()),
|
|
577
|
+
arguments: zod.z.array(zod.z.unknown())
|
|
578
|
+
});
|
|
579
|
+
var nearTxSchema = zod.z.object({
|
|
580
|
+
signerId: zod.z.string().optional(),
|
|
581
|
+
receiverId: nonEmptyStringSchema,
|
|
582
|
+
actions: zod.z.array(zod.z.unknown()).min(1)
|
|
583
|
+
});
|
|
584
|
+
var transferBaseSchema = zod.z.object({
|
|
585
|
+
kind: nonEmptyStringSchema,
|
|
586
|
+
chainId: zod.z.string().optional(),
|
|
587
|
+
chain: zod.z.string().optional(),
|
|
588
|
+
amount: decimalStringSchema,
|
|
589
|
+
depositAddress: nonEmptyStringSchema
|
|
590
|
+
});
|
|
591
|
+
var tronTxSchema = transferBaseSchema.extend({
|
|
592
|
+
tokenAddress: zod.z.string().optional(),
|
|
593
|
+
standard: zod.z.string().optional()
|
|
594
|
+
});
|
|
595
|
+
var bitcoinTxSchema = transferBaseSchema.extend({
|
|
596
|
+
feeRate: zod.z.number().positive().optional()
|
|
597
|
+
});
|
|
598
|
+
var zcashTxSchema = transferBaseSchema.extend({
|
|
599
|
+
decimals: zod.z.number().int().nonnegative().optional()
|
|
600
|
+
});
|
|
601
|
+
var suiTxSchema = transferBaseSchema.extend({
|
|
602
|
+
coinType: nonEmptyStringSchema.refine(
|
|
603
|
+
(value) => !value.toLowerCase().startsWith("nep141:"),
|
|
604
|
+
"Sui coinType cannot be an OMFT asset id"
|
|
605
|
+
)
|
|
606
|
+
});
|
|
607
|
+
function normalizeBuild(raw, executionId = createExecutionId(), request) {
|
|
608
|
+
try {
|
|
609
|
+
const common = commonBuildSchema.parse(raw);
|
|
610
|
+
const fromChain = fromApiChain(common.fromChain);
|
|
611
|
+
const toChain = fromApiChain(common.toChain);
|
|
612
|
+
const lane = laneFromChainType(common.chainType, fromChain);
|
|
613
|
+
assertLaneMatchesChain(lane, fromChain);
|
|
614
|
+
const execution = normalizeExecution(raw, lane, fromChain);
|
|
615
|
+
const order = normalizeOrder(raw, execution);
|
|
616
|
+
const deposit = normalizeDeposit(raw.deposit);
|
|
617
|
+
return {
|
|
618
|
+
executionId,
|
|
619
|
+
...raw.signingRequest?.quoteId ? { quoteId: raw.signingRequest.quoteId } : {},
|
|
620
|
+
isCrossChain: common.isCrossChain,
|
|
621
|
+
fromChain,
|
|
622
|
+
toChain,
|
|
623
|
+
tokenIn: { chain: fromChain, ...common.tokenIn },
|
|
624
|
+
tokenOut: { chain: toChain, ...common.tokenOut },
|
|
625
|
+
amountIn: common.amountIn,
|
|
626
|
+
estimatedOut: common.estimatedOut,
|
|
627
|
+
minAmountOut: common.minAmountOut,
|
|
628
|
+
router: common.router,
|
|
629
|
+
execution,
|
|
630
|
+
...order ? { order } : {},
|
|
631
|
+
...deposit ? { deposit } : {},
|
|
632
|
+
...request ? { request: { ...request } } : {},
|
|
633
|
+
raw
|
|
634
|
+
};
|
|
635
|
+
} catch (error) {
|
|
636
|
+
if (error instanceof SwapSdkError) throw error;
|
|
637
|
+
throw invalidBuild(error);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
function normalizeExecution(raw, lane, chain) {
|
|
641
|
+
switch (lane) {
|
|
642
|
+
case "evm":
|
|
643
|
+
return normalizeEvmExecution(raw, chain);
|
|
644
|
+
case "solana": {
|
|
645
|
+
const tx = solanaTxSchema.parse(raw.tx);
|
|
646
|
+
return {
|
|
647
|
+
kind: "solana-transaction",
|
|
648
|
+
chain,
|
|
649
|
+
transaction: tx.transaction,
|
|
650
|
+
format: tx.format,
|
|
651
|
+
metadata: {
|
|
652
|
+
...tx.addressLookupTableAddresses ? { addressLookupTableAddresses: tx.addressLookupTableAddresses } : {},
|
|
653
|
+
...tx.recentBlockhash ? { recentBlockhash: tx.recentBlockhash } : {},
|
|
654
|
+
...tx.txValidUntil === void 0 ? {} : { txValidUntil: tx.txValidUntil },
|
|
655
|
+
...tx.transactionSize === void 0 ? {} : { transactionSize: tx.transactionSize }
|
|
656
|
+
}
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
case "aptos": {
|
|
660
|
+
const tx = aptosTxSchema.parse(raw.tx);
|
|
661
|
+
return {
|
|
662
|
+
kind: "aptos-entry-function",
|
|
663
|
+
chain,
|
|
664
|
+
function: tx.function,
|
|
665
|
+
typeArguments: tx.type_arguments,
|
|
666
|
+
functionArguments: tx.arguments
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
case "near": {
|
|
670
|
+
const transactions = zod.z.union([nearTxSchema, zod.z.array(nearTxSchema).min(1)]).parse(raw.tx);
|
|
671
|
+
return {
|
|
672
|
+
kind: "near-transaction-batch",
|
|
673
|
+
chain,
|
|
674
|
+
transactions: Array.isArray(transactions) ? transactions : [transactions]
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
case "tron": {
|
|
678
|
+
const tx = tronTxSchema.parse(raw.tx);
|
|
679
|
+
assertKind(tx.kind, ["tron_transfer"]);
|
|
680
|
+
return {
|
|
681
|
+
kind: "tron-transfer",
|
|
682
|
+
chain,
|
|
683
|
+
amount: tx.amount,
|
|
684
|
+
depositAddress: tx.depositAddress,
|
|
685
|
+
...tx.tokenAddress ? { tokenAddress: tx.tokenAddress } : {},
|
|
686
|
+
...tx.standard ? { standard: tx.standard } : {}
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
case "bitcoin": {
|
|
690
|
+
const tx = bitcoinTxSchema.parse(raw.tx);
|
|
691
|
+
assertKind(tx.kind, ["btc_transfer", "utxo_transfer"]);
|
|
692
|
+
return {
|
|
693
|
+
kind: "bitcoin-transfer",
|
|
694
|
+
chain,
|
|
695
|
+
amount: tx.amount,
|
|
696
|
+
depositAddress: tx.depositAddress,
|
|
697
|
+
...tx.feeRate === void 0 ? {} : { feeRate: tx.feeRate }
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
case "zcash": {
|
|
701
|
+
const tx = zcashTxSchema.parse(raw.tx);
|
|
702
|
+
assertKind(tx.kind, ["zcash_transfer", "utxo_transfer"]);
|
|
703
|
+
return {
|
|
704
|
+
kind: "zcash-transfer",
|
|
705
|
+
chain,
|
|
706
|
+
amount: tx.amount,
|
|
707
|
+
depositAddress: tx.depositAddress,
|
|
708
|
+
...tx.decimals === void 0 ? {} : { decimals: tx.decimals }
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
case "sui": {
|
|
712
|
+
const candidate = typeof raw.tx === "object" && raw.tx !== null ? {
|
|
713
|
+
...raw.tx,
|
|
714
|
+
coinType: "coinType" in raw.tx && raw.tx.coinType ? raw.tx.coinType : raw.tokenIn.address
|
|
715
|
+
} : raw.tx;
|
|
716
|
+
const tx = suiTxSchema.parse(candidate);
|
|
717
|
+
assertKind(tx.kind, ["sui_transfer"]);
|
|
718
|
+
return {
|
|
719
|
+
kind: "sui-transfer",
|
|
720
|
+
chain,
|
|
721
|
+
amount: tx.amount,
|
|
722
|
+
depositAddress: tx.depositAddress,
|
|
723
|
+
coinType: tx.coinType
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
function normalizeEvmExecution(raw, chain) {
|
|
729
|
+
const approval = normalizeEvmApproval(raw, chain);
|
|
730
|
+
if (raw.executionType === "signature" || raw.signingRequest) {
|
|
731
|
+
const request = signingRequestSchema.parse(raw.signingRequest);
|
|
732
|
+
assertEvmChainId(chain, request.chainId);
|
|
733
|
+
return {
|
|
734
|
+
kind: "evm-signature",
|
|
735
|
+
chain,
|
|
736
|
+
request,
|
|
737
|
+
...approval ? { approval } : {}
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
const tx = evmTxSchema.parse(raw.tx);
|
|
741
|
+
assertEvmChainId(chain, tx.chainId);
|
|
742
|
+
return {
|
|
743
|
+
kind: "evm-transaction",
|
|
744
|
+
chain,
|
|
745
|
+
tx,
|
|
746
|
+
...approval ? { approval } : {}
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
function normalizeEvmApproval(raw, chain) {
|
|
750
|
+
if (raw.approve) {
|
|
751
|
+
const approveTx = evmTxSchema.parse(raw.approve.tx);
|
|
752
|
+
assertEvmChainId(chain, approveTx.chainId);
|
|
753
|
+
return {
|
|
754
|
+
tx: approveTx,
|
|
755
|
+
spender: nonEmptyStringSchema.parse(raw.approve.spender)
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
return void 0;
|
|
759
|
+
}
|
|
760
|
+
function laneFromChainType(chainType, fromChain) {
|
|
761
|
+
const normalized = chainType.toLowerCase();
|
|
762
|
+
const aliases = {
|
|
763
|
+
evm: "evm",
|
|
764
|
+
solana: "solana",
|
|
765
|
+
sol: "solana",
|
|
766
|
+
aptos: "aptos",
|
|
767
|
+
near: "near",
|
|
768
|
+
tron: "tron",
|
|
769
|
+
btc: "bitcoin",
|
|
770
|
+
bitcoin: "bitcoin",
|
|
771
|
+
zcash: "zcash",
|
|
772
|
+
zec: "zcash",
|
|
773
|
+
sui: "sui"
|
|
774
|
+
};
|
|
775
|
+
if (normalized === "cross-chain") return laneFromChain(fromChain);
|
|
776
|
+
const lane = aliases[normalized];
|
|
777
|
+
if (!lane) {
|
|
778
|
+
throw new SwapSdkError(
|
|
779
|
+
"UNSUPPORTED_CHAIN",
|
|
780
|
+
"build",
|
|
781
|
+
`Unsupported build chainType: ${chainType}`
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
return lane;
|
|
785
|
+
}
|
|
786
|
+
function laneFromChain(chain) {
|
|
787
|
+
if (/^[1-9]\d*$/.test(chain)) return "evm";
|
|
788
|
+
const lanes = {
|
|
789
|
+
solana: "solana",
|
|
790
|
+
aptos: "aptos",
|
|
791
|
+
near: "near",
|
|
792
|
+
tron: "tron",
|
|
793
|
+
btc: "bitcoin",
|
|
794
|
+
zcash: "zcash",
|
|
795
|
+
sui: "sui"
|
|
796
|
+
};
|
|
797
|
+
const lane = lanes[chain];
|
|
798
|
+
if (!lane) {
|
|
799
|
+
throw new SwapSdkError(
|
|
800
|
+
"UNSUPPORTED_CHAIN",
|
|
801
|
+
"build",
|
|
802
|
+
`Unsupported source chain: ${chain}`
|
|
803
|
+
);
|
|
804
|
+
}
|
|
805
|
+
return lane;
|
|
806
|
+
}
|
|
807
|
+
function assertLaneMatchesChain(lane, chain) {
|
|
808
|
+
const chainLane = laneFromChain(chain);
|
|
809
|
+
if (lane !== chainLane) {
|
|
810
|
+
throw new SwapSdkError(
|
|
811
|
+
"CHAIN_MISMATCH",
|
|
812
|
+
"build",
|
|
813
|
+
`Build chainType ${lane} does not match source chain ${chain}`
|
|
814
|
+
);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
function assertEvmChainId(chain, chainId) {
|
|
818
|
+
if (chain !== String(chainId)) {
|
|
819
|
+
throw new SwapSdkError(
|
|
820
|
+
"CHAIN_MISMATCH",
|
|
821
|
+
"build",
|
|
822
|
+
`EVM transaction chainId ${chainId} does not match ${chain}`
|
|
823
|
+
);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
function assertKind(actual, allowed) {
|
|
827
|
+
if (!allowed.includes(actual.toLowerCase())) {
|
|
828
|
+
throw new SwapSdkError(
|
|
829
|
+
"INVALID_API_RESPONSE",
|
|
830
|
+
"build",
|
|
831
|
+
`Unexpected transaction kind: ${actual}`
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
function normalizeOrder(raw, execution) {
|
|
836
|
+
const depositOrderId = readRecordString(raw.deposit, "orderId");
|
|
837
|
+
const orderId = raw.orderId ?? depositOrderId;
|
|
838
|
+
if (!orderId) return void 0;
|
|
839
|
+
const router = raw.statusRouter ?? (execution.kind === "evm-signature" ? execution.request.router : void 0) ?? raw.router;
|
|
840
|
+
const chainId = execution.kind === "evm-signature" ? String(execution.request.chainId) : execution.kind === "evm-transaction" ? String(execution.tx.chainId) : void 0;
|
|
841
|
+
return { orderId, router, ...chainId ? { chainId } : {} };
|
|
842
|
+
}
|
|
843
|
+
function normalizeDeposit(value) {
|
|
844
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
845
|
+
const depositAddress = readRecordString(value, "depositAddress");
|
|
846
|
+
if (!depositAddress) return void 0;
|
|
847
|
+
return {
|
|
848
|
+
depositAddress,
|
|
849
|
+
...readRecordString(value, "depositMemo") ? { depositMemo: readRecordString(value, "depositMemo") } : {},
|
|
850
|
+
...readRecordString(value, "orderId") ? { orderId: readRecordString(value, "orderId") } : {},
|
|
851
|
+
...readRecordString(value, "estimatedOut") ? { estimatedOut: decimalStringSchema.parse(readRecordString(value, "estimatedOut")) } : {},
|
|
852
|
+
...readRecordString(value, "minAmountOut") ? { minAmountOut: decimalStringSchema.parse(readRecordString(value, "minAmountOut")) } : {},
|
|
853
|
+
...typeof Reflect.get(value, "timeEstimate") === "number" || typeof Reflect.get(value, "timeEstimate") === "string" ? { timeEstimate: Reflect.get(value, "timeEstimate") } : {}
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
function readRecordString(value, key) {
|
|
857
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
858
|
+
const field = Reflect.get(value, key);
|
|
859
|
+
return typeof field === "string" && field.trim() ? field.trim() : void 0;
|
|
860
|
+
}
|
|
861
|
+
function createExecutionId() {
|
|
862
|
+
if (typeof globalThis.crypto?.randomUUID === "function") {
|
|
863
|
+
return globalThis.crypto.randomUUID();
|
|
864
|
+
}
|
|
865
|
+
return `swap_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
866
|
+
}
|
|
867
|
+
function invalidBuild(error) {
|
|
868
|
+
return new SwapSdkError(
|
|
869
|
+
"INVALID_API_RESPONSE",
|
|
870
|
+
"build",
|
|
871
|
+
"Invalid swap build response",
|
|
872
|
+
{
|
|
873
|
+
cause: error,
|
|
874
|
+
details: error instanceof zod.ZodError ? {
|
|
875
|
+
issues: error.issues.map((issue) => ({
|
|
876
|
+
path: issue.path.join("."),
|
|
877
|
+
message: issue.message
|
|
878
|
+
}))
|
|
879
|
+
} : void 0
|
|
880
|
+
}
|
|
881
|
+
);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// src/types/chain.ts
|
|
885
|
+
var BASE_UNIT_AMOUNT = /^(0|[1-9]\d*)$/;
|
|
886
|
+
function assertBaseUnitAmount(value) {
|
|
887
|
+
if (!BASE_UNIT_AMOUNT.test(value)) {
|
|
888
|
+
throw new SwapSdkError(
|
|
889
|
+
"INVALID_REQUEST",
|
|
890
|
+
"quote",
|
|
891
|
+
`Invalid base-unit amount: ${value}`
|
|
892
|
+
);
|
|
893
|
+
}
|
|
894
|
+
return value;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// src/normalizers/quote.ts
|
|
898
|
+
var DEFAULT_QUOTE_WAITING_TIME_MS = 3e3;
|
|
899
|
+
function serializeQuoteRequest(request) {
|
|
900
|
+
validateQuoteRequest(request);
|
|
901
|
+
return {
|
|
902
|
+
...request.extensions ?? {},
|
|
903
|
+
fromChain: toApiChain(request.fromChain),
|
|
904
|
+
toChain: toApiChain(request.toChain),
|
|
905
|
+
tokenIn: toApiAssetAddress(request.tokenIn),
|
|
906
|
+
tokenOut: toApiAssetAddress(request.tokenOut),
|
|
907
|
+
amountIn: request.amountIn,
|
|
908
|
+
slippage: request.slippageBps,
|
|
909
|
+
quoteWaitingTimeMs: request.quoteWaitingTimeMs ?? DEFAULT_QUOTE_WAITING_TIME_MS,
|
|
910
|
+
sender: request.sender.trim(),
|
|
911
|
+
...request.recipient?.trim() ? { recipient: request.recipient.trim() } : {}
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
function normalizeQuote(request, raw, receivedAt = Date.now()) {
|
|
915
|
+
const bestRoute = normalizeRoute(raw.bestQuote, true);
|
|
916
|
+
if (!bestRoute) {
|
|
917
|
+
throw invalidQuote("Quote response does not contain a valid best route");
|
|
918
|
+
}
|
|
919
|
+
const quoteId = readOptionalString(raw.bestQuote.quoteId);
|
|
920
|
+
const apiRequest = Object.freeze({ ...serializeQuoteRequest(request) });
|
|
921
|
+
const buildContext = Object.freeze({
|
|
922
|
+
request: apiRequest,
|
|
923
|
+
router: bestRoute.router,
|
|
924
|
+
...bestRoute.market ? { market: bestRoute.market } : {},
|
|
925
|
+
expectedOut: bestRoute.amountOut,
|
|
926
|
+
minAmountOut: bestRoute.minAmountOut,
|
|
927
|
+
preSwap: raw.bestQuote.preSwap ?? null,
|
|
928
|
+
bridge: raw.bestQuote.bridge ?? null,
|
|
929
|
+
...quoteId ? { quoteId } : {}
|
|
930
|
+
});
|
|
931
|
+
const expiresAt = readTimestamp(
|
|
932
|
+
raw.bestQuote.expiresAt ?? raw.bestQuote.expirationTime
|
|
933
|
+
);
|
|
934
|
+
return {
|
|
935
|
+
...quoteId ? { id: quoteId } : {},
|
|
936
|
+
fromChain: request.fromChain,
|
|
937
|
+
toChain: request.toChain,
|
|
938
|
+
tokenIn: { ...request.tokenIn },
|
|
939
|
+
tokenOut: { ...request.tokenOut },
|
|
940
|
+
amountIn: validateResponseAmount(request.amountIn, "amountIn"),
|
|
941
|
+
estimatedOut: bestRoute.amountOut,
|
|
942
|
+
minAmountOut: bestRoute.minAmountOut,
|
|
943
|
+
route: bestRoute,
|
|
944
|
+
alternatives: raw.allQuotes.flatMap((route) => {
|
|
945
|
+
try {
|
|
946
|
+
const normalized = normalizeRoute(route, false);
|
|
947
|
+
return normalized ? [normalized] : [];
|
|
948
|
+
} catch {
|
|
949
|
+
return [];
|
|
950
|
+
}
|
|
951
|
+
}),
|
|
952
|
+
receivedAt,
|
|
953
|
+
...expiresAt === void 0 ? {} : { expiresAt },
|
|
954
|
+
buildContext,
|
|
955
|
+
raw
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
function validateQuoteRequest(request) {
|
|
959
|
+
assertBaseUnitAmount(request.amountIn);
|
|
960
|
+
if (!Number.isInteger(request.slippageBps) || request.slippageBps < 0) {
|
|
961
|
+
throw new SwapSdkError(
|
|
962
|
+
"INVALID_REQUEST",
|
|
963
|
+
"quote",
|
|
964
|
+
"slippageBps must be a non-negative integer"
|
|
965
|
+
);
|
|
966
|
+
}
|
|
967
|
+
if (request.slippageBps > 1e4) {
|
|
968
|
+
throw new SwapSdkError(
|
|
969
|
+
"INVALID_REQUEST",
|
|
970
|
+
"quote",
|
|
971
|
+
"slippageBps cannot exceed 10000"
|
|
972
|
+
);
|
|
973
|
+
}
|
|
974
|
+
if (request.quoteWaitingTimeMs !== void 0 && (!Number.isFinite(request.quoteWaitingTimeMs) || !Number.isInteger(request.quoteWaitingTimeMs) || request.quoteWaitingTimeMs < 0)) {
|
|
975
|
+
throw new SwapSdkError(
|
|
976
|
+
"INVALID_REQUEST",
|
|
977
|
+
"quote",
|
|
978
|
+
"quoteWaitingTimeMs must be a non-negative integer"
|
|
979
|
+
);
|
|
980
|
+
}
|
|
981
|
+
if (!request.sender.trim()) {
|
|
982
|
+
throw new SwapSdkError(
|
|
983
|
+
"INVALID_REQUEST",
|
|
984
|
+
"quote",
|
|
985
|
+
"sender is required"
|
|
986
|
+
);
|
|
987
|
+
}
|
|
988
|
+
if (request.tokenIn.chain !== request.fromChain) {
|
|
989
|
+
throw new SwapSdkError(
|
|
990
|
+
"CHAIN_MISMATCH",
|
|
991
|
+
"quote",
|
|
992
|
+
"tokenIn chain does not match fromChain"
|
|
993
|
+
);
|
|
994
|
+
}
|
|
995
|
+
if (request.tokenOut.chain !== request.toChain) {
|
|
996
|
+
throw new SwapSdkError(
|
|
997
|
+
"CHAIN_MISMATCH",
|
|
998
|
+
"quote",
|
|
999
|
+
"tokenOut chain does not match toChain"
|
|
1000
|
+
);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
function normalizeRoute(raw, required) {
|
|
1004
|
+
const router = readOptionalString(raw.router);
|
|
1005
|
+
const amountOutValue = raw.amountOut ?? raw.estimatedOut;
|
|
1006
|
+
const amountOut = readOptionalString(amountOutValue);
|
|
1007
|
+
const minAmountOut = readOptionalString(raw.minAmountOut) ?? amountOut;
|
|
1008
|
+
if (!router || !amountOut || !minAmountOut) {
|
|
1009
|
+
if (required) throw invalidQuote("Quote response is missing route fields");
|
|
1010
|
+
return void 0;
|
|
1011
|
+
}
|
|
1012
|
+
return {
|
|
1013
|
+
router,
|
|
1014
|
+
...readOptionalString(raw.market) ? { market: readOptionalString(raw.market) } : {},
|
|
1015
|
+
amountOut: validateResponseAmount(amountOut, "amountOut"),
|
|
1016
|
+
minAmountOut: validateResponseAmount(minAmountOut, "minAmountOut"),
|
|
1017
|
+
raw
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
function validateResponseAmount(value, field) {
|
|
1021
|
+
try {
|
|
1022
|
+
return assertBaseUnitAmount(value);
|
|
1023
|
+
} catch (error) {
|
|
1024
|
+
throw invalidQuote(`Quote response has invalid ${field}`, error);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
function readOptionalString(value) {
|
|
1028
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
1029
|
+
}
|
|
1030
|
+
function readTimestamp(value) {
|
|
1031
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
1032
|
+
if (typeof value === "string" && /^\d+$/.test(value)) return Number(value);
|
|
1033
|
+
return void 0;
|
|
1034
|
+
}
|
|
1035
|
+
function invalidQuote(message, cause) {
|
|
1036
|
+
return new SwapSdkError("INVALID_API_RESPONSE", "quote", message, {
|
|
1037
|
+
cause
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
var historyRecordSchema = zod.z.object({
|
|
1041
|
+
id: zod.z.number().int(),
|
|
1042
|
+
sender: zod.z.string(),
|
|
1043
|
+
recipient: zod.z.unknown().optional(),
|
|
1044
|
+
from_hash: zod.z.string(),
|
|
1045
|
+
to_hash: zod.z.string().nullable().optional(),
|
|
1046
|
+
deposit_address: zod.z.string().optional(),
|
|
1047
|
+
from_token: zod.z.string(),
|
|
1048
|
+
to_token: zod.z.string(),
|
|
1049
|
+
from_chain: zod.z.string(),
|
|
1050
|
+
to_chain: zod.z.string(),
|
|
1051
|
+
amount_in: decimalStringSchema.optional(),
|
|
1052
|
+
estimated_out: decimalStringSchema.optional(),
|
|
1053
|
+
actual_out: decimalStringSchema.nullable().optional(),
|
|
1054
|
+
router: zod.z.string().optional(),
|
|
1055
|
+
status: zod.z.string().optional(),
|
|
1056
|
+
swap_id: zod.z.string().nullable().optional(),
|
|
1057
|
+
status_response: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
|
|
1058
|
+
created_at: zod.z.string().optional(),
|
|
1059
|
+
updated_at: zod.z.string().optional()
|
|
1060
|
+
}).passthrough();
|
|
1061
|
+
var historyPageSchema = zod.z.object({
|
|
1062
|
+
record_list: zod.z.array(historyRecordSchema),
|
|
1063
|
+
page_number: zod.z.number().int().nonnegative(),
|
|
1064
|
+
page_size: zod.z.number().int().nonnegative(),
|
|
1065
|
+
total_page: zod.z.number().int().nonnegative(),
|
|
1066
|
+
total_size: zod.z.number().int().nonnegative()
|
|
1067
|
+
});
|
|
1068
|
+
var HISTORY_STATUS_MAP = {
|
|
1069
|
+
PENDING: "pending",
|
|
1070
|
+
CREATED: "pending",
|
|
1071
|
+
PROCESSING: "processing",
|
|
1072
|
+
IN_PROGRESS: "processing",
|
|
1073
|
+
SUCCESS: "completed",
|
|
1074
|
+
COMPLETED: "completed",
|
|
1075
|
+
FILLED: "completed",
|
|
1076
|
+
FAILED: "failed",
|
|
1077
|
+
REFUNDED: "refunded",
|
|
1078
|
+
EXPIRED: "expired"
|
|
1079
|
+
};
|
|
1080
|
+
var NATIVE_ASSET_IDS = {
|
|
1081
|
+
solana: [
|
|
1082
|
+
"sol",
|
|
1083
|
+
"So11111111111111111111111111111111111111112"
|
|
1084
|
+
],
|
|
1085
|
+
aptos: ["0xa", "0x1::aptos_coin::AptosCoin"],
|
|
1086
|
+
near: ["near", "wrap.near"],
|
|
1087
|
+
tron: ["trx"],
|
|
1088
|
+
btc: ["btc", "bitcoin"],
|
|
1089
|
+
zcash: ["zec", "zcash", "nep141:zec.omft.near"],
|
|
1090
|
+
sui: ["0x2::sui::SUI"]
|
|
1091
|
+
};
|
|
1092
|
+
function normalizeHistory(raw) {
|
|
1093
|
+
const parsed = historyPageSchema.safeParse(raw);
|
|
1094
|
+
if (!parsed.success) {
|
|
1095
|
+
throw new SwapSdkError(
|
|
1096
|
+
"INVALID_API_RESPONSE",
|
|
1097
|
+
"history",
|
|
1098
|
+
"Invalid swap history response",
|
|
1099
|
+
{
|
|
1100
|
+
details: {
|
|
1101
|
+
issues: parsed.error.issues.map((issue) => ({
|
|
1102
|
+
path: issue.path.join("."),
|
|
1103
|
+
message: issue.message
|
|
1104
|
+
}))
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
);
|
|
1108
|
+
}
|
|
1109
|
+
return {
|
|
1110
|
+
items: raw.record_list.map(normalizeHistoryItem),
|
|
1111
|
+
page: raw.page_number,
|
|
1112
|
+
pageSize: raw.page_size,
|
|
1113
|
+
totalPages: raw.total_page,
|
|
1114
|
+
totalItems: raw.total_size
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
function normalizeHistoryStatus(status) {
|
|
1118
|
+
if (typeof status !== "string") return "unknown";
|
|
1119
|
+
return HISTORY_STATUS_MAP[status.trim().toUpperCase()] ?? "unknown";
|
|
1120
|
+
}
|
|
1121
|
+
function normalizeHistoryItem(raw) {
|
|
1122
|
+
const fromChain = fromApiChain(raw.from_chain);
|
|
1123
|
+
const toChain = fromApiChain(raw.to_chain);
|
|
1124
|
+
const createdAt = normalizeTimestamp(raw.created_at);
|
|
1125
|
+
const updatedAt = normalizeTimestamp(raw.updated_at);
|
|
1126
|
+
return {
|
|
1127
|
+
id: String(raw.id),
|
|
1128
|
+
sender: raw.sender,
|
|
1129
|
+
...typeof raw.recipient === "string" && raw.recipient ? { recipient: raw.recipient } : {},
|
|
1130
|
+
fromChain,
|
|
1131
|
+
toChain,
|
|
1132
|
+
tokenIn: normalizeHistoryAsset(fromChain, raw.from_token),
|
|
1133
|
+
tokenOut: normalizeHistoryAsset(toChain, raw.to_token),
|
|
1134
|
+
...raw.amount_in !== void 0 ? { amountIn: raw.amount_in } : {},
|
|
1135
|
+
...raw.estimated_out !== void 0 ? { estimatedOut: raw.estimated_out } : {},
|
|
1136
|
+
...raw.actual_out !== void 0 && raw.actual_out !== null ? { actualOut: raw.actual_out } : {},
|
|
1137
|
+
...raw.from_hash ? { sourceTxHash: raw.from_hash } : {},
|
|
1138
|
+
...raw.to_hash ? { destinationTxHash: raw.to_hash } : {},
|
|
1139
|
+
...raw.swap_id ? { orderId: raw.swap_id } : {},
|
|
1140
|
+
...raw.deposit_address ? { depositAddress: raw.deposit_address } : {},
|
|
1141
|
+
...raw.router ? { router: raw.router } : {},
|
|
1142
|
+
status: normalizeHistoryStatus(raw.status),
|
|
1143
|
+
...createdAt ? { createdAt } : {},
|
|
1144
|
+
...updatedAt ? { updatedAt } : {},
|
|
1145
|
+
...raw.status_response ? { statusResponse: raw.status_response } : {},
|
|
1146
|
+
raw
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
function normalizeHistoryAsset(chain, address) {
|
|
1150
|
+
return {
|
|
1151
|
+
chain,
|
|
1152
|
+
address,
|
|
1153
|
+
...isNativeAsset(chain, address) ? { isNative: true } : {}
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1156
|
+
function isNativeAsset(chain, address) {
|
|
1157
|
+
const normalized = address.trim().toLowerCase();
|
|
1158
|
+
if (/^[1-9]\d*$/.test(chain) && (normalized === "0x0000000000000000000000000000000000000000" || normalized === "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee")) {
|
|
1159
|
+
return true;
|
|
1160
|
+
}
|
|
1161
|
+
return (NATIVE_ASSET_IDS[chain] ?? []).some(
|
|
1162
|
+
(candidate) => candidate.toLowerCase() === normalized
|
|
1163
|
+
);
|
|
1164
|
+
}
|
|
1165
|
+
function normalizeTimestamp(value) {
|
|
1166
|
+
if (!value?.trim()) return void 0;
|
|
1167
|
+
const trimmed = value.trim();
|
|
1168
|
+
const normalized = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d+)?$/.test(
|
|
1169
|
+
trimmed
|
|
1170
|
+
) ? `${trimmed.replace(" ", "T")}Z` : trimmed;
|
|
1171
|
+
const date = new Date(normalized);
|
|
1172
|
+
return Number.isNaN(date.getTime()) ? void 0 : date.toISOString();
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
// src/mca/quote.ts
|
|
1176
|
+
function serializeMcaQuoteRequest(request, signer) {
|
|
1177
|
+
const mcaAccountId = request.mcaAccountId.trim();
|
|
1178
|
+
if (!mcaAccountId) {
|
|
1179
|
+
throw invalidRequest("mcaAccountId is required");
|
|
1180
|
+
}
|
|
1181
|
+
if (signer.chain !== request.signerChain) {
|
|
1182
|
+
throw invalidRequest(
|
|
1183
|
+
`Resolved signer chain ${signer.chain} does not match requested signer chain ${request.signerChain}`
|
|
1184
|
+
);
|
|
1185
|
+
}
|
|
1186
|
+
const identityKey = signer.identityKey.trim();
|
|
1187
|
+
if (!identityKey) {
|
|
1188
|
+
throw invalidRequest("signer identityKey is required");
|
|
1189
|
+
}
|
|
1190
|
+
if (request.flow === "withdraw" && !/^(?:0|[1-9]\d*)(?:\.\d+)?$/.test(
|
|
1191
|
+
request.collateral.decreaseAmountBurrow.trim()
|
|
1192
|
+
)) {
|
|
1193
|
+
throw invalidRequest(
|
|
1194
|
+
"collateral.decreaseAmountBurrow must be a non-negative decimal string"
|
|
1195
|
+
);
|
|
1196
|
+
}
|
|
1197
|
+
const mca = {
|
|
1198
|
+
flow: request.flow,
|
|
1199
|
+
mcaAccountId,
|
|
1200
|
+
signer: {
|
|
1201
|
+
chain: signer.chain,
|
|
1202
|
+
identityKey
|
|
1203
|
+
},
|
|
1204
|
+
...request.flow === "deposit" ? { useAsCollateral: request.collateral.useAsCollateral } : {
|
|
1205
|
+
needDecreaseCollateral: request.collateral.needDecrease,
|
|
1206
|
+
decreaseCollateralAmountBurrow: request.collateral.decreaseAmountBurrow,
|
|
1207
|
+
...request.collateral.withdrawAll ? { withdrawAll: true } : {}
|
|
1208
|
+
},
|
|
1209
|
+
...request.recipientMsgSignatures ? { recipientMsgSignatures: [...request.recipientMsgSignatures] } : {},
|
|
1210
|
+
...request.depositSignerProofSignatures ? {
|
|
1211
|
+
depositSignerProofSignatures: [
|
|
1212
|
+
...request.depositSignerProofSignatures
|
|
1213
|
+
]
|
|
1214
|
+
} : {}
|
|
1215
|
+
};
|
|
1216
|
+
return {
|
|
1217
|
+
...serializeQuoteRequest(request),
|
|
1218
|
+
mca
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
function normalizeMcaQuote(request, signer, quote) {
|
|
1222
|
+
const mca = serializeMcaQuoteRequest(request, signer).mca;
|
|
1223
|
+
const base = {
|
|
1224
|
+
...quote,
|
|
1225
|
+
flow: request.flow,
|
|
1226
|
+
mcaAccountId: request.mcaAccountId.trim(),
|
|
1227
|
+
signer: Object.freeze({
|
|
1228
|
+
...signer,
|
|
1229
|
+
identityKey: signer.identityKey.trim()
|
|
1230
|
+
}),
|
|
1231
|
+
request,
|
|
1232
|
+
mca: Object.freeze({ ...mca })
|
|
1233
|
+
};
|
|
1234
|
+
if (request.flow === "deposit") {
|
|
1235
|
+
if (readString(quote.raw.nearDepositTxError)) {
|
|
1236
|
+
throw invalidResponse(String(quote.raw.nearDepositTxError));
|
|
1237
|
+
}
|
|
1238
|
+
return {
|
|
1239
|
+
...base,
|
|
1240
|
+
flow: "deposit",
|
|
1241
|
+
executionMode: "deposit",
|
|
1242
|
+
request,
|
|
1243
|
+
...quote.raw.nearDepositTx === void 0 ? {} : { nearDepositTx: quote.raw.nearDepositTx }
|
|
1244
|
+
};
|
|
1245
|
+
}
|
|
1246
|
+
if (readString(quote.raw.nearMcaWithdrawTxError)) {
|
|
1247
|
+
throw invalidResponse(String(quote.raw.nearMcaWithdrawTxError));
|
|
1248
|
+
}
|
|
1249
|
+
const mode = resolveWithdrawMode(request);
|
|
1250
|
+
const preview = mode === "withdraw-near" ? quote.raw.nearMcaWithdrawTx ?? quote.raw.mcaWithdrawToIntents : quote.raw.mcaWithdrawToIntents;
|
|
1251
|
+
if (!isPlainObject(preview)) {
|
|
1252
|
+
throw invalidResponse(
|
|
1253
|
+
mode === "withdraw-near" ? "MCA withdraw quote is missing nearMcaWithdrawTx" : "MCA withdraw quote is missing mcaWithdrawToIntents"
|
|
1254
|
+
);
|
|
1255
|
+
}
|
|
1256
|
+
return {
|
|
1257
|
+
...base,
|
|
1258
|
+
flow: "withdraw",
|
|
1259
|
+
executionMode: mode,
|
|
1260
|
+
request,
|
|
1261
|
+
preview
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
function resolveWithdrawMode(request) {
|
|
1265
|
+
if (request.executionPreference === "near") return "withdraw-near";
|
|
1266
|
+
if (request.executionPreference === "relayer") return "withdraw-relayer";
|
|
1267
|
+
const recipient = request.recipient?.trim();
|
|
1268
|
+
const boundNearAccountId = request.boundNearAccountId?.trim();
|
|
1269
|
+
return request.toChain === "near" && Boolean(recipient) && recipient === boundNearAccountId ? "withdraw-near" : "withdraw-relayer";
|
|
1270
|
+
}
|
|
1271
|
+
function isPlainObject(value) {
|
|
1272
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1273
|
+
}
|
|
1274
|
+
function readString(value) {
|
|
1275
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
1276
|
+
}
|
|
1277
|
+
function invalidRequest(message) {
|
|
1278
|
+
return new SwapSdkError("INVALID_REQUEST", "quote", message);
|
|
1279
|
+
}
|
|
1280
|
+
function invalidResponse(message) {
|
|
1281
|
+
return new SwapSdkError("INVALID_API_RESPONSE", "quote", message);
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
// src/mca/signers.ts
|
|
1285
|
+
var DEFAULT_MCA_SIGNER_PRIORITY = [
|
|
1286
|
+
"evm",
|
|
1287
|
+
"solana",
|
|
1288
|
+
"btc",
|
|
1289
|
+
"aptos",
|
|
1290
|
+
"near",
|
|
1291
|
+
"zcash",
|
|
1292
|
+
"sui",
|
|
1293
|
+
"tron"
|
|
1294
|
+
];
|
|
1295
|
+
var WALLET_KEYS = {
|
|
1296
|
+
evm: "EVM",
|
|
1297
|
+
solana: "Solana",
|
|
1298
|
+
btc: "Bitcoin",
|
|
1299
|
+
near: "Near",
|
|
1300
|
+
aptos: "Aptos",
|
|
1301
|
+
sui: "Sui",
|
|
1302
|
+
zcash: "Zcash",
|
|
1303
|
+
tron: "Tron"
|
|
1304
|
+
};
|
|
1305
|
+
function formatMcaWallet(chain, identityKey) {
|
|
1306
|
+
const identity = identityKey.trim();
|
|
1307
|
+
if (!identity) {
|
|
1308
|
+
throw new SwapSdkError(
|
|
1309
|
+
"INVALID_REQUEST",
|
|
1310
|
+
"sign",
|
|
1311
|
+
"MCA signer identityKey is required"
|
|
1312
|
+
);
|
|
1313
|
+
}
|
|
1314
|
+
return {
|
|
1315
|
+
[WALLET_KEYS[chain]]: chain === "evm" ? identity.replace(/^0x/i, "") : identity
|
|
1316
|
+
};
|
|
1317
|
+
}
|
|
1318
|
+
function selectMcaSigner(boundWallets, connectedSigners, priority = DEFAULT_MCA_SIGNER_PRIORITY) {
|
|
1319
|
+
for (const chain of priority) {
|
|
1320
|
+
const key = WALLET_KEYS[chain];
|
|
1321
|
+
const boundValues = boundWallets.flatMap((wallet) => {
|
|
1322
|
+
const value = wallet[key];
|
|
1323
|
+
return typeof value === "string" && value.trim() ? [value.trim()] : [];
|
|
1324
|
+
});
|
|
1325
|
+
if (boundValues.length === 0) continue;
|
|
1326
|
+
for (const signer of connectedSigners) {
|
|
1327
|
+
if (signer.chain !== chain) continue;
|
|
1328
|
+
const candidates = [signer.identityKey, signer.accountId].filter((value) => Boolean(value?.trim())).map((value) => normalizeIdentity(chain, value));
|
|
1329
|
+
if (boundValues.some(
|
|
1330
|
+
(bound) => candidates.includes(normalizeIdentity(chain, bound))
|
|
1331
|
+
)) {
|
|
1332
|
+
return signer;
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
return void 0;
|
|
1337
|
+
}
|
|
1338
|
+
function isSameMcaSignerIdentity(a, b) {
|
|
1339
|
+
return a.chain === b.chain && normalizeIdentity(a.chain, a.identityKey) === normalizeIdentity(b.chain, b.identityKey);
|
|
1340
|
+
}
|
|
1341
|
+
function normalizeIdentity(chain, value) {
|
|
1342
|
+
const trimmed = value.trim();
|
|
1343
|
+
if (chain === "evm") return trimmed.replace(/^0x/i, "").toLowerCase();
|
|
1344
|
+
if (chain === "sui" || chain === "aptos") return trimmed.toLowerCase();
|
|
1345
|
+
return trimmed;
|
|
1346
|
+
}
|
|
6
1347
|
|
|
7
|
-
|
|
1348
|
+
// src/utils/units.ts
|
|
1349
|
+
var HUMAN_AMOUNT = /^(0|[1-9]\d*)(?:\.(\d+))?$/;
|
|
1350
|
+
var MAX_DECIMALS = 255;
|
|
1351
|
+
function parseUnits(value, decimals) {
|
|
1352
|
+
assertDecimals(decimals);
|
|
1353
|
+
const match = HUMAN_AMOUNT.exec(value);
|
|
1354
|
+
if (!match) {
|
|
1355
|
+
throw invalid(`Invalid human-readable amount: ${value}`);
|
|
1356
|
+
}
|
|
1357
|
+
const whole = match[1];
|
|
1358
|
+
const fraction = match[2] ?? "";
|
|
1359
|
+
if (fraction.length > decimals) {
|
|
1360
|
+
throw invalid(
|
|
1361
|
+
`Amount has ${fraction.length} fractional digits but token supports ${decimals}`
|
|
1362
|
+
);
|
|
1363
|
+
}
|
|
1364
|
+
const combined = `${whole}${fraction.padEnd(decimals, "0")}`;
|
|
1365
|
+
return combined.replace(/^0+(?=\d)/, "") || "0";
|
|
1366
|
+
}
|
|
1367
|
+
function formatUnits(value, decimals) {
|
|
1368
|
+
assertDecimals(decimals);
|
|
1369
|
+
const amount = assertBaseUnitAmount(value);
|
|
1370
|
+
if (decimals === 0) return amount;
|
|
1371
|
+
const padded = amount.padStart(decimals + 1, "0");
|
|
1372
|
+
const whole = padded.slice(0, -decimals);
|
|
1373
|
+
const fraction = padded.slice(-decimals).replace(/0+$/, "");
|
|
1374
|
+
return fraction ? `${whole}.${fraction}` : whole;
|
|
1375
|
+
}
|
|
1376
|
+
function assertDecimals(decimals) {
|
|
1377
|
+
if (!Number.isSafeInteger(decimals) || decimals < 0 || decimals > MAX_DECIMALS) {
|
|
1378
|
+
throw invalid(`Invalid token decimals: ${String(decimals)}`);
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
function invalid(message) {
|
|
1382
|
+
return new SwapSdkError("INVALID_REQUEST", "quote", message);
|
|
1383
|
+
}
|
|
8
1384
|
|
|
9
|
-
// src/
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const level = process.env.LOG_LEVEL.toLowerCase();
|
|
20
|
-
if (LOG_LEVELS[level] !== void 0) {
|
|
21
|
-
return level;
|
|
22
|
-
}
|
|
1385
|
+
// src/mca/withdraw.ts
|
|
1386
|
+
function extractMcaWithdrawBusiness(preview) {
|
|
1387
|
+
if (!isPlainObject2(preview)) return void 0;
|
|
1388
|
+
if (isPlainObject2(preview.business)) return cloneObject(preview.business);
|
|
1389
|
+
return extractPreviewField(preview, "business");
|
|
1390
|
+
}
|
|
1391
|
+
function extractMcaWithdrawSignerWallet(preview) {
|
|
1392
|
+
if (!isPlainObject2(preview)) return void 0;
|
|
1393
|
+
if (isPlainObject2(preview.signer_wallet)) {
|
|
1394
|
+
return cloneObject(preview.signer_wallet);
|
|
23
1395
|
}
|
|
24
|
-
return
|
|
1396
|
+
return extractPreviewField(preview, "signer_wallet");
|
|
25
1397
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
function shouldLog(level) {
|
|
29
|
-
return LOG_LEVELS[level] <= currentLevelValue;
|
|
1398
|
+
function extractMcaWithdrawDepositAddress(input) {
|
|
1399
|
+
return pickString(input.snapshotDepositAddress) || pickString(input.storeDepositAddress) || extractDepositAddressFromObject(input.preview) || extractDepositAddressFromObject(input.bestQuote);
|
|
30
1400
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
1401
|
+
function buildNearMcaWithdrawTransactions(preview, input) {
|
|
1402
|
+
if (!isPlainObject2(preview)) {
|
|
1403
|
+
throw invalidPreview("MCA withdraw preview must be an object");
|
|
1404
|
+
}
|
|
1405
|
+
const fallbackAccount = input.mcaAccountId.trim();
|
|
1406
|
+
const executionArgs = {
|
|
1407
|
+
business: cloneObject(input.business),
|
|
1408
|
+
signer_wallet: cloneObject(input.signerWallet),
|
|
1409
|
+
signature: input.signature ?? ""
|
|
1410
|
+
};
|
|
1411
|
+
const transactions = Array.isArray(preview.transactions) ? preview.transactions : [];
|
|
1412
|
+
const mapped = transactions.flatMap((value) => {
|
|
1413
|
+
if (!isPlainObject2(value)) return [];
|
|
1414
|
+
const receiverId = pickString(value.contractId) || fallbackAccount;
|
|
1415
|
+
if (!receiverId) return [];
|
|
1416
|
+
const methodName = pickString(value.methodName) || "exec";
|
|
1417
|
+
const existingArgs = isPlainObject2(value.args) ? cloneObject(value.args) : {};
|
|
1418
|
+
return [
|
|
1419
|
+
{
|
|
1420
|
+
receiverId,
|
|
1421
|
+
actions: [
|
|
1422
|
+
{
|
|
1423
|
+
type: "FunctionCall",
|
|
1424
|
+
params: {
|
|
1425
|
+
methodName,
|
|
1426
|
+
args: { ...existingArgs, ...executionArgs },
|
|
1427
|
+
gas: normalizeGas(value.gas),
|
|
1428
|
+
deposit: normalizeDeposit2(value.deposit)
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
]
|
|
1432
|
+
}
|
|
1433
|
+
];
|
|
1434
|
+
});
|
|
1435
|
+
if (mapped.length > 0) return mapped;
|
|
1436
|
+
if (!fallbackAccount) {
|
|
1437
|
+
throw invalidPreview("MCA withdraw preview is missing mcaAccountId");
|
|
1438
|
+
}
|
|
1439
|
+
return [
|
|
1440
|
+
{
|
|
1441
|
+
receiverId: fallbackAccount,
|
|
1442
|
+
actions: [
|
|
1443
|
+
{
|
|
1444
|
+
type: "FunctionCall",
|
|
1445
|
+
params: {
|
|
1446
|
+
methodName: "exec",
|
|
1447
|
+
args: executionArgs,
|
|
1448
|
+
gas: "300000000000000",
|
|
1449
|
+
deposit: "0"
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
]
|
|
40
1453
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
1454
|
+
];
|
|
1455
|
+
}
|
|
1456
|
+
function buildMcaWithdrawRelayerRequest(input) {
|
|
1457
|
+
const mcaAccountId = input.mcaAccountId.trim();
|
|
1458
|
+
if (!mcaAccountId) {
|
|
1459
|
+
throw new SwapSdkError(
|
|
1460
|
+
"INVALID_REQUEST",
|
|
1461
|
+
"build",
|
|
1462
|
+
"mcaAccountId is required for an MCA relayer request"
|
|
1463
|
+
);
|
|
1464
|
+
}
|
|
1465
|
+
const recipient = pickString(input.quoteBuild.recipient) || pickString(input.recipientFallback);
|
|
1466
|
+
return {
|
|
1467
|
+
...input.quoteBuild,
|
|
1468
|
+
...recipient ? { recipient } : {},
|
|
1469
|
+
mcaRelayer: {
|
|
1470
|
+
...input.mcaRelayer,
|
|
1471
|
+
wallet: cloneValue(input.mcaRelayer.wallet),
|
|
1472
|
+
business: cloneObject(input.mcaRelayer.business)
|
|
1473
|
+
},
|
|
1474
|
+
mca: {
|
|
1475
|
+
...input.quoteBuild.mca ?? {},
|
|
1476
|
+
flow: input.quoteBuild.mca?.flow ?? input.quoteBuild.mca?.mcaFlow ?? "withdraw",
|
|
1477
|
+
mcaAccountId
|
|
1478
|
+
},
|
|
1479
|
+
deposit_address: input.depositAddress.trim(),
|
|
1480
|
+
is_cross_chain: input.isCrossChain,
|
|
1481
|
+
tx_type: "mca-withdraw-relayer",
|
|
1482
|
+
multi_addr: mcaAccountId
|
|
1483
|
+
};
|
|
1484
|
+
}
|
|
1485
|
+
function extractPreviewField(preview, field) {
|
|
1486
|
+
const transactions = preview.transactions;
|
|
1487
|
+
if (Array.isArray(transactions)) {
|
|
1488
|
+
for (const transaction of transactions) {
|
|
1489
|
+
if (!isPlainObject2(transaction) || !isPlainObject2(transaction.args)) {
|
|
1490
|
+
continue;
|
|
1491
|
+
}
|
|
1492
|
+
const value = parseObject(transaction.args[field]);
|
|
1493
|
+
if (value) return cloneObject(value);
|
|
45
1494
|
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
1495
|
+
}
|
|
1496
|
+
const actions = preview.actions;
|
|
1497
|
+
if (Array.isArray(actions)) {
|
|
1498
|
+
for (const action of actions) {
|
|
1499
|
+
if (!isPlainObject2(action) || action.type !== "FunctionCall" || !isPlainObject2(action.params)) {
|
|
1500
|
+
continue;
|
|
1501
|
+
}
|
|
1502
|
+
const args = parseObject(action.params.args);
|
|
1503
|
+
if (!args) continue;
|
|
1504
|
+
const value = parseObject(args[field]);
|
|
1505
|
+
if (value) return cloneObject(value);
|
|
50
1506
|
}
|
|
51
1507
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
1508
|
+
return void 0;
|
|
1509
|
+
}
|
|
1510
|
+
function extractDepositAddressFromObject(value) {
|
|
1511
|
+
if (!isPlainObject2(value)) return "";
|
|
1512
|
+
const direct = pickString(value.depositAddress) || pickString(value.deposit_address);
|
|
1513
|
+
if (direct) return direct;
|
|
1514
|
+
if (!isPlainObject2(value.deposit)) return "";
|
|
1515
|
+
return pickString(value.deposit.depositAddress) || pickString(value.deposit.deposit_address);
|
|
1516
|
+
}
|
|
1517
|
+
function parseObject(value) {
|
|
1518
|
+
if (isPlainObject2(value)) return value;
|
|
1519
|
+
if (typeof value !== "string" || !value.trim()) return void 0;
|
|
1520
|
+
try {
|
|
1521
|
+
const parsed = JSON.parse(value);
|
|
1522
|
+
return isPlainObject2(parsed) ? parsed : void 0;
|
|
1523
|
+
} catch {
|
|
1524
|
+
return void 0;
|
|
65
1525
|
}
|
|
66
|
-
return bluechipTokensConfig;
|
|
67
1526
|
}
|
|
68
|
-
function
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
1527
|
+
function isPlainObject2(value) {
|
|
1528
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1529
|
+
}
|
|
1530
|
+
function pickString(value) {
|
|
1531
|
+
return typeof value === "string" && value.trim() ? value.trim() : "";
|
|
1532
|
+
}
|
|
1533
|
+
function normalizeGas(value) {
|
|
1534
|
+
if (typeof value === "number" && Number.isFinite(value) && value >= 0) {
|
|
1535
|
+
return BigInt(Math.trunc(value)) < 10000n ? (BigInt(Math.trunc(value)) * 1000000000000n).toString() : BigInt(Math.trunc(value)).toString();
|
|
72
1536
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
1537
|
+
if (typeof value === "string" && /^\d+$/.test(value.trim())) {
|
|
1538
|
+
const gas = BigInt(value.trim());
|
|
1539
|
+
return gas < 10000n ? (gas * 1000000000000n).toString() : gas.toString();
|
|
76
1540
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
1541
|
+
return "300000000000000";
|
|
1542
|
+
}
|
|
1543
|
+
function normalizeDeposit2(value) {
|
|
1544
|
+
if (value === void 0 || value === null || value === "") return "0";
|
|
1545
|
+
const decimal = typeof value === "number" && Number.isFinite(value) && value >= 0 ? String(value) : typeof value === "string" ? value.trim() : "";
|
|
1546
|
+
if (!decimal) {
|
|
1547
|
+
throw invalidPreview("MCA withdraw transaction has an invalid NEAR deposit");
|
|
1548
|
+
}
|
|
1549
|
+
try {
|
|
1550
|
+
return parseUnits(decimal, 24);
|
|
1551
|
+
} catch (error) {
|
|
1552
|
+
throw new SwapSdkError(
|
|
1553
|
+
"INVALID_API_RESPONSE",
|
|
1554
|
+
"build",
|
|
1555
|
+
"MCA withdraw transaction has an invalid NEAR deposit",
|
|
1556
|
+
{ cause: error }
|
|
1557
|
+
);
|
|
82
1558
|
}
|
|
83
|
-
return normalized;
|
|
84
1559
|
}
|
|
85
|
-
function
|
|
86
|
-
|
|
87
|
-
|
|
1560
|
+
function cloneObject(value) {
|
|
1561
|
+
return cloneValue(value);
|
|
1562
|
+
}
|
|
1563
|
+
function cloneValue(value) {
|
|
1564
|
+
return JSON.parse(JSON.stringify(value));
|
|
1565
|
+
}
|
|
1566
|
+
function invalidPreview(message) {
|
|
1567
|
+
return new SwapSdkError("INVALID_API_RESPONSE", "build", message);
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
// src/mca/McaSwapService.ts
|
|
1571
|
+
var McaSwapService = class {
|
|
1572
|
+
constructor(client, config) {
|
|
1573
|
+
this.client = client;
|
|
1574
|
+
this.relayerReports = /* @__PURE__ */ new Map();
|
|
1575
|
+
this.now = config.now ?? Date.now;
|
|
1576
|
+
this.maxQuoteAgeMs = config.maxQuoteAgeMs === void 0 ? 3e4 : config.maxQuoteAgeMs;
|
|
1577
|
+
this.reportMode = config.reportMode ?? "auto";
|
|
1578
|
+
this.onEvent = config.onEvent;
|
|
1579
|
+
this.resolveSignerIdentity = config.resolveSignerIdentity;
|
|
1580
|
+
this.resolveMessageSigner = config.resolveMessageSigner;
|
|
1581
|
+
this.buildStandardSwap = config.buildStandardSwap;
|
|
88
1582
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
1583
|
+
async quote(request, options = {}) {
|
|
1584
|
+
const signer = await this.readSignerIdentity(request.signerChain, "quote");
|
|
1585
|
+
const rawRequest = serializeMcaQuoteRequest(request, signer);
|
|
1586
|
+
const raw = await this.client.quoteRaw(rawRequest, options);
|
|
1587
|
+
const normalizationRequest = {
|
|
1588
|
+
...request,
|
|
1589
|
+
extensions: {
|
|
1590
|
+
...request.extensions ?? {},
|
|
1591
|
+
mca: rawRequest.mca
|
|
1592
|
+
}
|
|
1593
|
+
};
|
|
1594
|
+
const quote = normalizeQuote(normalizationRequest, raw, this.now());
|
|
1595
|
+
return normalizeMcaQuote(request, signer, quote);
|
|
95
1596
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
1597
|
+
async build(input) {
|
|
1598
|
+
this.assertQuoteFresh(input.quote);
|
|
1599
|
+
if (input.quote.executionMode === "withdraw-relayer") {
|
|
1600
|
+
throw new SwapSdkError(
|
|
1601
|
+
"INVALID_REQUEST",
|
|
1602
|
+
"build",
|
|
1603
|
+
"MCA relayer withdraw requires a signature and must use swap()"
|
|
1604
|
+
);
|
|
1605
|
+
}
|
|
1606
|
+
const build = input.quote.executionMode === "deposit" ? await this.buildStandardSwap(input) : this.buildNearWithdraw(input.quote);
|
|
1607
|
+
return {
|
|
1608
|
+
...build,
|
|
1609
|
+
reportContext: {
|
|
1610
|
+
...build.reportContext,
|
|
1611
|
+
multiAddr: input.quote.mcaAccountId,
|
|
1612
|
+
...input.quote.executionMode === "deposit" ? {
|
|
1613
|
+
txType: build.isCrossChain ? "cross-chain" : "same-chain"
|
|
1614
|
+
} : {}
|
|
1615
|
+
}
|
|
1616
|
+
};
|
|
111
1617
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
1618
|
+
async swap(input) {
|
|
1619
|
+
this.assertQuoteFresh(input.quote);
|
|
1620
|
+
if (input.quote.executionMode === "withdraw-relayer") {
|
|
1621
|
+
return this.executeRelayerWithdraw({ ...input, quote: input.quote });
|
|
1622
|
+
}
|
|
1623
|
+
const build = await this.build({
|
|
1624
|
+
quote: input.quote,
|
|
1625
|
+
signal: input.signal,
|
|
1626
|
+
idempotencyKey: input.idempotencyKey
|
|
118
1627
|
});
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
1628
|
+
const nearStatusKey = input.quote.executionMode === "withdraw-near" && input.waitFor === "completed" ? extractMcaWithdrawDepositAddress({
|
|
1629
|
+
preview: input.quote.preview,
|
|
1630
|
+
bestQuote: input.quote.raw.bestQuote
|
|
1631
|
+
}) : void 0;
|
|
1632
|
+
if (input.quote.executionMode === "withdraw-near" && input.waitFor === "completed" && !nearStatusKey) {
|
|
1633
|
+
throw new SwapSdkError(
|
|
1634
|
+
"INVALID_API_RESPONSE",
|
|
1635
|
+
"status",
|
|
1636
|
+
"MCA NEAR withdraw is missing a status deposit address"
|
|
1637
|
+
);
|
|
1638
|
+
}
|
|
1639
|
+
const result = await this.client.executeSwap({
|
|
1640
|
+
build,
|
|
1641
|
+
waitFor: input.quote.executionMode === "withdraw-near" && input.waitFor === "completed" ? "source-confirmed" : input.waitFor,
|
|
1642
|
+
signal: input.signal,
|
|
1643
|
+
onEvent: input.onEvent
|
|
126
1644
|
});
|
|
1645
|
+
if (input.quote.executionMode === "withdraw-near" && input.waitFor === "completed") {
|
|
1646
|
+
const status = await this.client.waitForOrder({
|
|
1647
|
+
orderId: nearStatusKey,
|
|
1648
|
+
router: input.quote.route.router,
|
|
1649
|
+
signal: input.signal
|
|
1650
|
+
});
|
|
1651
|
+
this.emitEvent(
|
|
1652
|
+
{
|
|
1653
|
+
type: "order-status",
|
|
1654
|
+
executionId: result.executionId,
|
|
1655
|
+
status: status.status
|
|
1656
|
+
},
|
|
1657
|
+
input.onEvent
|
|
1658
|
+
);
|
|
1659
|
+
result.status = status.status === "unknown" || status.status === "pending" ? "processing" : status.status;
|
|
1660
|
+
if (status.status === "completed") {
|
|
1661
|
+
this.emitEvent(
|
|
1662
|
+
{ type: "completed", executionId: result.executionId },
|
|
1663
|
+
input.onEvent
|
|
1664
|
+
);
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
return result;
|
|
127
1668
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
1669
|
+
reportIfManaged(result) {
|
|
1670
|
+
const request = this.relayerReports.get(result.executionId);
|
|
1671
|
+
if (!request) return void 0;
|
|
1672
|
+
return this.client.reportRaw(request);
|
|
1673
|
+
}
|
|
1674
|
+
buildNearWithdraw(quote) {
|
|
1675
|
+
const business = extractMcaWithdrawBusiness(quote.preview);
|
|
1676
|
+
if (!business) {
|
|
1677
|
+
throw new SwapSdkError(
|
|
1678
|
+
"INVALID_API_RESPONSE",
|
|
1679
|
+
"build",
|
|
1680
|
+
"MCA NEAR withdraw preview is missing business"
|
|
1681
|
+
);
|
|
1682
|
+
}
|
|
1683
|
+
const recipient = quote.request.recipient?.trim();
|
|
1684
|
+
const signerWallet = extractMcaWithdrawSignerWallet(quote.preview) ?? (recipient ? formatMcaWallet("near", recipient) : void 0);
|
|
1685
|
+
if (!signerWallet) {
|
|
1686
|
+
throw new SwapSdkError(
|
|
1687
|
+
"INVALID_REQUEST",
|
|
1688
|
+
"build",
|
|
1689
|
+
"MCA NEAR withdraw requires a NEAR recipient or signer_wallet"
|
|
1690
|
+
);
|
|
1691
|
+
}
|
|
1692
|
+
const transactions = buildNearMcaWithdrawTransactions(quote.preview, {
|
|
1693
|
+
business,
|
|
1694
|
+
signerWallet,
|
|
1695
|
+
signature: "",
|
|
1696
|
+
mcaAccountId: quote.mcaAccountId
|
|
1697
|
+
});
|
|
1698
|
+
const request = buildRequestFromQuote(quote);
|
|
1699
|
+
const depositAddress = extractMcaWithdrawDepositAddress({
|
|
1700
|
+
preview: quote.preview,
|
|
1701
|
+
bestQuote: quote.raw.bestQuote
|
|
1702
|
+
});
|
|
1703
|
+
const build = normalizeBuild(
|
|
1704
|
+
{
|
|
1705
|
+
isCrossChain: true,
|
|
1706
|
+
chainType: "near",
|
|
1707
|
+
router: quote.route.router,
|
|
1708
|
+
fromChain: request.fromChain,
|
|
1709
|
+
toChain: request.toChain,
|
|
1710
|
+
tokenIn: {
|
|
1711
|
+
address: request.tokenIn,
|
|
1712
|
+
symbol: quote.tokenIn.symbol ?? "",
|
|
1713
|
+
decimals: quote.tokenIn.decimals ?? 0
|
|
1714
|
+
},
|
|
1715
|
+
tokenOut: {
|
|
1716
|
+
address: request.tokenOut,
|
|
1717
|
+
symbol: quote.tokenOut.symbol ?? "",
|
|
1718
|
+
decimals: quote.tokenOut.decimals ?? 0
|
|
1719
|
+
},
|
|
1720
|
+
amountIn: quote.amountIn,
|
|
1721
|
+
estimatedOut: quote.estimatedOut,
|
|
1722
|
+
minAmountOut: quote.minAmountOut,
|
|
1723
|
+
tx: transactions,
|
|
1724
|
+
approve: null,
|
|
1725
|
+
...depositAddress ? { deposit: { depositAddress } } : {}
|
|
1726
|
+
},
|
|
1727
|
+
void 0,
|
|
1728
|
+
request
|
|
131
1729
|
);
|
|
132
1730
|
return {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
1731
|
+
...build,
|
|
1732
|
+
reportContext: {
|
|
1733
|
+
txType: "mca-withdraw-near",
|
|
1734
|
+
multiAddr: quote.mcaAccountId,
|
|
1735
|
+
...depositAddress ? { depositAddress } : {},
|
|
1736
|
+
...recipient ? { recipient } : {},
|
|
1737
|
+
isCrossChain: true
|
|
1738
|
+
}
|
|
137
1739
|
};
|
|
138
1740
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
1741
|
+
async executeRelayerWithdraw(input) {
|
|
1742
|
+
const executionId = createExecutionId();
|
|
1743
|
+
const emit = (event) => this.emitEvent(event, input.onEvent);
|
|
1744
|
+
try {
|
|
1745
|
+
const signer = this.resolveMessageSigner(input.quote.signer.chain);
|
|
1746
|
+
const currentSigner = await this.readSignerIdentity(
|
|
1747
|
+
input.quote.signer.chain,
|
|
1748
|
+
"sign",
|
|
1749
|
+
signer
|
|
1750
|
+
);
|
|
1751
|
+
assertSignerMatchesQuote(currentSigner, input.quote.signer);
|
|
1752
|
+
const business = extractMcaWithdrawBusiness(input.quote.preview);
|
|
1753
|
+
if (!business) {
|
|
1754
|
+
throw new SwapSdkError(
|
|
1755
|
+
"INVALID_API_RESPONSE",
|
|
1756
|
+
"sign",
|
|
1757
|
+
"MCA relayer preview is missing business"
|
|
1758
|
+
);
|
|
1759
|
+
}
|
|
1760
|
+
const message = readString2(input.quote.preview.messageToSign);
|
|
1761
|
+
if (!message) {
|
|
1762
|
+
throw new SwapSdkError(
|
|
1763
|
+
"INVALID_API_RESPONSE",
|
|
1764
|
+
"sign",
|
|
1765
|
+
"MCA relayer preview is missing messageToSign"
|
|
1766
|
+
);
|
|
1767
|
+
}
|
|
1768
|
+
const depositAddress = extractMcaWithdrawDepositAddress({
|
|
1769
|
+
snapshotDepositAddress: readString2(input.quote.raw.depositAddress),
|
|
1770
|
+
preview: input.quote.preview,
|
|
1771
|
+
bestQuote: input.quote.raw.bestQuote
|
|
1772
|
+
});
|
|
1773
|
+
if (!depositAddress) {
|
|
1774
|
+
throw new SwapSdkError(
|
|
1775
|
+
"INVALID_API_RESPONSE",
|
|
1776
|
+
"build",
|
|
1777
|
+
"MCA relayer preview is missing a deposit address"
|
|
1778
|
+
);
|
|
1779
|
+
}
|
|
1780
|
+
emit({ type: "signing-requested", executionId });
|
|
1781
|
+
await input.beforeSign?.({
|
|
1782
|
+
chain: currentSigner.chain,
|
|
1783
|
+
identityKey: currentSigner.identityKey,
|
|
1784
|
+
message,
|
|
1785
|
+
business
|
|
1786
|
+
});
|
|
1787
|
+
let signature;
|
|
1788
|
+
try {
|
|
1789
|
+
signature = (await signer.signMessage(message, {
|
|
1790
|
+
signal: input.signal,
|
|
1791
|
+
context: {
|
|
1792
|
+
flow: "withdraw",
|
|
1793
|
+
mcaAccountId: input.quote.mcaAccountId,
|
|
1794
|
+
business
|
|
1795
|
+
}
|
|
1796
|
+
})).trim();
|
|
1797
|
+
} catch (error) {
|
|
1798
|
+
if (error instanceof SwapSdkError) throw error;
|
|
1799
|
+
throw new SwapSdkError(
|
|
1800
|
+
"SIGNING_FAILED",
|
|
1801
|
+
"sign",
|
|
1802
|
+
"Failed to sign the MCA relayer message",
|
|
1803
|
+
{ cause: error }
|
|
1804
|
+
);
|
|
1805
|
+
}
|
|
1806
|
+
if (!signature) {
|
|
1807
|
+
throw new SwapSdkError(
|
|
1808
|
+
"SIGNING_FAILED",
|
|
1809
|
+
"sign",
|
|
1810
|
+
"MCA signer returned an empty signature"
|
|
1811
|
+
);
|
|
1812
|
+
}
|
|
1813
|
+
const mcaRelayer = {
|
|
1814
|
+
mcaAccountId: input.quote.mcaAccountId,
|
|
1815
|
+
wallet: formatMcaWallet(
|
|
1816
|
+
currentSigner.chain,
|
|
1817
|
+
currentSigner.identityKey
|
|
1818
|
+
),
|
|
1819
|
+
business,
|
|
1820
|
+
signature
|
|
1821
|
+
};
|
|
1822
|
+
const request = buildMcaWithdrawRelayerRequest({
|
|
1823
|
+
quoteBuild: buildRequestFromQuote(input.quote),
|
|
1824
|
+
mcaRelayer,
|
|
1825
|
+
isCrossChain: input.quote.raw.isCrossChain ?? true,
|
|
1826
|
+
depositAddress,
|
|
1827
|
+
mcaAccountId: input.quote.mcaAccountId,
|
|
1828
|
+
recipientFallback: input.quote.request.recipient
|
|
1829
|
+
});
|
|
1830
|
+
const raw = await this.client.buildRaw(request, {
|
|
1831
|
+
signal: input.signal,
|
|
1832
|
+
idempotencyKey: input.idempotencyKey
|
|
1833
|
+
});
|
|
1834
|
+
const orderId = readString2(raw.orderId) ?? readRecordString2(raw.deposit, "orderId");
|
|
1835
|
+
const router = readString2(raw.router) ?? input.quote.route.router;
|
|
1836
|
+
const submittedDepositAddress = readRecordString2(raw.deposit, "depositAddress") ?? depositAddress;
|
|
1837
|
+
if (!orderId || !router) {
|
|
1838
|
+
throw new SwapSdkError(
|
|
1839
|
+
"INVALID_API_RESPONSE",
|
|
1840
|
+
"submit",
|
|
1841
|
+
"MCA relayer submission is missing orderId or router"
|
|
1842
|
+
);
|
|
1843
|
+
}
|
|
1844
|
+
const reportRequest = {
|
|
1845
|
+
sender: input.quote.request.sender,
|
|
1846
|
+
recipient: input.quote.request.recipient ?? input.quote.request.sender,
|
|
1847
|
+
from_hash: orderId,
|
|
1848
|
+
from_token: request.tokenIn,
|
|
1849
|
+
to_token: request.tokenOut,
|
|
1850
|
+
deposit_address: submittedDepositAddress,
|
|
1851
|
+
from_chain: request.fromChain,
|
|
1852
|
+
to_chain: request.toChain,
|
|
1853
|
+
amount_in: request.amountIn,
|
|
1854
|
+
estimated_out: input.quote.estimatedOut,
|
|
1855
|
+
router,
|
|
1856
|
+
is_cross_chain: true,
|
|
1857
|
+
tx_type: "mca-withdraw-relayer",
|
|
1858
|
+
multi_addr: input.quote.mcaAccountId,
|
|
1859
|
+
swapId: orderId
|
|
1860
|
+
};
|
|
1861
|
+
this.relayerReports.set(executionId, reportRequest);
|
|
1862
|
+
const result = {
|
|
1863
|
+
executionId,
|
|
1864
|
+
status: "submitted",
|
|
1865
|
+
router,
|
|
1866
|
+
orderId,
|
|
1867
|
+
depositAddress: submittedDepositAddress,
|
|
1868
|
+
report: { status: "skipped" },
|
|
1869
|
+
raw
|
|
1870
|
+
};
|
|
1871
|
+
emit({ type: "submitted", executionId, orderId });
|
|
1872
|
+
if (this.reportMode === "auto") {
|
|
1873
|
+
try {
|
|
1874
|
+
await this.client.reportRaw(reportRequest, { signal: input.signal });
|
|
1875
|
+
result.report = { status: "reported" };
|
|
1876
|
+
} catch (error) {
|
|
1877
|
+
const warning = {
|
|
1878
|
+
code: "REPORT_FAILED",
|
|
1879
|
+
message: "MCA withdraw submitted but report failed",
|
|
1880
|
+
cause: error
|
|
1881
|
+
};
|
|
1882
|
+
result.report = { status: "failed", warning };
|
|
1883
|
+
emit({ type: "warning", executionId, warning });
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
if (input.waitFor === "completed") {
|
|
1887
|
+
const status = await this.client.waitForOrder({
|
|
1888
|
+
orderId,
|
|
1889
|
+
router,
|
|
1890
|
+
signal: input.signal
|
|
1891
|
+
});
|
|
1892
|
+
emit({ type: "order-status", executionId, status: status.status });
|
|
1893
|
+
result.status = status.status === "unknown" || status.status === "pending" ? "processing" : status.status;
|
|
1894
|
+
if (status.status === "completed") {
|
|
1895
|
+
emit({ type: "completed", executionId });
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
return result;
|
|
1899
|
+
} catch (error) {
|
|
1900
|
+
const sdkError = asSwapSdkError(error, "submit");
|
|
1901
|
+
emit({ type: "failed", executionId, error: sdkError });
|
|
1902
|
+
throw sdkError;
|
|
1903
|
+
}
|
|
145
1904
|
}
|
|
146
|
-
|
|
147
|
-
|
|
1905
|
+
async readSignerIdentity(chain, stage, provider = this.resolveSignerIdentity(chain)) {
|
|
1906
|
+
let identityKey;
|
|
1907
|
+
try {
|
|
1908
|
+
identityKey = (await provider.getIdentityKey()).trim();
|
|
1909
|
+
} catch (error) {
|
|
1910
|
+
if (error instanceof SwapSdkError) throw error;
|
|
1911
|
+
throw new SwapSdkError(
|
|
1912
|
+
"SIGNING_FAILED",
|
|
1913
|
+
stage,
|
|
1914
|
+
"Failed to read the MCA signer identity",
|
|
1915
|
+
{ cause: error }
|
|
1916
|
+
);
|
|
1917
|
+
}
|
|
1918
|
+
if (!identityKey) {
|
|
1919
|
+
throw new SwapSdkError(
|
|
1920
|
+
"SIGNING_FAILED",
|
|
1921
|
+
stage,
|
|
1922
|
+
"MCA executor returned an empty signer identity"
|
|
1923
|
+
);
|
|
1924
|
+
}
|
|
1925
|
+
return { chain, identityKey };
|
|
148
1926
|
}
|
|
149
|
-
|
|
150
|
-
|
|
1927
|
+
emitEvent(event, local) {
|
|
1928
|
+
this.onEvent?.(event);
|
|
1929
|
+
if (local !== this.onEvent) local?.(event);
|
|
151
1930
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
1931
|
+
assertQuoteFresh(quote) {
|
|
1932
|
+
const now = this.now();
|
|
1933
|
+
const expiredByApi = quote.expiresAt !== void 0 && now > quote.expiresAt;
|
|
1934
|
+
const expiredByAge = this.maxQuoteAgeMs !== null && now - quote.receivedAt > Math.max(0, this.maxQuoteAgeMs);
|
|
1935
|
+
if (expiredByApi || expiredByAge) {
|
|
1936
|
+
throw new SwapSdkError(
|
|
1937
|
+
"QUOTE_EXPIRED",
|
|
1938
|
+
"build",
|
|
1939
|
+
"Quote has expired; request a fresh quote"
|
|
1940
|
+
);
|
|
1941
|
+
}
|
|
158
1942
|
}
|
|
159
|
-
|
|
160
|
-
|
|
1943
|
+
};
|
|
1944
|
+
function buildRequestFromQuote(quote) {
|
|
1945
|
+
const context = quote.buildContext;
|
|
1946
|
+
return {
|
|
1947
|
+
...context.request,
|
|
1948
|
+
router: context.router,
|
|
1949
|
+
...context.market ? { market: context.market } : {},
|
|
1950
|
+
expectedOut: context.expectedOut,
|
|
1951
|
+
minAmountOut: context.minAmountOut,
|
|
1952
|
+
preSwap: context.preSwap,
|
|
1953
|
+
bridge: context.bridge,
|
|
1954
|
+
...context.quoteId ? { quoteId: context.quoteId } : {}
|
|
1955
|
+
};
|
|
1956
|
+
}
|
|
1957
|
+
function assertSignerMatchesQuote(signer, expected) {
|
|
1958
|
+
if (!isSameMcaSignerIdentity(signer, expected)) {
|
|
1959
|
+
throw new SwapSdkError(
|
|
1960
|
+
"SIGNING_FAILED",
|
|
1961
|
+
"sign",
|
|
1962
|
+
"MCA signer does not match the signer used for the quote"
|
|
1963
|
+
);
|
|
161
1964
|
}
|
|
162
|
-
|
|
163
|
-
|
|
1965
|
+
}
|
|
1966
|
+
function readString2(value) {
|
|
1967
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
1968
|
+
}
|
|
1969
|
+
function readRecordString2(value, field) {
|
|
1970
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1971
|
+
return void 0;
|
|
164
1972
|
}
|
|
165
|
-
return
|
|
1973
|
+
return readString2(value[field]);
|
|
166
1974
|
}
|
|
167
|
-
|
|
1975
|
+
|
|
1976
|
+
// src/client/SwapClient.ts
|
|
1977
|
+
var SwapClient = class {
|
|
168
1978
|
constructor(config) {
|
|
169
|
-
this.
|
|
170
|
-
this.
|
|
171
|
-
this.
|
|
172
|
-
this.
|
|
173
|
-
this.
|
|
174
|
-
this.
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
if (!normalizedTokenIn || !normalizedTokenOut) {
|
|
210
|
-
logger.error("SmartRouter quote - Invalid token addresses:", {
|
|
211
|
-
tokenIn: {
|
|
212
|
-
original: tokenIn.address,
|
|
213
|
-
normalized: normalizedTokenIn
|
|
214
|
-
},
|
|
215
|
-
tokenOut: {
|
|
216
|
-
original: tokenOut.address,
|
|
217
|
-
normalized: normalizedTokenOut
|
|
218
|
-
}
|
|
219
|
-
});
|
|
220
|
-
return {
|
|
221
|
-
success: false,
|
|
222
|
-
tokenIn: params.tokenIn,
|
|
223
|
-
tokenOut: params.tokenOut,
|
|
224
|
-
amountIn: params.amountIn,
|
|
225
|
-
amountOut: "0",
|
|
226
|
-
minAmountOut: "0",
|
|
227
|
-
routes: [],
|
|
228
|
-
error: `Invalid token address: tokenIn=${normalizedTokenIn || "empty"}, tokenOut=${normalizedTokenOut || "empty"}`
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
const slippageBps = convertSlippageToBasisPoints(slippage);
|
|
232
|
-
const slippageDecimalForApi = slippageBps / 1e4;
|
|
233
|
-
logger.debug("SmartRouter quote - Calling findPath:", {
|
|
234
|
-
tokenIn: normalizedTokenIn,
|
|
235
|
-
tokenOut: normalizedTokenOut,
|
|
236
|
-
amountIn,
|
|
237
|
-
slippage: slippageDecimalForApi,
|
|
238
|
-
slippageBps
|
|
239
|
-
});
|
|
240
|
-
const response = await this.findPathAdapter.findPath({
|
|
241
|
-
tokenIn: normalizedTokenIn,
|
|
242
|
-
tokenOut: normalizedTokenOut,
|
|
243
|
-
amountIn: String(amountIn),
|
|
244
|
-
slippage: slippageDecimalForApi,
|
|
245
|
-
supportLedger: false
|
|
246
|
-
});
|
|
247
|
-
logger.debug("SmartRouter quote - findPath response:", {
|
|
248
|
-
result_code: response?.result_code,
|
|
249
|
-
result_msg: response?.result_msg || response?.result_message,
|
|
250
|
-
hasRoutes: !!response?.result_data?.routes?.length
|
|
1979
|
+
this.inFlight = /* @__PURE__ */ new Set();
|
|
1980
|
+
this.reportRequests = /* @__PURE__ */ new Map();
|
|
1981
|
+
this.config = config;
|
|
1982
|
+
this.api = new ApiClient(config);
|
|
1983
|
+
this.registry = new ExecutorRegistry(config.executors ?? []);
|
|
1984
|
+
this.reportMode = config.reportMode ?? "auto";
|
|
1985
|
+
this.now = config.now ?? Date.now;
|
|
1986
|
+
this.managedSwapFlow = new McaSwapService(this, {
|
|
1987
|
+
now: this.now,
|
|
1988
|
+
maxQuoteAgeMs: config.maxQuoteAgeMs,
|
|
1989
|
+
reportMode: this.reportMode,
|
|
1990
|
+
resolveSignerIdentity: (chain) => this.registry.getSigner(chain),
|
|
1991
|
+
resolveMessageSigner: (chain) => this.registry.getSigner(chain, true),
|
|
1992
|
+
buildStandardSwap: (input) => this.buildStandardSwap(input),
|
|
1993
|
+
...config.onEvent ? { onEvent: config.onEvent } : {}
|
|
1994
|
+
});
|
|
1995
|
+
}
|
|
1996
|
+
quoteRaw(request, options = {}) {
|
|
1997
|
+
return this.api.quote(request, options);
|
|
1998
|
+
}
|
|
1999
|
+
async quote(request, options = {}) {
|
|
2000
|
+
if (isMcaQuoteRequest(request)) {
|
|
2001
|
+
return this.managedSwapFlow.quote(request, options);
|
|
2002
|
+
}
|
|
2003
|
+
return this.quoteStandard(request, options);
|
|
2004
|
+
}
|
|
2005
|
+
async quoteStandard(request, options = {}) {
|
|
2006
|
+
const rawRequest = serializeQuoteRequest(request);
|
|
2007
|
+
const raw = await this.api.quote(rawRequest, options);
|
|
2008
|
+
return normalizeQuote(request, raw, this.now());
|
|
2009
|
+
}
|
|
2010
|
+
buildRaw(request, options = {}) {
|
|
2011
|
+
return this.api.build(request, options);
|
|
2012
|
+
}
|
|
2013
|
+
async buildSwap(input) {
|
|
2014
|
+
if (isMcaQuote(input.quote)) {
|
|
2015
|
+
return this.managedSwapFlow.build({
|
|
2016
|
+
quote: input.quote,
|
|
2017
|
+
signal: input.signal,
|
|
2018
|
+
idempotencyKey: input.idempotencyKey
|
|
251
2019
|
});
|
|
252
|
-
if (response?.result_code !== 0 || !response?.result_data?.routes?.length) {
|
|
253
|
-
return {
|
|
254
|
-
success: false,
|
|
255
|
-
tokenIn,
|
|
256
|
-
tokenOut,
|
|
257
|
-
amountIn,
|
|
258
|
-
amountOut: "0",
|
|
259
|
-
minAmountOut: "0",
|
|
260
|
-
routes: [],
|
|
261
|
-
error: response?.result_msg || response?.result_message || "No route found"
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
const { routes: serverRoutes, amount_out } = response.result_data;
|
|
265
|
-
const slippageDecimal = new Big__default.default(slippageBps).div(1e4);
|
|
266
|
-
const routes = serverRoutes.map((route) => ({
|
|
267
|
-
pools: route.pools.map((pool) => ({
|
|
268
|
-
pool_id: Number(pool.pool_id),
|
|
269
|
-
token_in: pool.token_in || normalizedTokenIn,
|
|
270
|
-
token_out: pool.token_out || normalizedTokenOut,
|
|
271
|
-
amount_in: pool.amount_in,
|
|
272
|
-
amount_out: pool.amount_out,
|
|
273
|
-
fee: pool.fee
|
|
274
|
-
})),
|
|
275
|
-
amountIn,
|
|
276
|
-
amountOut: route.amount_out || amount_out || "0"
|
|
277
|
-
}));
|
|
278
|
-
const amountOut = new Big__default.default(amount_out || 0);
|
|
279
|
-
const minAmountOut = amountOut.mul(new Big__default.default(1).minus(slippageDecimal)).toFixed(0, Big__default.default.roundDown);
|
|
280
|
-
return {
|
|
281
|
-
success: true,
|
|
282
|
-
tokenIn,
|
|
283
|
-
tokenOut,
|
|
284
|
-
amountIn,
|
|
285
|
-
amountOut: amountOut.toFixed(0),
|
|
286
|
-
minAmountOut,
|
|
287
|
-
routes,
|
|
288
|
-
// Save raw serverRoutes data for executeSwap
|
|
289
|
-
rawRoutes: serverRoutes
|
|
290
|
-
};
|
|
291
|
-
} catch (error) {
|
|
292
|
-
return {
|
|
293
|
-
success: false,
|
|
294
|
-
tokenIn: params.tokenIn,
|
|
295
|
-
tokenOut: params.tokenOut,
|
|
296
|
-
amountIn: params.amountIn,
|
|
297
|
-
amountOut: "0",
|
|
298
|
-
minAmountOut: "0",
|
|
299
|
-
routes: [],
|
|
300
|
-
error: error?.message || "Quote failed"
|
|
301
|
-
};
|
|
302
2020
|
}
|
|
2021
|
+
return this.buildStandardSwap(input);
|
|
2022
|
+
}
|
|
2023
|
+
async buildStandardSwap(input) {
|
|
2024
|
+
this.assertQuoteFresh(input.quote);
|
|
2025
|
+
const executionId = createExecutionId();
|
|
2026
|
+
this.emit({ type: "build-started", executionId });
|
|
2027
|
+
const context = input.quote.buildContext;
|
|
2028
|
+
const request = {
|
|
2029
|
+
...context.request,
|
|
2030
|
+
router: context.router,
|
|
2031
|
+
...context.market ? { market: context.market } : {},
|
|
2032
|
+
expectedOut: context.expectedOut,
|
|
2033
|
+
minAmountOut: context.minAmountOut,
|
|
2034
|
+
preSwap: context.preSwap,
|
|
2035
|
+
bridge: context.bridge,
|
|
2036
|
+
...context.quoteId ? { quoteId: context.quoteId } : {}
|
|
2037
|
+
};
|
|
2038
|
+
const raw = await this.api.build(request, {
|
|
2039
|
+
signal: input.signal,
|
|
2040
|
+
idempotencyKey: input.idempotencyKey
|
|
2041
|
+
});
|
|
2042
|
+
const build = normalizeBuild(raw, executionId, request);
|
|
2043
|
+
this.emit({ type: "build-completed", executionId });
|
|
2044
|
+
return build;
|
|
303
2045
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
2046
|
+
async executeSwap(input) {
|
|
2047
|
+
const { build } = input;
|
|
2048
|
+
if (this.inFlight.has(build.executionId)) {
|
|
2049
|
+
throw new SwapSdkError(
|
|
2050
|
+
"INVALID_REQUEST",
|
|
2051
|
+
"sign",
|
|
2052
|
+
`Execution ${build.executionId} is already in progress`
|
|
2053
|
+
);
|
|
2054
|
+
}
|
|
2055
|
+
this.inFlight.add(build.executionId);
|
|
2056
|
+
const emit = (event) => {
|
|
2057
|
+
this.emit(event);
|
|
2058
|
+
if (input.onEvent !== this.config.onEvent) input.onEvent?.(event);
|
|
2059
|
+
};
|
|
308
2060
|
try {
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
2061
|
+
const executor = this.registry.get(build.execution.kind);
|
|
2062
|
+
const context = {
|
|
2063
|
+
executionId: build.executionId,
|
|
2064
|
+
signal: input.signal,
|
|
2065
|
+
waitFor: input.waitFor ?? "submitted",
|
|
2066
|
+
emit,
|
|
2067
|
+
...input.beforeSign ? { beforeSign: input.beforeSign } : {}
|
|
2068
|
+
};
|
|
2069
|
+
await executor.validate(build.execution, context);
|
|
2070
|
+
emit({ type: "signing-requested", executionId: build.executionId });
|
|
2071
|
+
const chainResult = await executor.execute(build.execution, context);
|
|
2072
|
+
let orderId = chainResult.orderId ?? build.order?.orderId;
|
|
2073
|
+
let orderRouter = build.order?.router;
|
|
2074
|
+
let orderChainId = build.order?.chainId;
|
|
2075
|
+
if (chainResult.signature) {
|
|
2076
|
+
if (build.execution.kind !== "evm-signature") {
|
|
2077
|
+
throw new SwapSdkError(
|
|
2078
|
+
"INVALID_TRANSACTION",
|
|
2079
|
+
"submit",
|
|
2080
|
+
"Only signature executions can submit signed orders"
|
|
2081
|
+
);
|
|
2082
|
+
}
|
|
2083
|
+
const submitParams = build.execution.request.submit?.params;
|
|
2084
|
+
const submitRouter = readNonEmptyString(submitParams?.router) ?? build.execution.request.router;
|
|
2085
|
+
const submitQuoteId = readNonEmptyString(submitParams?.quoteId) ?? build.execution.request.quoteId;
|
|
2086
|
+
const signingScheme = readNonEmptyString(submitParams?.signingScheme) ?? chainResult.signingScheme ?? build.execution.request.signingScheme ?? "eip712";
|
|
2087
|
+
const submitted = await this.api.submitOrder(
|
|
2088
|
+
{
|
|
2089
|
+
router: submitRouter,
|
|
2090
|
+
quoteId: submitQuoteId,
|
|
2091
|
+
signature: chainResult.signature,
|
|
2092
|
+
signingScheme
|
|
2093
|
+
},
|
|
2094
|
+
{ signal: input.signal }
|
|
2095
|
+
);
|
|
2096
|
+
orderId = submitted.orderId;
|
|
2097
|
+
orderRouter = readNonEmptyString(submitted.router) ?? submitRouter;
|
|
2098
|
+
orderChainId = String(
|
|
2099
|
+
submitted.chainId ?? build.execution.request.chainId
|
|
2100
|
+
);
|
|
2101
|
+
}
|
|
2102
|
+
const result = {
|
|
2103
|
+
executionId: build.executionId,
|
|
2104
|
+
status: chainResult.status,
|
|
2105
|
+
router: build.router,
|
|
2106
|
+
...chainResult.txHash ? { txHash: chainResult.txHash } : {},
|
|
2107
|
+
...chainResult.txHashes ? { txHashes: chainResult.txHashes } : {},
|
|
2108
|
+
...orderId ? { orderId } : {},
|
|
2109
|
+
...build.deposit?.depositAddress ? { depositAddress: build.deposit.depositAddress } : {},
|
|
2110
|
+
report: { status: "skipped" },
|
|
2111
|
+
raw: chainResult.raw ?? build.raw
|
|
2112
|
+
};
|
|
2113
|
+
if (chainResult.status === "requires-user-action") {
|
|
2114
|
+
emit({
|
|
2115
|
+
type: "requires-user-action",
|
|
2116
|
+
executionId: build.executionId
|
|
327
2117
|
});
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
try {
|
|
340
|
-
const storageBalance = await this.nearChainAdapter.view({
|
|
341
|
-
contractId: quote.tokenOut.address,
|
|
342
|
-
methodName: "storage_balance_of",
|
|
343
|
-
args: {
|
|
344
|
-
account_id: finalRecipient
|
|
345
|
-
}
|
|
2118
|
+
} else {
|
|
2119
|
+
emit({
|
|
2120
|
+
type: "submitted",
|
|
2121
|
+
executionId: build.executionId,
|
|
2122
|
+
...chainResult.txHash ? { txHash: chainResult.txHash } : {},
|
|
2123
|
+
...orderId ? { orderId } : {}
|
|
2124
|
+
});
|
|
2125
|
+
if (chainResult.status === "source-confirmed") {
|
|
2126
|
+
emit({
|
|
2127
|
+
type: "source-confirmed",
|
|
2128
|
+
executionId: build.executionId
|
|
346
2129
|
});
|
|
347
|
-
isRegistered = !!storageBalance;
|
|
348
|
-
} catch (err) {
|
|
349
|
-
isRegistered = false;
|
|
350
2130
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
}
|
|
2131
|
+
}
|
|
2132
|
+
const reportRequest = this.createReportRequest(build, result);
|
|
2133
|
+
if (reportRequest) {
|
|
2134
|
+
this.reportRequests.set(build.executionId, reportRequest);
|
|
2135
|
+
}
|
|
2136
|
+
if (this.reportMode === "auto" && reportRequest) {
|
|
2137
|
+
try {
|
|
2138
|
+
await this.api.report(reportRequest, { signal: input.signal });
|
|
2139
|
+
result.report = { status: "reported" };
|
|
2140
|
+
} catch (error) {
|
|
2141
|
+
const warning = {
|
|
2142
|
+
code: "REPORT_FAILED",
|
|
2143
|
+
message: "Swap submitted but report failed",
|
|
2144
|
+
cause: error
|
|
2145
|
+
};
|
|
2146
|
+
result.report = { status: "failed", warning };
|
|
2147
|
+
emit({ type: "warning", executionId: build.executionId, warning });
|
|
366
2148
|
}
|
|
367
2149
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
amount: quote.amountIn,
|
|
380
|
-
swapMsg,
|
|
381
|
-
swapActionsCount: swapActions.length,
|
|
382
|
-
recipient: finalRecipient,
|
|
383
|
-
tokenOut: quote.tokenOut?.address
|
|
384
|
-
});
|
|
385
|
-
transactions.push({
|
|
386
|
-
contractId: quote.tokenIn.address,
|
|
387
|
-
methodName: "ft_transfer_call",
|
|
388
|
-
args: {
|
|
389
|
-
receiver_id: this.refExchangeId,
|
|
390
|
-
amount: quote.amountIn,
|
|
391
|
-
msg: JSON.stringify(swapMsg)
|
|
392
|
-
},
|
|
393
|
-
gas: "250",
|
|
394
|
-
// NEP-141 requires attaching 1 yoctoNEAR for certain calls.
|
|
395
|
-
expandDeposit: "1"
|
|
396
|
-
});
|
|
397
|
-
const result = await this.nearChainAdapter.call({
|
|
398
|
-
transactions
|
|
399
|
-
});
|
|
400
|
-
if (result.status === "success") {
|
|
401
|
-
return {
|
|
402
|
-
success: true,
|
|
403
|
-
txHash: result.txHash,
|
|
404
|
-
txHashArray: result.txHashArr || (result.txHash ? [result.txHash] : [])
|
|
405
|
-
};
|
|
406
|
-
} else {
|
|
407
|
-
return {
|
|
408
|
-
success: false,
|
|
409
|
-
error: result.message || "Execute swap failed"
|
|
410
|
-
};
|
|
2150
|
+
if ((input.waitFor ?? "submitted") === "completed" && orderId) {
|
|
2151
|
+
const status = await this.waitForOrder({
|
|
2152
|
+
orderId,
|
|
2153
|
+
router: orderRouter ?? build.router,
|
|
2154
|
+
chainId: orderChainId,
|
|
2155
|
+
signal: input.signal
|
|
2156
|
+
});
|
|
2157
|
+
result.status = status.status === "unknown" || status.status === "pending" ? "processing" : status.status;
|
|
2158
|
+
if (status.status === "completed") {
|
|
2159
|
+
emit({ type: "completed", executionId: build.executionId });
|
|
2160
|
+
}
|
|
411
2161
|
}
|
|
2162
|
+
return result;
|
|
412
2163
|
} catch (error) {
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
2164
|
+
const sdkError = asSwapSdkError(error, "broadcast");
|
|
2165
|
+
emit({ type: "failed", executionId: build.executionId, error: sdkError });
|
|
2166
|
+
throw sdkError;
|
|
2167
|
+
} finally {
|
|
2168
|
+
this.inFlight.delete(build.executionId);
|
|
417
2169
|
}
|
|
418
2170
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
recipient,
|
|
436
|
-
refundTo
|
|
437
|
-
} = params;
|
|
438
|
-
const { intentsQuotationAdapter, dexRouter, bluechipTokens, configAdapter } = config;
|
|
439
|
-
const wrapNearContractId = configAdapter.getWrapNearContractId();
|
|
440
|
-
if (!sourceToken?.address) {
|
|
441
|
-
throw new Error("Source token address is required");
|
|
442
|
-
}
|
|
443
|
-
if (!targetToken?.address) {
|
|
444
|
-
throw new Error("Target token address is required");
|
|
445
|
-
}
|
|
446
|
-
const needsPreSwap = sourceChain === "near" && !isNearIntentsSupportedToken(sourceToken, bluechipTokens);
|
|
447
|
-
const bluechipToken = findBestBluechipToken(
|
|
448
|
-
bluechipTokens,
|
|
449
|
-
wrapNearContractId
|
|
450
|
-
);
|
|
451
|
-
if (!bluechipToken?.address) {
|
|
452
|
-
logger.error("DEX Aggregator - Failed to find bluechip token:", {
|
|
453
|
-
bluechipToken,
|
|
454
|
-
bluechipTokens
|
|
2171
|
+
async swap(input) {
|
|
2172
|
+
if (isMcaQuote(input.quote)) {
|
|
2173
|
+
return this.managedSwapFlow.swap(input);
|
|
2174
|
+
}
|
|
2175
|
+
const regularInput = input;
|
|
2176
|
+
const build = await this.buildSwap({
|
|
2177
|
+
quote: regularInput.quote,
|
|
2178
|
+
signal: regularInput.signal,
|
|
2179
|
+
idempotencyKey: regularInput.idempotencyKey
|
|
2180
|
+
});
|
|
2181
|
+
return this.executeSwap({
|
|
2182
|
+
build,
|
|
2183
|
+
waitFor: regularInput.waitFor,
|
|
2184
|
+
signal: regularInput.signal,
|
|
2185
|
+
onEvent: regularInput.onEvent,
|
|
2186
|
+
beforeSign: regularInput.beforeSign
|
|
455
2187
|
});
|
|
456
|
-
throw new Error("Failed to find bluechip token address");
|
|
457
2188
|
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
if (needsPreSwap) {
|
|
465
|
-
if (!sourceToken?.address) {
|
|
466
|
-
throw new Error("Source token address is required");
|
|
467
|
-
}
|
|
468
|
-
logger.debug("DEX Aggregator - Pre-swap quote params:", {
|
|
469
|
-
tokenIn: {
|
|
470
|
-
address: sourceToken.address,
|
|
471
|
-
symbol: sourceToken.symbol
|
|
472
|
-
},
|
|
473
|
-
tokenOut: {
|
|
474
|
-
address: bluechipToken.address,
|
|
475
|
-
symbol: bluechipToken.symbol
|
|
2189
|
+
async getOrderStatus(input) {
|
|
2190
|
+
const raw = await this.api.getOrderStatus(
|
|
2191
|
+
{
|
|
2192
|
+
orderId: input.orderId,
|
|
2193
|
+
router: input.router,
|
|
2194
|
+
chainId: input.chainId
|
|
476
2195
|
},
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
2196
|
+
{ signal: input.signal }
|
|
2197
|
+
);
|
|
2198
|
+
return {
|
|
2199
|
+
orderId: input.orderId,
|
|
2200
|
+
router: input.router,
|
|
2201
|
+
status: normalizeOrderStatus(raw),
|
|
2202
|
+
raw
|
|
2203
|
+
};
|
|
2204
|
+
}
|
|
2205
|
+
async waitForOrder(input) {
|
|
2206
|
+
const intervalMs = input.intervalMs ?? 5e3;
|
|
2207
|
+
const timeoutMs = input.timeoutMs ?? 25e4;
|
|
2208
|
+
const startedAt = this.now();
|
|
2209
|
+
for (; ; ) {
|
|
2210
|
+
if (input.signal?.aborted) {
|
|
2211
|
+
throw new SwapSdkError(
|
|
2212
|
+
"REQUEST_ABORTED",
|
|
2213
|
+
"status",
|
|
2214
|
+
"Order polling aborted; any submitted transaction is unaffected"
|
|
2215
|
+
);
|
|
2216
|
+
}
|
|
2217
|
+
const result = await this.getOrderStatus(input);
|
|
2218
|
+
if (result.status === "completed" || result.status === "failed" || result.status === "refunded" || result.status === "expired") {
|
|
2219
|
+
return result;
|
|
2220
|
+
}
|
|
2221
|
+
if (this.now() - startedAt >= timeoutMs) {
|
|
2222
|
+
throw new SwapSdkError(
|
|
2223
|
+
"ORDER_TIMEOUT",
|
|
2224
|
+
"status",
|
|
2225
|
+
`Order status timed out after ${timeoutMs}ms`,
|
|
2226
|
+
{ retryable: true }
|
|
2227
|
+
);
|
|
2228
|
+
}
|
|
2229
|
+
await delay(intervalMs, input.signal);
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
submitOrderRaw(request, options = {}) {
|
|
2233
|
+
return this.api.submitOrder(request, options);
|
|
2234
|
+
}
|
|
2235
|
+
getOrderStatusRaw(request, options = {}) {
|
|
2236
|
+
return this.api.getOrderStatus(request, options);
|
|
2237
|
+
}
|
|
2238
|
+
reportRaw(request, options = {}) {
|
|
2239
|
+
return this.api.report(request, options);
|
|
2240
|
+
}
|
|
2241
|
+
report(result) {
|
|
2242
|
+
const managedReport = this.managedSwapFlow.reportIfManaged(result);
|
|
2243
|
+
if (managedReport) return managedReport;
|
|
2244
|
+
const request = this.reportRequests.get(result.executionId);
|
|
2245
|
+
if (!request) {
|
|
2246
|
+
throw new SwapSdkError(
|
|
2247
|
+
"INVALID_REQUEST",
|
|
2248
|
+
"report",
|
|
2249
|
+
`No report context for execution ${result.executionId}`
|
|
483
2250
|
);
|
|
484
2251
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
2252
|
+
return this.api.report(request);
|
|
2253
|
+
}
|
|
2254
|
+
retryReport(result) {
|
|
2255
|
+
return this.report(result);
|
|
2256
|
+
}
|
|
2257
|
+
getHistoryRaw(params, options = {}) {
|
|
2258
|
+
return this.api.getHistory(params, options);
|
|
2259
|
+
}
|
|
2260
|
+
async getHistory(request, options = {}) {
|
|
2261
|
+
const raw = await this.api.getHistory(
|
|
2262
|
+
{
|
|
2263
|
+
sender: request.sender,
|
|
2264
|
+
...request.page !== void 0 ? { pageNumber: request.page } : {},
|
|
2265
|
+
...request.pageSize !== void 0 ? { pageSize: request.pageSize } : {}
|
|
2266
|
+
},
|
|
2267
|
+
options
|
|
2268
|
+
);
|
|
2269
|
+
const page = normalizeHistory(raw);
|
|
2270
|
+
if (request.status === void 0) return page;
|
|
2271
|
+
const statuses = new Set(request.status);
|
|
2272
|
+
return {
|
|
2273
|
+
...page,
|
|
2274
|
+
items: page.items.filter((item) => statuses.has(item.status)),
|
|
2275
|
+
filteredLocally: true
|
|
2276
|
+
};
|
|
2277
|
+
}
|
|
2278
|
+
emit(event) {
|
|
2279
|
+
this.config.onEvent?.(event);
|
|
2280
|
+
}
|
|
2281
|
+
assertQuoteFresh(quote) {
|
|
2282
|
+
const now = this.now();
|
|
2283
|
+
const maxAge = this.config.maxQuoteAgeMs === void 0 ? 3e4 : this.config.maxQuoteAgeMs;
|
|
2284
|
+
const expiredByApi = quote.expiresAt !== void 0 && now > quote.expiresAt;
|
|
2285
|
+
const expiredByAge = maxAge !== null && now - quote.receivedAt > Math.max(0, maxAge);
|
|
2286
|
+
if (expiredByApi || expiredByAge) {
|
|
2287
|
+
throw new SwapSdkError(
|
|
2288
|
+
"QUOTE_EXPIRED",
|
|
2289
|
+
"build",
|
|
2290
|
+
"Quote has expired; request a fresh quote"
|
|
509
2291
|
);
|
|
510
2292
|
}
|
|
511
|
-
logger.debug("DEX Aggregator - Pre-swap quote success:", {
|
|
512
|
-
amountOut: preSwapAmountOut,
|
|
513
|
-
tokenOut: bluechipToken.symbol,
|
|
514
|
-
decimals: bluechipToken.decimals
|
|
515
|
-
});
|
|
516
2293
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
const
|
|
520
|
-
const
|
|
521
|
-
if (
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
2294
|
+
createReportRequest(build, result) {
|
|
2295
|
+
const request = build.request;
|
|
2296
|
+
const reportContext = build.reportContext;
|
|
2297
|
+
const fromHash = reportContext?.fromHash ?? result.txHash ?? result.orderId;
|
|
2298
|
+
if (!request || !fromHash) return void 0;
|
|
2299
|
+
return {
|
|
2300
|
+
sender: reportContext?.sender ?? request.sender,
|
|
2301
|
+
recipient: reportContext?.recipient ?? request.recipient ?? request.sender,
|
|
2302
|
+
from_hash: fromHash,
|
|
2303
|
+
from_token: request.tokenIn,
|
|
2304
|
+
to_token: request.tokenOut,
|
|
2305
|
+
deposit_address: reportContext?.depositAddress ?? result.depositAddress ?? "",
|
|
2306
|
+
from_chain: request.fromChain,
|
|
2307
|
+
to_chain: request.toChain,
|
|
2308
|
+
is_cross_chain: reportContext?.isCrossChain ?? build.isCrossChain,
|
|
2309
|
+
amount_in: request.amountIn,
|
|
2310
|
+
estimated_out: build.estimatedOut,
|
|
2311
|
+
router: build.router,
|
|
2312
|
+
tx_type: reportContext?.txType ?? (build.isCrossChain ? "cross-chain" : "same-chain"),
|
|
2313
|
+
...reportContext?.multiAddr ? { multi_addr: reportContext.multiAddr } : {},
|
|
2314
|
+
...reportContext?.swapId ?? result.orderId ? { swapId: reportContext?.swapId ?? result.orderId } : {}
|
|
2315
|
+
};
|
|
2316
|
+
}
|
|
2317
|
+
};
|
|
2318
|
+
function isMcaQuoteRequest(request) {
|
|
2319
|
+
if (!("flow" in request) || !("mcaAccountId" in request)) return false;
|
|
2320
|
+
const flow = Reflect.get(request, "flow");
|
|
2321
|
+
const accountId = Reflect.get(request, "mcaAccountId");
|
|
2322
|
+
return (flow === "deposit" || flow === "withdraw") && typeof accountId === "string" && accountId.trim().length > 0;
|
|
2323
|
+
}
|
|
2324
|
+
function isMcaQuote(quote) {
|
|
2325
|
+
if (!("executionMode" in quote) || !("mcaAccountId" in quote)) {
|
|
2326
|
+
return false;
|
|
2327
|
+
}
|
|
2328
|
+
const mode = Reflect.get(quote, "executionMode");
|
|
2329
|
+
return mode === "deposit" || mode === "withdraw-near" || mode === "withdraw-relayer";
|
|
2330
|
+
}
|
|
2331
|
+
function readNonEmptyString(value) {
|
|
2332
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
2333
|
+
}
|
|
2334
|
+
function delay(ms, signal) {
|
|
2335
|
+
return new Promise((resolve, reject) => {
|
|
2336
|
+
if (signal?.aborted) {
|
|
2337
|
+
reject(
|
|
2338
|
+
new SwapSdkError(
|
|
2339
|
+
"REQUEST_ABORTED",
|
|
2340
|
+
"status",
|
|
2341
|
+
"Order polling aborted; any submitted transaction is unaffected"
|
|
2342
|
+
)
|
|
536
2343
|
);
|
|
2344
|
+
return;
|
|
537
2345
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
sourceToken.address,
|
|
547
|
-
wrapNearContractId
|
|
548
|
-
);
|
|
549
|
-
if (!normalizedSourceAsset.startsWith("nep141:")) {
|
|
550
|
-
normalizedSourceAsset = `nep141:${normalizedSourceAsset}`;
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
} else {
|
|
554
|
-
normalizedSourceAsset = normalizeTokenId(
|
|
555
|
-
sourceToken.address,
|
|
556
|
-
wrapNearContractId
|
|
2346
|
+
const onAbort = () => {
|
|
2347
|
+
clearTimeout(timeout);
|
|
2348
|
+
reject(
|
|
2349
|
+
new SwapSdkError(
|
|
2350
|
+
"REQUEST_ABORTED",
|
|
2351
|
+
"status",
|
|
2352
|
+
"Order polling aborted; any submitted transaction is unaffected"
|
|
2353
|
+
)
|
|
557
2354
|
);
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
if (normalizedTargetAsset && !normalizedTargetAsset.startsWith("nep141:") && !normalizedTargetAsset.startsWith("nep245:") && normalizedTargetAsset.includes(".")) {
|
|
565
|
-
normalizedTargetAsset = `nep141:${normalizeTokenId(
|
|
566
|
-
normalizedTargetAsset,
|
|
567
|
-
wrapNearContractId
|
|
568
|
-
)}`;
|
|
569
|
-
}
|
|
570
|
-
normalizedTargetAsset = normalizeDestinationAsset(normalizedTargetAsset, wrapNearContractId) || normalizedTargetAsset;
|
|
571
|
-
const slippageBps = convertSlippageToBasisPoints(slippage);
|
|
572
|
-
const intentsAmount = needsPreSwap ? preSwapQuote.amountOut : amountIn;
|
|
573
|
-
logger.debug("DEX Aggregator - Calling NearIntents quotation:", {
|
|
574
|
-
originAsset: normalizedSourceAsset,
|
|
575
|
-
destinationAsset: normalizedTargetAsset,
|
|
576
|
-
amount: intentsAmount,
|
|
577
|
-
needsPreSwap,
|
|
578
|
-
preSwapAmountOut: needsPreSwap ? preSwapQuote.amountOut : void 0
|
|
579
|
-
});
|
|
580
|
-
const swapTypeForIntents = needsPreSwap ? "FLEX_INPUT" : void 0;
|
|
581
|
-
logger.debug("DEX Aggregator - swapType for NearIntents:", {
|
|
582
|
-
needsPreSwap,
|
|
583
|
-
swapType: swapTypeForIntents || "EXACT_INPUT (default)"
|
|
584
|
-
});
|
|
585
|
-
const intentsQuote = await intentsQuotationAdapter.quote({
|
|
586
|
-
originAsset: normalizedSourceAsset,
|
|
587
|
-
destinationAsset: normalizedTargetAsset,
|
|
588
|
-
amount: intentsAmount,
|
|
589
|
-
refundTo: refundTo || recipient,
|
|
590
|
-
recipient,
|
|
591
|
-
slippageTolerance: slippageBps,
|
|
592
|
-
swapType: swapTypeForIntents
|
|
593
|
-
});
|
|
594
|
-
logger.debug("DEX Aggregator - NearIntents quotation result:", {
|
|
595
|
-
quoteStatus: intentsQuote.quoteStatus,
|
|
596
|
-
message: intentsQuote.message,
|
|
597
|
-
hasDepositAddress: !!intentsQuote.quoteSuccessResult?.quote?.depositAddress
|
|
2355
|
+
};
|
|
2356
|
+
const timeout = setTimeout(() => {
|
|
2357
|
+
signal?.removeEventListener("abort", onAbort);
|
|
2358
|
+
resolve();
|
|
2359
|
+
}, Math.max(0, ms));
|
|
2360
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
598
2361
|
});
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
// src/mca/collateral.ts
|
|
2365
|
+
function resolveMcaWithdrawPolicy(input) {
|
|
2366
|
+
const collateral = parseDecimal(input.collateralBalance, "collateralBalance");
|
|
2367
|
+
const available = parseDecimal(input.availableBalance, "availableBalance");
|
|
2368
|
+
const amount = parseDecimal(input.amountIn, "amountIn");
|
|
2369
|
+
const needDecrease = collateral.digits > 0n;
|
|
2370
|
+
return {
|
|
2371
|
+
needDecrease,
|
|
2372
|
+
decreaseAmountBurrow: needDecrease ? input.collateralBalance.trim() : "0",
|
|
2373
|
+
withdrawAll: input.isMax || available.digits > 0n && isAtLeastWithdrawAllThreshold(amount, available)
|
|
2374
|
+
};
|
|
2375
|
+
}
|
|
2376
|
+
function isAtLeastWithdrawAllThreshold(amount, available) {
|
|
2377
|
+
const [amountScaled, availableScaled] = alignScale(amount, available);
|
|
2378
|
+
return amountScaled * 1000000n >= availableScaled * 999999n;
|
|
2379
|
+
}
|
|
2380
|
+
function alignScale(a, b) {
|
|
2381
|
+
const scale = Math.max(a.scale, b.scale);
|
|
2382
|
+
return [
|
|
2383
|
+
a.digits * pow10(scale - a.scale),
|
|
2384
|
+
b.digits * pow10(scale - b.scale)
|
|
2385
|
+
];
|
|
2386
|
+
}
|
|
2387
|
+
function parseDecimal(value, field) {
|
|
2388
|
+
const trimmed = value.trim();
|
|
2389
|
+
if (!/^(?:0|[1-9]\d*)(?:\.\d+)?$/.test(trimmed)) {
|
|
2390
|
+
throw new SwapSdkError(
|
|
2391
|
+
"INVALID_REQUEST",
|
|
2392
|
+
"quote",
|
|
2393
|
+
`${field} must be a non-negative decimal string`
|
|
2394
|
+
);
|
|
614
2395
|
}
|
|
2396
|
+
const [integer = "0", fraction = ""] = trimmed.split(".");
|
|
615
2397
|
return {
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
depositAddress
|
|
619
|
-
},
|
|
620
|
-
preSwap: needsPreSwap ? {
|
|
621
|
-
quote: preSwapQuote,
|
|
622
|
-
tokenIn: sourceToken,
|
|
623
|
-
tokenOut: bluechipToken,
|
|
624
|
-
executor: dexRouter
|
|
625
|
-
} : void 0,
|
|
626
|
-
finalAmountOut: intentsQuote.quoteSuccessResult?.quote?.amountOut || "0"
|
|
2398
|
+
digits: BigInt(`${integer}${fraction}`),
|
|
2399
|
+
scale: fraction.length
|
|
627
2400
|
};
|
|
628
2401
|
}
|
|
2402
|
+
function pow10(exponent) {
|
|
2403
|
+
return 10n ** BigInt(exponent);
|
|
2404
|
+
}
|
|
629
2405
|
|
|
630
|
-
exports.
|
|
631
|
-
exports.
|
|
632
|
-
exports.
|
|
633
|
-
exports.
|
|
634
|
-
exports.
|
|
635
|
-
exports.
|
|
636
|
-
exports.
|
|
637
|
-
exports.
|
|
638
|
-
exports.
|
|
639
|
-
exports.
|
|
2406
|
+
exports.ApiClient = ApiClient;
|
|
2407
|
+
exports.DEFAULT_MCA_SIGNER_PRIORITY = DEFAULT_MCA_SIGNER_PRIORITY;
|
|
2408
|
+
exports.ExecutorRegistry = ExecutorRegistry;
|
|
2409
|
+
exports.SwapClient = SwapClient;
|
|
2410
|
+
exports.SwapSdkError = SwapSdkError;
|
|
2411
|
+
exports.asSwapSdkError = asSwapSdkError;
|
|
2412
|
+
exports.assertBaseUnitAmount = assertBaseUnitAmount;
|
|
2413
|
+
exports.buildMcaWithdrawRelayerRequest = buildMcaWithdrawRelayerRequest;
|
|
2414
|
+
exports.buildNearMcaWithdrawTransactions = buildNearMcaWithdrawTransactions;
|
|
2415
|
+
exports.createExecutionId = createExecutionId;
|
|
2416
|
+
exports.extractMcaWithdrawBusiness = extractMcaWithdrawBusiness;
|
|
2417
|
+
exports.extractMcaWithdrawDepositAddress = extractMcaWithdrawDepositAddress;
|
|
2418
|
+
exports.extractMcaWithdrawSignerWallet = extractMcaWithdrawSignerWallet;
|
|
2419
|
+
exports.formatMcaWallet = formatMcaWallet;
|
|
2420
|
+
exports.formatUnits = formatUnits;
|
|
2421
|
+
exports.fromApiChain = fromApiChain;
|
|
2422
|
+
exports.isSameMcaSignerIdentity = isSameMcaSignerIdentity;
|
|
2423
|
+
exports.normalizeBuild = normalizeBuild;
|
|
2424
|
+
exports.normalizeHistory = normalizeHistory;
|
|
2425
|
+
exports.normalizeHistoryStatus = normalizeHistoryStatus;
|
|
2426
|
+
exports.normalizeMcaQuote = normalizeMcaQuote;
|
|
2427
|
+
exports.normalizeOrderStatus = normalizeOrderStatus;
|
|
2428
|
+
exports.normalizeQuote = normalizeQuote;
|
|
2429
|
+
exports.parseUnits = parseUnits;
|
|
2430
|
+
exports.resolveMcaWithdrawPolicy = resolveMcaWithdrawPolicy;
|
|
2431
|
+
exports.selectMcaSigner = selectMcaSigner;
|
|
2432
|
+
exports.serializeMcaQuoteRequest = serializeMcaQuoteRequest;
|
|
2433
|
+
exports.serializeQuoteRequest = serializeQuoteRequest;
|
|
2434
|
+
exports.toApiAssetAddress = toApiAssetAddress;
|
|
2435
|
+
exports.toApiChain = toApiChain;
|
|
640
2436
|
//# sourceMappingURL=index.js.map
|
|
641
2437
|
//# sourceMappingURL=index.js.map
|