@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.
Files changed (60) hide show
  1. package/README.md +277 -270
  2. package/dist/executors/aptos.d.mts +19 -0
  3. package/dist/executors/aptos.d.ts +19 -0
  4. package/dist/executors/aptos.js +140 -0
  5. package/dist/executors/aptos.js.map +1 -0
  6. package/dist/executors/aptos.mjs +138 -0
  7. package/dist/executors/aptos.mjs.map +1 -0
  8. package/dist/executors/bitcoin.d.mts +22 -0
  9. package/dist/executors/bitcoin.d.ts +22 -0
  10. package/dist/executors/bitcoin.js +153 -0
  11. package/dist/executors/bitcoin.js.map +1 -0
  12. package/dist/executors/bitcoin.mjs +151 -0
  13. package/dist/executors/bitcoin.mjs.map +1 -0
  14. package/dist/executors/evm.d.mts +18 -0
  15. package/dist/executors/evm.d.ts +18 -0
  16. package/dist/executors/evm.js +233 -0
  17. package/dist/executors/evm.js.map +1 -0
  18. package/dist/executors/evm.mjs +231 -0
  19. package/dist/executors/evm.mjs.map +1 -0
  20. package/dist/executors/near.d.mts +18 -0
  21. package/dist/executors/near.d.ts +18 -0
  22. package/dist/executors/near.js +162 -0
  23. package/dist/executors/near.js.map +1 -0
  24. package/dist/executors/near.mjs +160 -0
  25. package/dist/executors/near.mjs.map +1 -0
  26. package/dist/executors/solana.d.mts +20 -0
  27. package/dist/executors/solana.d.ts +20 -0
  28. package/dist/executors/solana.js +151 -0
  29. package/dist/executors/solana.js.map +1 -0
  30. package/dist/executors/solana.mjs +149 -0
  31. package/dist/executors/solana.mjs.map +1 -0
  32. package/dist/executors/sui.d.mts +19 -0
  33. package/dist/executors/sui.d.ts +19 -0
  34. package/dist/executors/sui.js +154 -0
  35. package/dist/executors/sui.js.map +1 -0
  36. package/dist/executors/sui.mjs +152 -0
  37. package/dist/executors/sui.mjs.map +1 -0
  38. package/dist/executors/tron.d.mts +24 -0
  39. package/dist/executors/tron.d.ts +24 -0
  40. package/dist/executors/tron.js +161 -0
  41. package/dist/executors/tron.js.map +1 -0
  42. package/dist/executors/tron.mjs +159 -0
  43. package/dist/executors/tron.mjs.map +1 -0
  44. package/dist/executors/zcash.d.mts +19 -0
  45. package/dist/executors/zcash.d.ts +19 -0
  46. package/dist/executors/zcash.js +158 -0
  47. package/dist/executors/zcash.js.map +1 -0
  48. package/dist/executors/zcash.mjs +156 -0
  49. package/dist/executors/zcash.mjs.map +1 -0
  50. package/dist/index.d.mts +278 -250
  51. package/dist/index.d.ts +278 -250
  52. package/dist/index.js +2369 -573
  53. package/dist/index.js.map +1 -1
  54. package/dist/index.mjs +2340 -560
  55. package/dist/index.mjs.map +1 -1
  56. package/dist/registry-DRYUqs7T.d.mts +532 -0
  57. package/dist/registry-DRYUqs7T.d.ts +532 -0
  58. package/dist/shared-BdH3hWuP.d.ts +23 -0
  59. package/dist/shared-BqpFeosz.d.mts +23 -0
  60. package/package.json +51 -17
package/dist/index.js CHANGED
@@ -1,641 +1,2437 @@
1
1
  'use strict';
2
2
 
3
- var Big = require('big.js');
3
+ var zod = require('zod');
4
4
 
5
- function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
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
- var Big__default = /*#__PURE__*/_interopDefault(Big);
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/utils/logger.ts
10
- var LOG_LEVELS = {
11
- silent: 0,
12
- error: 1,
13
- warn: 2,
14
- info: 3,
15
- debug: 4
16
- };
17
- function getLogLevel() {
18
- if (typeof process !== "undefined" && process.env?.LOG_LEVEL) {
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 typeof process !== "undefined" && process.env?.NODE_ENV === "production" ? "warn" : "debug";
1396
+ return extractPreviewField(preview, "signer_wallet");
25
1397
  }
26
- var currentLogLevel = getLogLevel();
27
- var currentLevelValue = LOG_LEVELS[currentLogLevel];
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
- var logger = {
32
- debug: (...args) => {
33
- if (shouldLog("debug")) {
34
- console.debug(...args);
35
- }
36
- },
37
- info: (...args) => {
38
- if (shouldLog("info")) {
39
- console.info(...args);
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
- warn: (...args) => {
43
- if (shouldLog("warn")) {
44
- console.warn(...args);
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
- error: (...args) => {
48
- if (shouldLog("error")) {
49
- console.error(...args);
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
- // src/utils/index.ts
55
- var bluechipTokensConfig = null;
56
- function setBluechipTokensConfig(config) {
57
- bluechipTokensConfig = config;
58
- }
59
- function getBluechipTokensConfig() {
60
- if (!bluechipTokensConfig) {
61
- logger.warn(
62
- "getBluechipTokensConfig - Bluechip tokens config not set, returning empty config"
63
- );
64
- return {};
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 normalizeTokenId(tokenId, wrapNearContractId = "wrap.near") {
69
- if (!tokenId) {
70
- logger.error("normalizeTokenId - Empty tokenId:", tokenId);
71
- return "";
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
- let normalized = tokenId.replace(/^nep141:/, "");
74
- if (normalized === "near") {
75
- normalized = wrapNearContractId;
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
- if (!normalized) {
78
- logger.error("normalizeTokenId - Result is empty:", {
79
- tokenId,
80
- normalized
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 isNearIntentsSupportedToken(token, bluechipTokens) {
86
- if (!token?.symbol || !token?.address) {
87
- return false;
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
- const config = bluechipTokens || getBluechipTokensConfig();
90
- const normalizedSymbol = token.symbol.toUpperCase();
91
- const symbolKey = normalizedSymbol === "NEAR" || normalizedSymbol === "WNEAR" ? "NEAR" : normalizedSymbol;
92
- const tokenConfig = config[symbolKey];
93
- if (!tokenConfig) {
94
- return false;
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
- const normalizeAddress = (addr) => addr.replace(/^nep141:/, "").toLowerCase();
97
- const tokenAddress = normalizeAddress(token.address);
98
- const configAddress = normalizeAddress(tokenConfig.address || "");
99
- const configAssetId = tokenConfig.assetId ? normalizeAddress(tokenConfig.assetId) : "";
100
- return tokenAddress === configAddress || tokenAddress === configAssetId;
101
- }
102
- function findBestBluechipToken(bluechipTokens, wrapNearContractId = "wrap.near") {
103
- const preferredTokens = [];
104
- if (bluechipTokens.USDT?.address) {
105
- preferredTokens.push({
106
- address: bluechipTokens.USDT.address,
107
- symbol: "USDT",
108
- decimals: bluechipTokens.USDT.decimals || 6,
109
- chain: "near"
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
- if (bluechipTokens.USDC?.address) {
113
- preferredTokens.push({
114
- address: bluechipTokens.USDC.address,
115
- symbol: "USDC",
116
- decimals: bluechipTokens.USDC.decimals || 6,
117
- chain: "near"
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
- if (bluechipTokens.NEAR?.address) {
121
- preferredTokens.push({
122
- address: bluechipTokens.NEAR.address,
123
- symbol: "wNEAR",
124
- decimals: bluechipTokens.NEAR.decimals || 24,
125
- chain: "near"
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
- if (preferredTokens.length === 0) {
129
- logger.warn(
130
- "findBestBluechipToken - No preferred tokens found, using wrap.near"
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
- address: wrapNearContractId,
134
- symbol: "wNEAR",
135
- decimals: 24,
136
- chain: "near"
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
- logger.debug("findBestBluechipToken - Selected token:", preferredTokens[0]);
140
- return preferredTokens[0];
141
- }
142
- function convertSlippageToBasisPoints(slippage) {
143
- if (slippage >= 1) {
144
- return Math.round(slippage);
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
- if (slippage > 0 && slippage < 0.01) {
147
- return Math.round(slippage * 1e4);
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
- if (slippage >= 0.01 && slippage < 1) {
150
- return Math.round(slippage * 100);
1927
+ emitEvent(event, local) {
1928
+ this.onEvent?.(event);
1929
+ if (local !== this.onEvent) local?.(event);
151
1930
  }
152
- return Math.round(slippage);
153
- }
154
- function normalizeDestinationAsset(assetId, wrapNearContractId = "wrap.near") {
155
- if (!assetId) return assetId;
156
- if (assetId.startsWith("nep141:") || assetId.startsWith("nep245:")) {
157
- return assetId;
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
- if (assetId === "near" || assetId === "nep141:near") {
160
- return `nep141:${wrapNearContractId}`;
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
- if (assetId.includes(".")) {
163
- return `nep141:${normalizeTokenId(assetId, wrapNearContractId)}`;
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 assetId;
1973
+ return readString2(value[field]);
166
1974
  }
167
- var NearSmartRouter = class {
1975
+
1976
+ // src/client/SwapClient.ts
1977
+ var SwapClient = class {
168
1978
  constructor(config) {
169
- this.findPathAdapter = config.findPathAdapter;
170
- this.nearChainAdapter = config.nearChainAdapter;
171
- this.configAdapter = config.configAdapter;
172
- this.wrapNearContractId = this.configAdapter.getWrapNearContractId();
173
- this.refExchangeId = this.configAdapter.getRefExchangeId();
174
- this.tokenStorageDepositRead = this.configAdapter.getTokenStorageDepositRead?.() || "1250000000000000000000";
175
- }
176
- /**
177
- * Get a swap quote (normalizes token ids and queries FindPath for routes).
178
- */
179
- async quote(params) {
180
- try {
181
- const {
182
- tokenIn,
183
- tokenOut,
184
- amountIn,
185
- slippage,
186
- swapType: _swapType = "EXACT_INPUT"
187
- // Currently not used, reserved for future use
188
- } = params;
189
- if (!tokenIn?.address || !tokenOut?.address) {
190
- return {
191
- success: false,
192
- tokenIn: params.tokenIn,
193
- tokenOut: params.tokenOut,
194
- amountIn: params.amountIn,
195
- amountOut: "0",
196
- minAmountOut: "0",
197
- routes: [],
198
- error: "Missing token address"
199
- };
200
- }
201
- const normalizedTokenIn = normalizeTokenId(
202
- tokenIn.address,
203
- this.wrapNearContractId
204
- );
205
- const normalizedTokenOut = normalizeTokenId(
206
- tokenOut.address,
207
- this.wrapNearContractId
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
- * Execute a swap: optionally adds `storage_deposit` for the recipient, then calls REF via `ft_transfer_call`.
306
- */
307
- async executeSwap(params) {
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 { quote, recipient, depositAddress } = params;
310
- if (!quote.success || !quote.routes.length) {
311
- return {
312
- success: false,
313
- error: "Invalid quote"
314
- };
315
- }
316
- const swapActions = [];
317
- const routesToUse = quote.rawRoutes || quote.routes;
318
- routesToUse.forEach((route) => {
319
- const pools = route.pools || [];
320
- pools.forEach((pool) => {
321
- const poolCopy = { ...pool };
322
- if (+(poolCopy?.amount_in || 0) == 0) {
323
- delete poolCopy.amount_in;
324
- }
325
- poolCopy.pool_id = Number(poolCopy.pool_id);
326
- swapActions.push(poolCopy);
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
- if (!swapActions.length) {
330
- return {
331
- success: false,
332
- error: "No swap actions"
333
- };
334
- }
335
- const finalRecipient = depositAddress || recipient;
336
- const transactions = [];
337
- if (finalRecipient && quote.tokenOut?.address) {
338
- let isRegistered = false;
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
- if (!isRegistered) {
352
- logger.debug("SmartRouter - Registering recipient account:", {
353
- contractId: quote.tokenOut.address,
354
- accountId: finalRecipient
355
- });
356
- transactions.push({
357
- contractId: quote.tokenOut.address,
358
- methodName: "storage_deposit",
359
- args: {
360
- account_id: finalRecipient,
361
- registration_only: true
362
- },
363
- gas: "50",
364
- expandDeposit: this.tokenStorageDepositRead
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
- const swapMsg = {
369
- force: 0,
370
- actions: swapActions,
371
- skip_unwrap_near: false
372
- };
373
- if (finalRecipient) {
374
- swapMsg.swap_out_recipient = finalRecipient;
375
- }
376
- logger.debug("SmartRouter - Executing swap:", {
377
- contractId: quote.tokenIn.address,
378
- receiver_id: this.refExchangeId,
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
- return {
414
- success: false,
415
- error: error?.message || "Execute swap failed"
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
- * Get supported chain
421
- */
422
- getSupportedChain() {
423
- return "near";
424
- }
425
- };
426
- async function completeQuote(params, config) {
427
- const {
428
- sourceToken,
429
- targetToken,
430
- sourceChain,
431
- targetChain: _targetChain,
432
- // Reserved for future use
433
- amountIn,
434
- slippage,
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
- logger.debug("DEX Aggregator - Using bluechip token:", {
459
- address: bluechipToken.address,
460
- symbol: bluechipToken.symbol,
461
- decimals: bluechipToken.decimals
462
- });
463
- let preSwapQuote = null;
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
- amountIn,
478
- slippage
479
- });
480
- if (!dexRouter) {
481
- throw new Error(
482
- `No DEX router registered for sourceChain=${sourceChain}`
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
- preSwapQuote = await dexRouter.quote({
486
- tokenIn: sourceToken,
487
- tokenOut: bluechipToken,
488
- amountIn,
489
- slippage,
490
- swapType: "EXACT_INPUT"
491
- });
492
- if (!preSwapQuote.success) {
493
- logger.error("DEX Aggregator - Pre-swap quote failed:", {
494
- error: preSwapQuote.error,
495
- tokenIn: sourceToken,
496
- tokenOut: bluechipToken
497
- });
498
- throw new Error(`Pre-swap quote failed: ${preSwapQuote.error}`);
499
- }
500
- const preSwapAmountOut = preSwapQuote.amountOut;
501
- if (!preSwapAmountOut || new Big__default.default(preSwapAmountOut).lte(0)) {
502
- logger.error("DEX Aggregator - Pre-swap amountOut is invalid:", {
503
- amountOut: preSwapAmountOut,
504
- tokenIn: sourceToken,
505
- tokenOut: bluechipToken
506
- });
507
- throw new Error(
508
- "Pre-swap returned invalid amount: amount is too small or zero"
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
- let normalizedSourceAsset;
518
- if (needsPreSwap) {
519
- const bluechipKey = bluechipToken.symbol?.toUpperCase() === "WNEAR" ? "NEAR" : bluechipToken.symbol?.toUpperCase();
520
- const bluechipTokenConfig = bluechipKey && bluechipTokens[bluechipKey] || void 0;
521
- if (bluechipTokenConfig?.assetId) {
522
- normalizedSourceAsset = bluechipTokenConfig.assetId;
523
- logger.debug("Using bluechip token assetId for NearIntents:", {
524
- symbol: bluechipToken.symbol,
525
- assetId: normalizedSourceAsset,
526
- contractAddress: bluechipToken.address
527
- });
528
- } else {
529
- normalizedSourceAsset = `nep141:${bluechipToken.address}`;
530
- logger.warn(
531
- "Bluechip token assetId not found, using contractAddress with prefix:",
532
- {
533
- symbol: bluechipToken.symbol,
534
- normalizedSourceAsset
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
- } else {
539
- if (sourceToken.symbol) {
540
- const sourceKey = sourceToken.symbol.toUpperCase();
541
- const sourceTokenConfig = bluechipTokens[sourceKey];
542
- if (sourceTokenConfig?.assetId) {
543
- normalizedSourceAsset = sourceTokenConfig.assetId;
544
- } else {
545
- normalizedSourceAsset = normalizeTokenId(
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
- if (!normalizedSourceAsset.startsWith("nep141:")) {
559
- normalizedSourceAsset = `nep141:${normalizedSourceAsset}`;
560
- }
561
- }
562
- }
563
- let normalizedTargetAsset = targetToken.address;
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
- if (intentsQuote.quoteStatus !== "success") {
600
- const errorMessage = intentsQuote.message || "Unknown error";
601
- logger.error("DEX Aggregator - NearIntents quote failed:", {
602
- error: errorMessage,
603
- originAsset: normalizedSourceAsset,
604
- destinationAsset: normalizedTargetAsset,
605
- amount: intentsAmount,
606
- needsPreSwap,
607
- preSwapAmountOut: needsPreSwap ? preSwapQuote.amountOut : void 0
608
- });
609
- throw new Error(`Intents quote failed: ${errorMessage}`);
610
- }
611
- const depositAddress = intentsQuote.quoteSuccessResult?.quote?.depositAddress || "";
612
- if (!depositAddress) {
613
- throw new Error("Deposit address not found in intents quote");
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
- intents: {
617
- quote: intentsQuote,
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.NearSmartRouter = NearSmartRouter;
631
- exports.completeQuote = completeQuote;
632
- exports.convertSlippageToBasisPoints = convertSlippageToBasisPoints;
633
- exports.findBestBluechipToken = findBestBluechipToken;
634
- exports.getBluechipTokensConfig = getBluechipTokensConfig;
635
- exports.isNearIntentsSupportedToken = isNearIntentsSupportedToken;
636
- exports.logger = logger;
637
- exports.normalizeDestinationAsset = normalizeDestinationAsset;
638
- exports.normalizeTokenId = normalizeTokenId;
639
- exports.setBluechipTokensConfig = setBluechipTokensConfig;
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