@ironwallet/mcp-server 1.0.0-preprod.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 (110) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +161 -0
  3. package/assets/knight.png +0 -0
  4. package/assets/wordmark.svg +20 -0
  5. package/dist/api/auth.d.ts +20 -0
  6. package/dist/api/auth.js +92 -0
  7. package/dist/api/auth.js.map +1 -0
  8. package/dist/api/balances.d.ts +25 -0
  9. package/dist/api/balances.js +565 -0
  10. package/dist/api/balances.js.map +1 -0
  11. package/dist/api/http.d.ts +34 -0
  12. package/dist/api/http.js +277 -0
  13. package/dist/api/http.js.map +1 -0
  14. package/dist/api/relay.d.ts +49 -0
  15. package/dist/api/relay.js +194 -0
  16. package/dist/api/relay.js.map +1 -0
  17. package/dist/api/rpc.d.ts +10 -0
  18. package/dist/api/rpc.js +26 -0
  19. package/dist/api/rpc.js.map +1 -0
  20. package/dist/api/swap.d.ts +134 -0
  21. package/dist/api/swap.js +554 -0
  22. package/dist/api/swap.js.map +1 -0
  23. package/dist/cli.d.ts +6 -0
  24. package/dist/cli.js +20 -0
  25. package/dist/cli.js.map +1 -0
  26. package/dist/config.d.ts +91 -0
  27. package/dist/config.js +175 -0
  28. package/dist/config.js.map +1 -0
  29. package/dist/generated/env-config.d.ts +5 -0
  30. package/dist/generated/env-config.js +23 -0
  31. package/dist/generated/env-config.js.map +1 -0
  32. package/dist/generated/env-profile.d.ts +15 -0
  33. package/dist/generated/env-profile.js +2 -0
  34. package/dist/generated/env-profile.js.map +1 -0
  35. package/dist/keystore/crypto.d.ts +17 -0
  36. package/dist/keystore/crypto.js +57 -0
  37. package/dist/keystore/crypto.js.map +1 -0
  38. package/dist/keystore/store.d.ts +45 -0
  39. package/dist/keystore/store.js +224 -0
  40. package/dist/keystore/store.js.map +1 -0
  41. package/dist/keystore/types.d.ts +31 -0
  42. package/dist/keystore/types.js +2 -0
  43. package/dist/keystore/types.js.map +1 -0
  44. package/dist/local-secrets.d.ts +16 -0
  45. package/dist/local-secrets.js +73 -0
  46. package/dist/local-secrets.js.map +1 -0
  47. package/dist/log.d.ts +30 -0
  48. package/dist/log.js +323 -0
  49. package/dist/log.js.map +1 -0
  50. package/dist/passphrase.d.ts +6 -0
  51. package/dist/passphrase.js +10 -0
  52. package/dist/passphrase.js.map +1 -0
  53. package/dist/policy.d.ts +22 -0
  54. package/dist/policy.js +108 -0
  55. package/dist/policy.js.map +1 -0
  56. package/dist/server.d.ts +6 -0
  57. package/dist/server.js +38 -0
  58. package/dist/server.js.map +1 -0
  59. package/dist/signing/bitcoin.d.ts +18 -0
  60. package/dist/signing/bitcoin.js +84 -0
  61. package/dist/signing/bitcoin.js.map +1 -0
  62. package/dist/signing/dispatch.d.ts +7 -0
  63. package/dist/signing/dispatch.js +35 -0
  64. package/dist/signing/dispatch.js.map +1 -0
  65. package/dist/signing/evm.d.ts +16 -0
  66. package/dist/signing/evm.js +59 -0
  67. package/dist/signing/evm.js.map +1 -0
  68. package/dist/signing/index.d.ts +10 -0
  69. package/dist/signing/index.js +57 -0
  70. package/dist/signing/index.js.map +1 -0
  71. package/dist/signing/solana.d.ts +18 -0
  72. package/dist/signing/solana.js +106 -0
  73. package/dist/signing/solana.js.map +1 -0
  74. package/dist/signing/swap.d.ts +10 -0
  75. package/dist/signing/swap.js +114 -0
  76. package/dist/signing/swap.js.map +1 -0
  77. package/dist/signing/ton.d.ts +33 -0
  78. package/dist/signing/ton.js +166 -0
  79. package/dist/signing/ton.js.map +1 -0
  80. package/dist/signing/tron.d.ts +11 -0
  81. package/dist/signing/tron.js +39 -0
  82. package/dist/signing/tron.js.map +1 -0
  83. package/dist/signing/types.d.ts +23 -0
  84. package/dist/signing/types.js +2 -0
  85. package/dist/signing/types.js.map +1 -0
  86. package/dist/signing/xrp.d.ts +11 -0
  87. package/dist/signing/xrp.js +122 -0
  88. package/dist/signing/xrp.js.map +1 -0
  89. package/dist/tools/helpers.d.ts +42 -0
  90. package/dist/tools/helpers.js +80 -0
  91. package/dist/tools/helpers.js.map +1 -0
  92. package/dist/tools/index.d.ts +7 -0
  93. package/dist/tools/index.js +16 -0
  94. package/dist/tools/index.js.map +1 -0
  95. package/dist/tools/swap.d.ts +6 -0
  96. package/dist/tools/swap.js +343 -0
  97. package/dist/tools/swap.js.map +1 -0
  98. package/dist/tools/transfer.d.ts +6 -0
  99. package/dist/tools/transfer.js +284 -0
  100. package/dist/tools/transfer.js.map +1 -0
  101. package/dist/tools/wallets.d.ts +6 -0
  102. package/dist/tools/wallets.js +79 -0
  103. package/dist/tools/wallets.js.map +1 -0
  104. package/dist/wallet/derive.d.ts +53 -0
  105. package/dist/wallet/derive.js +233 -0
  106. package/dist/wallet/derive.js.map +1 -0
  107. package/dist/web/manager.d.ts +15 -0
  108. package/dist/web/manager.js +444 -0
  109. package/dist/web/manager.js.map +1 -0
  110. package/package.json +54 -0
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Swap Proxy (SWP) HTTP client — matches mobile SwapProxyApiClient:
3
+ * /swp/refs/* discovery
4
+ * /swp/exchange/* estimate → create → execute → status
5
+ *
6
+ * Auth: IWB Bearer + device headers (same as relay). Not the transfer relay.
7
+ */
8
+ export interface SwapAsset {
9
+ network: string;
10
+ symbol: string;
11
+ address?: string | null;
12
+ decimals: number;
13
+ }
14
+ export interface SwapSide {
15
+ asset: SwapAsset;
16
+ address: string;
17
+ amount?: string;
18
+ }
19
+ export interface SwapCallOpts {
20
+ correlationId?: string;
21
+ }
22
+ export interface SwapApiError {
23
+ code?: number;
24
+ reason?: string;
25
+ traceId?: string;
26
+ raw?: unknown;
27
+ }
28
+ export declare class SwapError extends Error {
29
+ readonly code?: number;
30
+ readonly traceId?: string;
31
+ readonly raw?: unknown;
32
+ constructor(message: string, info?: SwapApiError);
33
+ }
34
+ export interface SwapNetworkItem {
35
+ id?: number;
36
+ name: string;
37
+ decimals?: number;
38
+ }
39
+ export declare function listSwapNetworks(token: string, opts?: SwapCallOpts): Promise<SwapNetworkItem[]>;
40
+ export interface SwapAssetsPage {
41
+ items: SwapAsset[];
42
+ total: number;
43
+ page: number;
44
+ pageSize: number;
45
+ }
46
+ export declare function listSwapAssetsFrom(token: string, params?: {
47
+ search?: string;
48
+ page?: number;
49
+ pageSize?: number;
50
+ networks?: string[];
51
+ }, opts?: SwapCallOpts): Promise<SwapAssetsPage>;
52
+ export declare function listSwapAssetsTo(token: string, params: {
53
+ from: Pick<SwapAsset, "network" | "symbol" | "address">;
54
+ search?: string;
55
+ page?: number;
56
+ pageSize?: number;
57
+ networks?: string[];
58
+ }, opts?: SwapCallOpts): Promise<SwapAssetsPage>;
59
+ export interface SwapEstimateResult {
60
+ operationId: string;
61
+ provider?: string;
62
+ from: SwapSide & {
63
+ amount?: string;
64
+ };
65
+ to: SwapSide & {
66
+ amount?: string;
67
+ };
68
+ correctedAmountFrom?: string;
69
+ correctedAmountTo?: string;
70
+ details?: Record<string, unknown>;
71
+ raw: Record<string, unknown>;
72
+ }
73
+ export declare function estimateSwap(token: string, params: {
74
+ from: SwapSide;
75
+ to: SwapSide;
76
+ maxMode?: boolean;
77
+ }, opts?: SwapCallOpts): Promise<SwapEstimateResult>;
78
+ export interface SwapTxToSign {
79
+ txId?: string;
80
+ txData?: string;
81
+ extraData?: string;
82
+ type?: string;
83
+ }
84
+ export interface SwapCreateResult {
85
+ operationId: string;
86
+ swapOrderId: string;
87
+ provider?: string;
88
+ txsToSign: SwapTxToSign[];
89
+ raw: Record<string, unknown>;
90
+ }
91
+ export declare function createSwap(token: string, params: {
92
+ operationId: string;
93
+ correctedAmountFrom: string;
94
+ from: SwapSide;
95
+ to: SwapSide;
96
+ maxMode?: boolean;
97
+ }, opts?: SwapCallOpts): Promise<SwapCreateResult>;
98
+ export interface SwapSignedTxDto {
99
+ type: string;
100
+ signedTx: string;
101
+ txData: string;
102
+ txHash: string;
103
+ txId: string;
104
+ }
105
+ export interface SwapExecuteResult {
106
+ operationId: string;
107
+ txHash?: string;
108
+ raw: Record<string, unknown>;
109
+ }
110
+ export declare function executeSwap(token: string, params: {
111
+ operationId: string;
112
+ swapOrderId: string;
113
+ from: SwapSide;
114
+ to: SwapSide;
115
+ signedTransactions: SwapSignedTxDto[];
116
+ }, opts?: SwapCallOpts): Promise<SwapExecuteResult>;
117
+ export declare function getSwapStatus(token: string, operationId: string, opts?: SwapCallOpts): Promise<Record<string, unknown>>;
118
+ /**
119
+ * Resolve decimals for a short asset ref via SWP catalog.
120
+ * Sell assets: `/assets/from`. Buy assets: `/assets/to` (needs `from` context).
121
+ *
122
+ * Walks catalog pages (not just page 1). Prefer passing `decimals` from
123
+ * `list_swap_assets` to skip lookup entirely.
124
+ */
125
+ export declare function resolveSwapAsset(token: string, ref: {
126
+ network: string;
127
+ symbol: string;
128
+ address?: string | null;
129
+ decimals?: number;
130
+ }, opts?: SwapCallOpts & {
131
+ /** When resolving a buy-side asset, pass the sell asset for `/assets/to`. */
132
+ fromForToLookup?: Pick<SwapAsset, "network" | "symbol" | "address">;
133
+ }): Promise<SwapAsset>;
134
+ export declare function assetKey(a: Pick<SwapAsset, "network" | "symbol" | "address">): string;
@@ -0,0 +1,554 @@
1
+ /**
2
+ * Swap Proxy (SWP) HTTP client — matches mobile SwapProxyApiClient:
3
+ * /swp/refs/* discovery
4
+ * /swp/exchange/* estimate → create → execute → status
5
+ *
6
+ * Auth: IWB Bearer + device headers (same as relay). Not the transfer relay.
7
+ */
8
+ import { getConfig } from "../config.js";
9
+ import { authHeaders } from "./auth.js";
10
+ import { httpJson } from "./http.js";
11
+ import { logError, logInfo, newCorrelationId } from "../log.js";
12
+ export class SwapError extends Error {
13
+ code;
14
+ traceId;
15
+ raw;
16
+ constructor(message, info = {}) {
17
+ super(message);
18
+ this.name = "SwapError";
19
+ this.code = info.code;
20
+ this.traceId = info.traceId;
21
+ this.raw = info.raw;
22
+ }
23
+ }
24
+ function capitalizeNetwork(name) {
25
+ const t = name.trim();
26
+ if (!t)
27
+ return t;
28
+ return t.charAt(0).toUpperCase() + t.slice(1).toLowerCase();
29
+ }
30
+ /** SwapKit tickers are uppercase (`USDT`). SWP catalog symbols are not (`usdt`, `USDt`). */
31
+ function canonicalSwapSymbol(symbol) {
32
+ return symbol.trim().toUpperCase();
33
+ }
34
+ function assetToJson(asset) {
35
+ const body = {
36
+ network: asset.network.toLowerCase(),
37
+ symbol: canonicalSwapSymbol(asset.symbol),
38
+ decimals: asset.decimals,
39
+ };
40
+ if (asset.address)
41
+ body.address = asset.address;
42
+ return body;
43
+ }
44
+ function sideToJson(side) {
45
+ const body = {
46
+ asset: assetToJson(side.asset),
47
+ address: side.address,
48
+ };
49
+ if (side.amount !== undefined)
50
+ body.amount = side.amount;
51
+ return body;
52
+ }
53
+ function parseSwapError(body, fallback) {
54
+ if (!body)
55
+ return new SwapError(fallback);
56
+ try {
57
+ const json = JSON.parse(body);
58
+ const errors = json.errors;
59
+ const first = Array.isArray(errors) && errors[0] && typeof errors[0] === "object"
60
+ ? errors[0]
61
+ : json;
62
+ const codeRaw = first.code ?? json.status;
63
+ const code = typeof codeRaw === "number"
64
+ ? codeRaw
65
+ : typeof codeRaw === "string"
66
+ ? Number.parseInt(codeRaw, 10)
67
+ : undefined;
68
+ const reason = (typeof first.reason === "string" && first.reason) ||
69
+ (typeof json.title === "string" && json.title) ||
70
+ fallback;
71
+ const traceId = typeof json.traceId === "string" ? json.traceId : undefined;
72
+ const msg = code !== undefined && Number.isFinite(code)
73
+ ? `Swap API error ${code}: ${reason}`
74
+ : `Swap API error: ${reason}`;
75
+ return new SwapError(msg, { code, reason, traceId, raw: json });
76
+ }
77
+ catch {
78
+ return new SwapError(`${fallback}: ${body.slice(0, 300)}`);
79
+ }
80
+ }
81
+ async function swpJson(path, init, opts) {
82
+ const cfg = getConfig();
83
+ const url = `${cfg.swapProxyUrl.replace(/\/$/, "")}${path}`;
84
+ const correlationId = opts.correlationId ?? newCorrelationId("swp");
85
+ try {
86
+ return await httpJson(url, init, {
87
+ correlationId,
88
+ label: opts.label,
89
+ retry: opts.retry,
90
+ timeoutMs: opts.timeoutMs,
91
+ });
92
+ }
93
+ catch (e) {
94
+ const err = e;
95
+ if (err && typeof err.status === "number") {
96
+ throw parseSwapError(err.body, err.message);
97
+ }
98
+ throw e;
99
+ }
100
+ }
101
+ function assertNoError(json, label) {
102
+ const errors = json.errors;
103
+ const hasErrors = Array.isArray(errors)
104
+ ? errors.length > 0
105
+ : Boolean(errors);
106
+ if (json.error || hasErrors) {
107
+ throw parseSwapError(JSON.stringify(json), `${label} returned an error`);
108
+ }
109
+ }
110
+ export async function listSwapNetworks(token, opts = {}) {
111
+ const correlationId = opts.correlationId ?? newCorrelationId("swp-net");
112
+ logInfo("swap.networks.start", { correlationId });
113
+ try {
114
+ const json = await swpJson("/swp/refs/networks", {
115
+ method: "POST",
116
+ headers: { ...authHeaders(token), "Content-Type": "application/json" },
117
+ body: JSON.stringify({}),
118
+ }, { correlationId, label: "swap.networks", retry: true });
119
+ assertNoError(json, "list_swap_networks");
120
+ const items = json.items ?? [];
121
+ const result = items.map((i) => ({
122
+ id: typeof i.id === "number" ? i.id : undefined,
123
+ name: String(i.name ?? ""),
124
+ decimals: typeof i.decimals === "number" ? i.decimals : undefined,
125
+ }));
126
+ logInfo("swap.networks.ok", { correlationId, count: result.length });
127
+ return result;
128
+ }
129
+ catch (e) {
130
+ logError("swap.networks.fail", e, { correlationId });
131
+ throw e;
132
+ }
133
+ }
134
+ export async function listSwapAssetsFrom(token, params = {}, opts = {}) {
135
+ const correlationId = opts.correlationId ?? newCorrelationId("swp-af");
136
+ const page = params.page ?? 1;
137
+ const pageSize = params.pageSize ?? 50;
138
+ const payload = {};
139
+ if (params.networks?.length) {
140
+ payload.networks = params.networks.map(capitalizeNetwork);
141
+ }
142
+ const q = params.search?.trim();
143
+ if (q)
144
+ payload.findBySymbol = q;
145
+ const json = await swpJson("/swp/refs/assets/from", {
146
+ method: "POST",
147
+ headers: { ...authHeaders(token), "Content-Type": "application/json" },
148
+ body: JSON.stringify({ page, pageSize, payload }),
149
+ }, { correlationId, label: "swap.assets.from", retry: true });
150
+ assertNoError(json, "list_swap_assets(from)");
151
+ return parseAssetsPage(json, page, pageSize);
152
+ }
153
+ export async function listSwapAssetsTo(token, params, opts = {}) {
154
+ const correlationId = opts.correlationId ?? newCorrelationId("swp-at");
155
+ const page = params.page ?? 1;
156
+ const pageSize = params.pageSize ?? 50;
157
+ const body = {
158
+ page,
159
+ pageSize,
160
+ fromAsset: {
161
+ network: capitalizeNetwork(params.from.network),
162
+ symbol: canonicalSwapSymbol(params.from.symbol),
163
+ ...(params.from.address ? { address: params.from.address } : {}),
164
+ },
165
+ };
166
+ if (params.networks?.length) {
167
+ body.toNetworks = params.networks.map(capitalizeNetwork);
168
+ }
169
+ const q = params.search?.trim();
170
+ if (q)
171
+ body.findBySymbol = q;
172
+ const json = await swpJson("/swp/refs/assets/to", {
173
+ method: "POST",
174
+ headers: { ...authHeaders(token), "Content-Type": "application/json" },
175
+ body: JSON.stringify(body),
176
+ }, { correlationId, label: "swap.assets.to", retry: true });
177
+ assertNoError(json, "list_swap_assets(to)");
178
+ return parseAssetsPage(json, page, pageSize);
179
+ }
180
+ function parseAssetsPage(json, page, pageSize) {
181
+ const raw = json.items ?? [];
182
+ const items = raw.map((i) => ({
183
+ network: String(i.network ?? "").toLowerCase(),
184
+ symbol: canonicalSwapSymbol(String(i.symbol ?? "")),
185
+ decimals: Number(i.decimals ?? 0),
186
+ address: i.address == null || i.address === "" ? null : String(i.address),
187
+ }));
188
+ return {
189
+ items,
190
+ total: typeof json.total === "number" ? json.total : items.length,
191
+ page: typeof json.page === "number" ? json.page : page,
192
+ pageSize: typeof json.pageSize === "number" ? json.pageSize : pageSize,
193
+ };
194
+ }
195
+ export async function estimateSwap(token, params, opts = {}) {
196
+ const correlationId = opts.correlationId ?? newCorrelationId("swp-est");
197
+ logInfo("swap.estimate.start", {
198
+ correlationId,
199
+ fromNetwork: params.from.asset.network,
200
+ toNetwork: params.to.asset.network,
201
+ fromSymbol: params.from.asset.symbol,
202
+ toSymbol: params.to.asset.symbol,
203
+ amount: params.from.amount,
204
+ maxMode: Boolean(params.maxMode),
205
+ });
206
+ try {
207
+ const json = await swpJson("/swp/exchange/estimate", {
208
+ method: "POST",
209
+ headers: { ...authHeaders(token), "Content-Type": "application/json" },
210
+ body: JSON.stringify({
211
+ from: sideToJson(params.from),
212
+ to: sideToJson(params.to),
213
+ maxMode: Boolean(params.maxMode),
214
+ }),
215
+ }, { correlationId, label: "swap.estimate", retry: true });
216
+ assertNoError(json, "estimate_swap");
217
+ const result = mapEstimate(json);
218
+ logInfo("swap.estimate.ok", {
219
+ correlationId,
220
+ operationId: result.operationId,
221
+ correctedAmountFrom: result.correctedAmountFrom,
222
+ amountTo: result.to.amount,
223
+ });
224
+ return result;
225
+ }
226
+ catch (e) {
227
+ logError("swap.estimate.fail", e, { correlationId });
228
+ throw e;
229
+ }
230
+ }
231
+ function mapSide(raw) {
232
+ const assetRaw = raw?.asset ?? {};
233
+ return {
234
+ asset: {
235
+ network: String(assetRaw.network ?? "").toLowerCase(),
236
+ symbol: canonicalSwapSymbol(String(assetRaw.symbol ?? "")),
237
+ decimals: Number(assetRaw.decimals ?? 0),
238
+ address: assetRaw.address == null || assetRaw.address === ""
239
+ ? null
240
+ : String(assetRaw.address),
241
+ },
242
+ address: String(raw?.address ?? ""),
243
+ amount: raw?.amount != null ? String(raw.amount) : undefined,
244
+ };
245
+ }
246
+ function mapEstimate(json) {
247
+ return {
248
+ operationId: String(json.operationId ?? ""),
249
+ provider: json.provider != null ? String(json.provider) : undefined,
250
+ from: mapSide(json.from),
251
+ to: mapSide(json.to),
252
+ correctedAmountFrom: json.correctedAmountFrom != null ? String(json.correctedAmountFrom) : undefined,
253
+ correctedAmountTo: json.correctedAmountTo != null ? String(json.correctedAmountTo) : undefined,
254
+ details: slimSwapDetails(json.details ?? undefined),
255
+ raw: json,
256
+ };
257
+ }
258
+ /** Agent-facing swap fees: network / tokenFee / coinFee only. */
259
+ const SWAP_FEE_KEYS = ["network", "tokenFee", "coinFee"];
260
+ function pickSwapFees(fees) {
261
+ if (!fees || typeof fees !== "object" || Array.isArray(fees))
262
+ return undefined;
263
+ const src = fees;
264
+ const out = {};
265
+ for (const key of SWAP_FEE_KEYS) {
266
+ if (src[key] !== undefined)
267
+ out[key] = src[key];
268
+ }
269
+ return Object.keys(out).length > 0 ? out : undefined;
270
+ }
271
+ function slimSwapDetails(details) {
272
+ if (!details)
273
+ return undefined;
274
+ if (!("fees" in details))
275
+ return details;
276
+ const { fees, ...rest } = details;
277
+ const slim = pickSwapFees(fees);
278
+ return slim ? { ...rest, fees: slim } : rest;
279
+ }
280
+ function slimSwapPayload(json) {
281
+ const next = { ...json };
282
+ if ("fees" in next) {
283
+ const slim = pickSwapFees(next.fees);
284
+ if (slim)
285
+ next.fees = slim;
286
+ else
287
+ delete next.fees;
288
+ }
289
+ if (next.details && typeof next.details === "object" && !Array.isArray(next.details)) {
290
+ next.details = slimSwapDetails(next.details);
291
+ }
292
+ return next;
293
+ }
294
+ export async function createSwap(token, params, opts = {}) {
295
+ const correlationId = opts.correlationId ?? newCorrelationId("swp-crt");
296
+ logInfo("swap.create.start", {
297
+ correlationId,
298
+ operationId: params.operationId,
299
+ correctedAmountFrom: params.correctedAmountFrom,
300
+ });
301
+ try {
302
+ const json = await swpJson("/swp/exchange/create", {
303
+ method: "POST",
304
+ headers: {
305
+ ...authHeaders(token),
306
+ "Content-Type": "application/json",
307
+ "x-idempotency-key": params.operationId,
308
+ },
309
+ body: JSON.stringify({
310
+ operationId: params.operationId,
311
+ correctedAmountFrom: params.correctedAmountFrom,
312
+ from: sideToJson(params.from),
313
+ to: sideToJson(params.to),
314
+ maxMode: Boolean(params.maxMode),
315
+ }),
316
+ },
317
+ // Create is not safely retryable after acceptance; keep default POST no-retry.
318
+ { correlationId, label: "swap.create", retry: false });
319
+ assertNoError(json, "create_swap");
320
+ const order = json.order;
321
+ const txs = json.txsToSign ?? [];
322
+ const result = {
323
+ operationId: String(json.operationId ?? params.operationId),
324
+ swapOrderId: String(order?.uid ?? order?.id ?? ""),
325
+ provider: json.provider != null ? String(json.provider) : undefined,
326
+ txsToSign: txs.map((t) => ({
327
+ txId: t.txId != null ? String(t.txId) : undefined,
328
+ txData: t.txData != null ? String(t.txData) : undefined,
329
+ extraData: t.extraData != null ? String(t.extraData) : undefined,
330
+ type: t.type != null ? String(t.type) : undefined,
331
+ })),
332
+ raw: json,
333
+ };
334
+ logInfo("swap.create.ok", {
335
+ correlationId,
336
+ operationId: result.operationId,
337
+ swapOrderId: result.swapOrderId,
338
+ txCount: result.txsToSign.length,
339
+ });
340
+ return result;
341
+ }
342
+ catch (e) {
343
+ logError("swap.create.fail", e, {
344
+ correlationId,
345
+ operationId: params.operationId,
346
+ });
347
+ throw e;
348
+ }
349
+ }
350
+ export async function executeSwap(token, params, opts = {}) {
351
+ const correlationId = opts.correlationId ?? newCorrelationId("swp-exe");
352
+ const cfg = getConfig();
353
+ logInfo("swap.execute.start", {
354
+ correlationId,
355
+ operationId: params.operationId,
356
+ swapOrderId: params.swapOrderId,
357
+ signedCount: params.signedTransactions.length,
358
+ });
359
+ try {
360
+ const json = await swpJson("/swp/exchange/execute", {
361
+ method: "POST",
362
+ headers: {
363
+ ...authHeaders(token),
364
+ "Content-Type": "application/json",
365
+ "x-idempotency-key": params.operationId,
366
+ },
367
+ body: JSON.stringify({
368
+ operationId: params.operationId,
369
+ swapOrderId: params.swapOrderId,
370
+ from: sideToJson(params.from),
371
+ to: sideToJson(params.to),
372
+ signedTransactions: params.signedTransactions,
373
+ }),
374
+ }, {
375
+ correlationId,
376
+ label: "swap.execute",
377
+ retry: false,
378
+ timeoutMs: cfg.httpForwardTimeoutMs,
379
+ });
380
+ assertNoError(json, "execute_swap");
381
+ const result = {
382
+ operationId: String(json.operationId ?? params.operationId),
383
+ txHash: json.txHash != null
384
+ ? String(json.txHash)
385
+ : json.hash != null
386
+ ? String(json.hash)
387
+ : undefined,
388
+ raw: json,
389
+ };
390
+ logInfo("swap.execute.ok", {
391
+ correlationId,
392
+ operationId: result.operationId,
393
+ txHash: result.txHash,
394
+ });
395
+ return result;
396
+ }
397
+ catch (e) {
398
+ const msg = e instanceof Error ? e.message : String(e);
399
+ const timedOut = /timed out/i.test(msg);
400
+ logError("swap.execute.fail", e, {
401
+ correlationId,
402
+ operationId: params.operationId,
403
+ timedOut,
404
+ warning: "Swap may still be in progress. Poll get_swap_status before retrying.",
405
+ });
406
+ if (timedOut) {
407
+ throw new Error(`${msg}. The swap may still be processing — call get_swap_status ` +
408
+ `with operationId=${params.operationId} before retrying. ` +
409
+ `[correlationId=${correlationId}]`);
410
+ }
411
+ throw e;
412
+ }
413
+ }
414
+ export async function getSwapStatus(token, operationId, opts = {}) {
415
+ const correlationId = opts.correlationId ?? newCorrelationId("swp-st");
416
+ const json = await swpJson(`/swp/exchange/status/${encodeURIComponent(operationId)}`, {
417
+ method: "GET",
418
+ headers: authHeaders(token),
419
+ }, { correlationId, label: "swap.status", retry: true });
420
+ assertNoError(json, "get_swap_status");
421
+ return slimSwapPayload(json);
422
+ }
423
+ /** SWP allows up to 200; keep under the cap while limiting round-trips. */
424
+ const RESOLVE_PAGE_SIZE = 100;
425
+ /** Hard stop so a broken `total` cannot loop forever (~5k assets). */
426
+ const RESOLVE_MAX_PAGES = 50;
427
+ function matchAsset(items, ref) {
428
+ const wantNet = ref.network.toLowerCase();
429
+ const wantSym = ref.symbol.toLowerCase();
430
+ const wantAddr = (ref.address ?? "").toLowerCase();
431
+ return items.find((a) => a.network === wantNet &&
432
+ a.symbol.toLowerCase() === wantSym &&
433
+ (a.address ?? "").toLowerCase() === wantAddr);
434
+ }
435
+ /**
436
+ * Same network + symbol, different contracts — used for a clear error when the
437
+ * caller omitted `address` and the catalog is ambiguous.
438
+ */
439
+ function sameSymbolOnNetwork(items, ref) {
440
+ const wantNet = ref.network.toLowerCase();
441
+ const wantSym = ref.symbol.toLowerCase();
442
+ return items.filter((a) => a.network === wantNet && a.symbol.toLowerCase() === wantSym);
443
+ }
444
+ async function findAssetAcrossPages(fetchPage, ref) {
445
+ let pageNum = 1;
446
+ let total = Number.POSITIVE_INFINITY;
447
+ let scanned = 0;
448
+ const symbolHits = [];
449
+ const wantAddr = (ref.address ?? "").toLowerCase();
450
+ while (pageNum <= RESOLVE_MAX_PAGES && scanned < total) {
451
+ const page = await fetchPage(pageNum, RESOLVE_PAGE_SIZE);
452
+ total = Number.isFinite(page.total) ? page.total : scanned + page.items.length;
453
+ scanned += page.items.length;
454
+ const hit = matchAsset(page.items, ref);
455
+ if (hit)
456
+ return { match: hit, scanned };
457
+ // Track collisions only when caller did not pin a contract address.
458
+ if (!wantAddr) {
459
+ for (const a of sameSymbolOnNetwork(page.items, ref)) {
460
+ const key = (a.address ?? "").toLowerCase();
461
+ if (!symbolHits.some((x) => (x.address ?? "").toLowerCase() === key)) {
462
+ symbolHits.push(a);
463
+ }
464
+ }
465
+ }
466
+ if (page.items.length === 0)
467
+ break;
468
+ if (page.items.length < page.pageSize)
469
+ break;
470
+ if (pageNum * page.pageSize >= total)
471
+ break;
472
+ pageNum += 1;
473
+ }
474
+ if (!wantAddr && symbolHits.length > 1) {
475
+ // Prefer native (null address) when Max/agent omitted address.
476
+ const native = symbolHits.find((a) => !a.address);
477
+ if (native)
478
+ return { match: native, scanned };
479
+ return { scanned, ambiguous: symbolHits };
480
+ }
481
+ if (!wantAddr && symbolHits.length === 1) {
482
+ return { match: symbolHits[0], scanned };
483
+ }
484
+ return { scanned };
485
+ }
486
+ /**
487
+ * Resolve decimals for a short asset ref via SWP catalog.
488
+ * Sell assets: `/assets/from`. Buy assets: `/assets/to` (needs `from` context).
489
+ *
490
+ * Walks catalog pages (not just page 1). Prefer passing `decimals` from
491
+ * `list_swap_assets` to skip lookup entirely.
492
+ */
493
+ export async function resolveSwapAsset(token, ref, opts = {}) {
494
+ if (ref.decimals !== undefined && Number.isFinite(ref.decimals)) {
495
+ return {
496
+ network: ref.network.toLowerCase(),
497
+ symbol: ref.symbol,
498
+ address: ref.address ?? null,
499
+ decimals: ref.decimals,
500
+ };
501
+ }
502
+ const networkFilter = [ref.network];
503
+ const fetchWithSearch = (page, pageSize) => opts.fromForToLookup
504
+ ? listSwapAssetsTo(token, {
505
+ from: opts.fromForToLookup,
506
+ search: ref.symbol,
507
+ networks: networkFilter,
508
+ page,
509
+ pageSize,
510
+ }, opts)
511
+ : listSwapAssetsFrom(token, {
512
+ search: ref.symbol,
513
+ networks: networkFilter,
514
+ page,
515
+ pageSize,
516
+ }, opts);
517
+ // 1) Symbol search + paginate (usual path).
518
+ let result = await findAssetAcrossPages(fetchWithSearch, ref);
519
+ // 2) Fallback: no findBySymbol — only network filter. Needed when search
520
+ // ranking buries an exact symbol/address past RESOLVE_MAX_PAGES, or when
521
+ // findBySymbol is too narrow. Still capped.
522
+ if (!result.match && !result.ambiguous && ref.address) {
523
+ const fetchNoSearch = (page, pageSize) => opts.fromForToLookup
524
+ ? listSwapAssetsTo(token, {
525
+ from: opts.fromForToLookup,
526
+ networks: networkFilter,
527
+ page,
528
+ pageSize,
529
+ }, opts)
530
+ : listSwapAssetsFrom(token, {
531
+ networks: networkFilter,
532
+ page,
533
+ pageSize,
534
+ }, opts);
535
+ result = await findAssetAcrossPages(fetchNoSearch, ref);
536
+ }
537
+ if (result.match)
538
+ return result.match;
539
+ if (result.ambiguous?.length) {
540
+ const sample = result.ambiguous
541
+ .slice(0, 5)
542
+ .map((a) => a.address ?? "native")
543
+ .join(", ");
544
+ throw new Error(`Asset ${ref.symbol} on ${ref.network} is ambiguous (${result.ambiguous.length} contracts). ` +
545
+ `Pass address (and preferably decimals) from list_swap_assets. Examples: ${sample}`);
546
+ }
547
+ throw new Error(`Asset ${ref.symbol} on ${ref.network}` +
548
+ (ref.address ? ` (${ref.address})` : " (native)") +
549
+ " not found in SWP catalog after paging. Pass decimals from list_swap_assets, or ensure the pair is valid.");
550
+ }
551
+ export function assetKey(a) {
552
+ return `${a.network.toLowerCase()}|${a.symbol.toLowerCase()}|${(a.address ?? "").toLowerCase()}`;
553
+ }
554
+ //# sourceMappingURL=swap.js.map