@gala-chain/gswap-sdk 1.1.0 → 1.2.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -31,8 +31,66 @@ bignumber_js = __toESM(bignumber_js, 1);
31
31
  let decimal_js = require("decimal.js");
32
32
  let debug = require("debug");
33
33
  debug = __toESM(debug, 1);
34
- let _gala_chain_api = require("@gala-chain/api");
34
+ let _noble_curves_secp256k1 = require("@noble/curves/secp256k1");
35
+ let _noble_hashes_sha3 = require("@noble/hashes/sha3");
36
+ let _noble_hashes_utils = require("@noble/hashes/utils");
35
37
 
38
+ //#region src/utils/confirmation_schedule.ts
39
+ const DEFAULT_TIMEOUT_MS = 6e4;
40
+ const DEFAULT_POLL_INTERVAL_MS = 2e3;
41
+ const MIN_POLL_INTERVAL_MS = 500;
42
+ const MAX_BACKOFF_MS = 8e3;
43
+ /**
44
+ * Builds the schedule for one `confirm()` call. `abortError` creates the error thrown when
45
+ * `options.signal` aborts, so each confirmation kind keeps its own error type and details.
46
+ */
47
+ function createConfirmationSchedule(options, defaultInitialDelayMs, abortError) {
48
+ const timeoutMs = Math.max(0, (options === null || options === void 0 ? void 0 : options.timeoutMs) ?? DEFAULT_TIMEOUT_MS);
49
+ const pollIntervalMs = Math.max(MIN_POLL_INTERVAL_MS, (options === null || options === void 0 ? void 0 : options.pollIntervalMs) ?? DEFAULT_POLL_INTERVAL_MS);
50
+ const initialDelayMs = Math.max(0, (options === null || options === void 0 ? void 0 : options.initialDelayMs) ?? defaultInitialDelayMs);
51
+ const signal = options === null || options === void 0 ? void 0 : options.signal;
52
+ const deadline = Date.now() + timeoutMs;
53
+ let firstRead = true;
54
+ let attempt = 0;
55
+ return { async waitForRead(retryAfterMs) {
56
+ throwIfAborted(signal, abortError);
57
+ if (firstRead) {
58
+ firstRead = false;
59
+ const remaining = Math.max(0, deadline - Date.now());
60
+ await delay(Math.min(initialDelayMs, remaining), signal, abortError);
61
+ return true;
62
+ }
63
+ const backoff = Math.min(MAX_BACKOFF_MS, pollIntervalMs * 2 ** Math.min(attempt, 5));
64
+ attempt += 1;
65
+ const waitMs = retryAfterMs === void 0 ? jitter(backoff) : Math.max(0, retryAfterMs);
66
+ if (Date.now() + waitMs > deadline) return false;
67
+ await delay(waitMs, signal, abortError);
68
+ return true;
69
+ } };
70
+ }
71
+ /** Throws `abortError(signal.reason)` when the signal has aborted. */
72
+ function throwIfAborted(signal, abortError) {
73
+ if ((signal === null || signal === void 0 ? void 0 : signal.aborted) === true) throw abortError(signal.reason);
74
+ }
75
+ function delay(milliseconds, signal, abortError) {
76
+ if ((signal === null || signal === void 0 ? void 0 : signal.aborted) === true) return Promise.reject(abortError(signal.reason));
77
+ return new Promise((resolve, reject) => {
78
+ const onAbort = () => {
79
+ clearTimeout(timer);
80
+ reject(abortError(signal === null || signal === void 0 ? void 0 : signal.reason));
81
+ };
82
+ const timer = setTimeout(() => {
83
+ signal === null || signal === void 0 || signal.removeEventListener("abort", onAbort);
84
+ resolve();
85
+ }, milliseconds);
86
+ signal === null || signal === void 0 || signal.addEventListener("abort", onAbort, { once: true });
87
+ });
88
+ }
89
+ function jitter(milliseconds) {
90
+ return Math.max(0, Math.round(milliseconds * (.9 + Math.random() * .2)));
91
+ }
92
+
93
+ //#endregion
36
94
  //#region src/classes/gswap_sdk_error.ts
37
95
  /** Error raised by the SDK for validation, gateway, chain, or transaction failures. */
38
96
  var GSwapSDKError = class GSwapSDKError extends Error {
@@ -40,6 +98,19 @@ var GSwapSDKError = class GSwapSDKError extends Error {
40
98
  details;
41
99
  chainMessage;
42
100
  retryAfterMs;
101
+ /** The HTTP status of the response that caused this error, when there was one. */
102
+ status;
103
+ /**
104
+ * The response body parsed as JSON, when the failing response had one; `undefined` for a body
105
+ * that was not JSON (see {@link bodyText}). Never part of {@link message}.
106
+ */
107
+ body;
108
+ /**
109
+ * The raw text of a failing response whose body was NOT JSON (a proxy's HTML 502, a WAF page,
110
+ * an empty body). It is kept here for diagnostics and is never copied into {@link message} or
111
+ * {@link userMessage}, so it cannot reach a user interface by accident.
112
+ */
113
+ bodyText;
43
114
  constructor(message, code, details, chainMessage, retryAfterMs) {
44
115
  super(message);
45
116
  this.code = code;
@@ -47,6 +118,32 @@ var GSwapSDKError = class GSwapSDKError extends Error {
47
118
  this.name = "GSwapSDKError";
48
119
  this.chainMessage = chainMessage;
49
120
  this.retryAfterMs = retryAfterMs;
121
+ const status = details === null || details === void 0 ? void 0 : details["status"];
122
+ this.status = typeof status === "number" ? status : void 0;
123
+ const body = details === null || details === void 0 ? void 0 : details["body"];
124
+ this.body = typeof body === "string" ? void 0 : body;
125
+ this.bodyText = typeof body === "string" ? body : void 0;
126
+ }
127
+ /**
128
+ * A message that is safe to show a user as is: the server's own message when it sent one
129
+ * (a gateway or chain rejection), a neutral line naming the status for an HTTP failure with no
130
+ * readable answer, and otherwise {@link message}. Apps that word these differently (a
131
+ * "network busy" line for a 502, say) branch on {@link code} and {@link status} instead.
132
+ */
133
+ get userMessage() {
134
+ if (this.chainMessage !== void 0 && this.chainMessage !== "") return this.chainMessage;
135
+ if (this.code === "HTTP_ERROR" && this.status !== void 0) return `The server answered with an unexpected response (HTTP ${this.status}).`;
136
+ return this.message;
137
+ }
138
+ /**
139
+ * True when the failure means "that market, pool or position does not exist" rather than
140
+ * "something went wrong": an HTTP 404, a pair with no pool at any fee tier
141
+ * (`NO_POOL_AVAILABLE`), or a token the DEX contract has no trading symbol for
142
+ * (`UNKNOWN_TOKEN`). A pool that exists but is too thin (`INSUFFICIENT_LIQUIDITY`) is NOT
143
+ * not-found.
144
+ */
145
+ get notFound() {
146
+ return this.status === 404 || this.code === "NO_POOL_AVAILABLE" || this.code === "UNKNOWN_TOKEN";
50
147
  }
51
148
  /** Create the standard missing-signer error. */
52
149
  static noSignerError() {
@@ -56,21 +153,38 @@ var GSwapSDKError = class GSwapSDKError extends Error {
56
153
  static noWalletAddressError() {
57
154
  return new GSwapSDKError("Cannot confirm this write: no walletAddress was provided to the GSwap constructor, so the resulting position cannot be looked up to confirm it landed on chain.", "NO_WALLET_ADDRESS");
58
155
  }
59
- /** Create the error used when no pool can quote the requested pair. */
60
- static noPoolAvailableError(tokenIn, tokenOut, fee) {
156
+ /**
157
+ * Create the error used when no pool can quote the requested pair. `source` is the backend
158
+ * answer that said so; its status, body and message are kept (the message as `chainMessage`,
159
+ * so {@link userMessage} is the server's own wording).
160
+ */
161
+ static noPoolAvailableError(tokenIn, tokenOut, fee, source) {
61
162
  return new GSwapSDKError("No pool is available for the requested pair.", "NO_POOL_AVAILABLE", {
62
163
  tokenIn,
63
164
  tokenOut,
64
- ...fee === void 0 ? {} : { fee }
65
- });
165
+ ...fee === void 0 ? {} : { fee },
166
+ ...sourceDetails(source)
167
+ }, source === null || source === void 0 ? void 0 : source.serverMessage);
66
168
  }
67
- /** Create the error used when a pool lacks liquidity for a quote. */
68
- static insufficientLiquidityError(tokenIn, tokenOut, fee) {
169
+ /** Create the error used when a pool lacks liquidity for a quote. `source` as for {@link noPoolAvailableError}. */
170
+ static insufficientLiquidityError(tokenIn, tokenOut, fee, source) {
69
171
  return new GSwapSDKError("The selected pool has insufficient liquidity for the requested quote.", "INSUFFICIENT_LIQUIDITY", {
70
172
  tokenIn,
71
173
  tokenOut,
72
- ...fee === void 0 ? {} : { fee }
73
- });
174
+ ...fee === void 0 ? {} : { fee },
175
+ ...sourceDetails(source)
176
+ }, source === null || source === void 0 ? void 0 : source.serverMessage);
177
+ }
178
+ /**
179
+ * The message the server put in a JSON answer (`message`, or `error.Message`), when this error
180
+ * came from an HTTP response that had one.
181
+ */
182
+ get serverMessage() {
183
+ var _this$details;
184
+ const direct = (_this$details = this.details) === null || _this$details === void 0 ? void 0 : _this$details["message"];
185
+ if (typeof direct === "string") return direct;
186
+ const body = this.body;
187
+ return getStringProperty(body, "message") ?? getStringProperty(body, "Message") ?? getStringProperty(getObjectProperty(body, "error"), "Message");
74
188
  }
75
189
  /** Convert a chain-gateway bounce into a typed SDK error. */
76
190
  static fromGatewayBounce(code, message, status, retryAfterMs) {
@@ -123,6 +237,15 @@ var GSwapSDKError = class GSwapSDKError extends Error {
123
237
  });
124
238
  }
125
239
  };
240
+ /** The HTTP facts of the error a classified error was derived from. */
241
+ function sourceDetails(source) {
242
+ if (source === void 0) return {};
243
+ return {
244
+ ...source.status === void 0 ? {} : { status: source.status },
245
+ ...source.body === void 0 ? {} : { body: source.body },
246
+ ...source.bodyText === void 0 ? {} : { body: source.bodyText }
247
+ };
248
+ }
126
249
  /** Narrow an object property without trusting a cast at a network boundary. */
127
250
  function getObjectProperty(value, property) {
128
251
  if (typeof value !== "object" || value === null || !(property in value)) return void 0;
@@ -144,6 +267,280 @@ function parseJson(text) {
144
267
  }
145
268
  }
146
269
 
270
+ //#endregion
271
+ //#region src/utils/transport.ts
272
+ /**
273
+ * Arms the abort timer for a request. `Infinity` means "never time out": `setTimeout` would turn
274
+ * it into 1 ms, so no timer is armed at all. Returns a function that disarms it.
275
+ */
276
+ function armTimeout(controller, timeoutMs) {
277
+ if (timeoutMs === Infinity) return () => void 0;
278
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
279
+ return () => clearTimeout(timer);
280
+ }
281
+ /**
282
+ * Validates a request timeout in milliseconds: a positive finite number, or `Infinity` for none.
283
+ *
284
+ * @throws GSwapSDKError VALIDATION_ERROR otherwise.
285
+ */
286
+ function validateTimeoutMs(timeoutMs, name) {
287
+ if (timeoutMs === Infinity || Number.isFinite(timeoutMs) && timeoutMs > 0) return timeoutMs;
288
+ throw new GSwapSDKError(`${name} must be a positive number of milliseconds, or Infinity for no timeout.`, "VALIDATION_ERROR", { [name]: timeoutMs });
289
+ }
290
+ const READABLE_QUERY_SAFE = /[A-Za-z0-9\-._~!'()*$|:,]/u;
291
+ function encodeReadable(text) {
292
+ let out = "";
293
+ for (const char of text) if (char === " ") out += "+";
294
+ else if (READABLE_QUERY_SAFE.test(char)) out += char;
295
+ else out += encodeScalar(char);
296
+ return out;
297
+ }
298
+ /** Percent-encodes one code point; a lone surrogate becomes U+FFFD, as `URLSearchParams` does. */
299
+ function encodeScalar(char) {
300
+ try {
301
+ return encodeURIComponent(char);
302
+ } catch {
303
+ return "%EF%BF%BD";
304
+ }
305
+ }
306
+ /** Serializes GET parameters, in insertion order, in the encoding the wire options select. */
307
+ function buildQueryString(params, encoding = "standard") {
308
+ if (encoding === "readable") return Object.entries(params).map(([key, value]) => `${encodeReadable(key)}=${encodeReadable(value)}`).join("&");
309
+ return new URLSearchParams(params).toString();
310
+ }
311
+ async function requestWithTimeout(requestor, url, options, timeoutMs) {
312
+ const controller = new AbortController();
313
+ const disarm = armTimeout(controller, timeoutMs);
314
+ const externalSignal = options.signal;
315
+ const abortFromCaller = () => controller.abort();
316
+ externalSignal === null || externalSignal === void 0 || externalSignal.addEventListener("abort", abortFromCaller, { once: true });
317
+ try {
318
+ return await requestor(url, {
319
+ ...options,
320
+ signal: controller.signal
321
+ });
322
+ } catch (error) {
323
+ if (controller.signal.aborted) throw new GSwapSDKError(`Request timed out after ${timeoutMs} ms.`, "REQUEST_TIMEOUT", {
324
+ url,
325
+ timeoutMs,
326
+ cause: error
327
+ });
328
+ throw error;
329
+ } finally {
330
+ disarm();
331
+ externalSignal === null || externalSignal === void 0 || externalSignal.removeEventListener("abort", abortFromCaller);
332
+ }
333
+ }
334
+ async function readResponseBody(response) {
335
+ const text = await response.text();
336
+ return parseJson(text) ?? text;
337
+ }
338
+ /** Read a case-insensitive response header from either supported header shape. */
339
+ function readHeader(response, name) {
340
+ var _Object$entries$find;
341
+ const headers = response.headers;
342
+ if (headers === void 0) return void 0;
343
+ if (hasHeaderGetter(headers)) {
344
+ const value = headers.get(name);
345
+ return value === null || value === "" ? void 0 : value;
346
+ }
347
+ const value = (_Object$entries$find = Object.entries(headers).find(([key]) => key.toLowerCase() === name.toLowerCase())) === null || _Object$entries$find === void 0 ? void 0 : _Object$entries$find[1];
348
+ return value === void 0 || value === "" ? void 0 : value;
349
+ }
350
+ /** Read a Retry-After header (delta-seconds or an HTTP date) as non-negative milliseconds. */
351
+ function readRetryAfter(response) {
352
+ const raw = readHeader(response, "retry-after");
353
+ if (raw === void 0) return void 0;
354
+ const seconds = Number(raw);
355
+ if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1e3;
356
+ const timestamp = Date.parse(raw);
357
+ return Number.isNaN(timestamp) ? void 0 : Math.max(0, timestamp - Date.now());
358
+ }
359
+ /** Read a non-negative integer block number header. */
360
+ function readBlockNumberHeader(response, headerName = "x-block-number") {
361
+ const raw = readHeader(response, headerName);
362
+ if (raw === void 0) return void 0;
363
+ const blockNumber = Number(raw);
364
+ return Number.isInteger(blockNumber) && blockNumber >= 0 ? blockNumber : void 0;
365
+ }
366
+ function hasHeaderGetter(headers) {
367
+ return "get" in headers && typeof headers.get === "function";
368
+ }
369
+
370
+ //#endregion
371
+ //#region src/classes/apply_results.ts
372
+ /** The apply-results key constraint of the swap backend (`apply-results.dto.ts`). */
373
+ const UNIQUE_KEY_PATTERN$1 = /^[A-Za-z0-9._:-]{1,128}$/u;
374
+ /**
375
+ * The AutoApplier waits at least 2 s before applying and the apply lands ~6-8 s after the
376
+ * acknowledgement, so a read at t=0 is a guaranteed 404.
377
+ */
378
+ const DEFAULT_INITIAL_DELAY_MS = 3e3;
379
+ const DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
380
+ /**
381
+ * The outcome of a queued RequestTrade that could not be delivered as an {@link AppliedTrade}.
382
+ * `code` says why:
383
+ *
384
+ * - `CONFIRMATION_TIMEOUT`: no terminal result before the deadline. The trade may still apply;
385
+ * confirm the same `uniqueKey` again later and never resubmit.
386
+ * - `CONFIRMATION_ABORTED`: the caller's `signal` aborted. Nothing is known about the outcome.
387
+ * - `CONFIRMATION_FAILED`: the apply-results read itself failed (malformed key or body, a
388
+ * non-retryable HTTP status). The outcome is unknown.
389
+ * - anything else: the chain REJECTED the trade; `code` is the chain `ErrorKey` (or
390
+ * `CHAIN_ERROR`) and `chainMessage` is its message, which is also `message`.
391
+ *
392
+ * `details` always carries `uniqueKey` and, where known, `requestTransactionId`,
393
+ * `applyTransactionId`, `blockNumber`, `status`, `body`, `url` and the last transport `cause`.
394
+ */
395
+ var AppliedTradeError = class extends GSwapSDKError {
396
+ constructor(message, code, details = {}, chainMessage) {
397
+ super(message, code, details, chainMessage);
398
+ this.name = "AppliedTradeError";
399
+ }
400
+ };
401
+ /**
402
+ * Polls `GET /v1/chain/apply-results/{uniqueKey}?channel=asset&contract=dex-contract` until the
403
+ * queued RequestTrade reaches a terminal state.
404
+ *
405
+ * Pacing: wait `initialDelayMs` (default 3 s), then read; on 404, 429, 500, 502, 503, 504 or a
406
+ * transport error wait with jittered exponential backoff (`pollIntervalMs` doubling to 8 s,
407
+ * `Retry-After` honored as seconds or an HTTP date) until `timeoutMs`. Any other status is
408
+ * terminal. At least one read is always made.
409
+ *
410
+ * @returns The applied trade.
411
+ * @throws AppliedTradeError - see its documentation for every `code`.
412
+ * @example
413
+ * ```ts
414
+ * const applied = await confirmAppliedTrade(
415
+ * { dexBackendBaseUrl: 'https://dex-backend-prod1.defi.gala.com' },
416
+ * { uniqueKey, requestTransactionId: null },
417
+ * { initialDelayMs: 0, timeoutMs: 60_000, signal: controller.signal },
418
+ * );
419
+ * ```
420
+ */
421
+ async function confirmAppliedTrade(client, input, options) {
422
+ const { uniqueKey } = input;
423
+ if (!UNIQUE_KEY_PATTERN$1.test(uniqueKey)) throw new AppliedTradeError("uniqueKey must be 1-128 characters of A-Z, a-z, 0-9, \".\", \"_\", \":\" or \"-\".", "CONFIRMATION_FAILED", { uniqueKey });
424
+ const requestor = client.httpRequestor ?? fetch.bind(globalThis);
425
+ const requestTimeoutMs = client.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
426
+ const signal = options === null || options === void 0 ? void 0 : options.signal;
427
+ const abortError = (cause) => new AppliedTradeError("Transaction confirmation was aborted.", "CONFIRMATION_ABORTED", {
428
+ uniqueKey,
429
+ ...cause === void 0 ? {} : { cause }
430
+ });
431
+ const url = `${client.dexBackendBaseUrl.replace(/\/$/u, "")}/v1/chain/apply-results/${encodeURIComponent(uniqueKey)}?channel=asset&contract=dex-contract`;
432
+ const schedule = createConfirmationSchedule(options, DEFAULT_INITIAL_DELAY_MS, abortError);
433
+ await schedule.waitForRead();
434
+ let lastError;
435
+ while (true) {
436
+ throwIfAborted(signal, abortError);
437
+ let response;
438
+ let body;
439
+ try {
440
+ response = await requestWithTimeout(requestor, url, {
441
+ method: "GET",
442
+ ...signal === void 0 ? {} : { signal }
443
+ }, requestTimeoutMs);
444
+ body = await readResponseBody(response);
445
+ } catch (error) {
446
+ throwIfAborted(signal, abortError);
447
+ lastError = error;
448
+ if (!await schedule.waitForRead()) break;
449
+ continue;
450
+ }
451
+ throwIfAborted(signal, abortError);
452
+ if (response.status === 200) return readAppliedResult(input, response, body, url);
453
+ if (response.status === 404 || response.status === 429 || isTransient(response.status)) {
454
+ const retryAfter = response.status === 429 ? readRetryAfter(response) : void 0;
455
+ if (!await schedule.waitForRead(retryAfter)) break;
456
+ continue;
457
+ }
458
+ throw new AppliedTradeError(bodyMessage(body), "CONFIRMATION_FAILED", {
459
+ uniqueKey,
460
+ status: response.status,
461
+ body,
462
+ url
463
+ });
464
+ }
465
+ throw new AppliedTradeError("Transaction confirmation timed out; the trade may still apply. Confirm the same uniqueKey again later instead of resubmitting.", "CONFIRMATION_TIMEOUT", {
466
+ uniqueKey,
467
+ requestTransactionId: input.requestTransactionId,
468
+ url,
469
+ ...lastError === void 0 ? {} : { cause: lastError }
470
+ });
471
+ }
472
+ function readAppliedResult(input, response, body, url) {
473
+ const { uniqueKey } = input;
474
+ const record = asRecord$2(body);
475
+ const responseKey = (record === null || record === void 0 ? void 0 : record["uniqueKey"]) ?? (record === null || record === void 0 ? void 0 : record["UniqueKey"]);
476
+ if (typeof responseKey === "string" && responseKey !== uniqueKey) throw malformed(input, response, body, url, "Apply result key did not match the requested key.");
477
+ const status = record === null || record === void 0 ? void 0 : record["Status"];
478
+ const applyTransactionId = readHeader(response, "x-apply-transaction-id") ?? null;
479
+ const blockNumber = readBlockNumberHeader(response, "x-apply-block-number") ?? null;
480
+ if (status === 0) {
481
+ const errorKey = getStringProperty(record, "ErrorKey") ?? "CHAIN_ERROR";
482
+ const chainMessage = getStringProperty(record, "Message") ?? "The chain rejected this trade.";
483
+ throw new AppliedTradeError(chainMessage, errorKey, {
484
+ uniqueKey,
485
+ requestTransactionId: input.requestTransactionId,
486
+ applyTransactionId,
487
+ blockNumber,
488
+ status: response.status,
489
+ body,
490
+ url,
491
+ errorKey,
492
+ message: chainMessage
493
+ }, chainMessage);
494
+ }
495
+ if (status !== 1) throw malformed(input, response, body, url);
496
+ const result = parseTradeExecution(record === null || record === void 0 ? void 0 : record["Data"]);
497
+ if (result === void 0) throw malformed(input, response, body, url);
498
+ return {
499
+ uniqueKey,
500
+ transactionId: applyTransactionId,
501
+ blockNumber,
502
+ requestTransactionId: input.requestTransactionId,
503
+ result
504
+ };
505
+ }
506
+ function malformed(input, response, body, url, message = "Transaction confirmation returned a malformed body") {
507
+ return new AppliedTradeError(message, "CONFIRMATION_FAILED", {
508
+ uniqueKey: input.uniqueKey,
509
+ status: response.status,
510
+ body,
511
+ url
512
+ });
513
+ }
514
+ function parseTradeExecution(value) {
515
+ const record = asRecord$2(value);
516
+ if (record === void 0 || typeof record["token0"] !== "string" || typeof record["token1"] !== "string" || typeof record["fee"] !== "number") return;
517
+ const optionalFields = [
518
+ "sell0Qty",
519
+ "sell1Qty",
520
+ "buy0Qty",
521
+ "buy1Qty",
522
+ "tradingFees",
523
+ "protocolFees",
524
+ "totalFees"
525
+ ];
526
+ for (const field of optionalFields) if (field in record && typeof record[field] !== "string") return void 0;
527
+ return {
528
+ token0: record["token0"],
529
+ token1: record["token1"],
530
+ fee: record["fee"],
531
+ ...Object.fromEntries(optionalFields.flatMap((field) => typeof record[field] === "string" ? [[field, record[field]]] : []))
532
+ };
533
+ }
534
+ function isTransient(status) {
535
+ return status === 500 || status === 502 || status === 503 || status === 504;
536
+ }
537
+ function bodyMessage(body) {
538
+ return getStringProperty(body, "message") ?? getStringProperty(body, "Message") ?? getStringProperty(getObjectProperty(body, "error"), "message") ?? getStringProperty(getObjectProperty(body, "error"), "Message") ?? "Transaction confirmation failed";
539
+ }
540
+ function asRecord$2(value) {
541
+ return typeof value === "object" && value !== null ? value : void 0;
542
+ }
543
+
147
544
  //#endregion
148
545
  //#region src/types/fees.ts
149
546
  /**
@@ -236,6 +633,33 @@ function assertTickRange(lower, upper, fee) {
236
633
  function assertSpacing(spacing) {
237
634
  if (!Number.isInteger(spacing) || spacing <= 0) throw new RangeError("Tick spacing must be positive");
238
635
  }
636
+ /**
637
+ * Puts a `slot0` answer into the frame of the request. The backend always answers in the pool's
638
+ * canonical order and says so with `flippedFromRequest`; when the caller asked for the reverse
639
+ * order the price is inverted here (at 60 significant digits, so a very large or very small
640
+ * reciprocal keeps its precision) and the tick is re-derived from the inverted price with the
641
+ * contract's greatest-tick rule. Negating the tick instead is wrong for a price between ticks
642
+ * (and `-t - 1` is wrong for a price exactly on a tick: sqrtPrice 1 / tick 0 stays 1 / 0).
643
+ *
644
+ * @example
645
+ * ```ts
646
+ * const slot0 = await gSwap.pools.getSlot0('A', 'B', 500);
647
+ * const { sqrtPrice, tick } = frameSlot0(slot0);
648
+ * ```
649
+ */
650
+ function frameSlot0(slot0) {
651
+ if (slot0.flippedFromRequest !== true) return {
652
+ sqrtPrice: slot0.sqrtPrice,
653
+ tick: slot0.tick
654
+ };
655
+ const inverted = new DexDecimal(1).dividedBy(new DexDecimal(slot0.sqrtPrice));
656
+ if (!inverted.isFinite() || inverted.lessThanOrEqualTo(0)) throw new GSwapSDKError("Cannot invert a non-positive sqrt price.", "VALIDATION_ERROR", { sqrtPrice: slot0.sqrtPrice });
657
+ const sqrtPrice = inverted.toString();
658
+ return {
659
+ sqrtPrice,
660
+ tick: sqrtPriceToTick(sqrtPrice)
661
+ };
662
+ }
239
663
 
240
664
  //#endregion
241
665
  //#region src/utils/validation.ts
@@ -351,6 +775,36 @@ function validateWalletAddress(address) {
351
775
  value: address
352
776
  });
353
777
  }
778
+ /** Words the chain gateway refuses as a trading symbol. */
779
+ const RESERVED_TRADING_SYMBOLS = /* @__PURE__ */ new Set([
780
+ "Token",
781
+ "Unit",
782
+ "none"
783
+ ]);
784
+ const TRADING_SYMBOL_PATTERN = /^[A-Za-z0-9]+$/u;
785
+ const MAX_TRADING_SYMBOL_LENGTH = 32;
786
+ /**
787
+ * True when `symbol` is acceptable to propose into the trading-symbol registry: ASCII letters and
788
+ * digits only, 1 to 32 characters, and not a reserved word (`Token`, `Unit`, `none`) — the rules
789
+ * the chain gateway enforces on a CreatePool symbol.
790
+ */
791
+ function isProposableTradingSymbol(symbol) {
792
+ return TRADING_SYMBOL_PATTERN.test(symbol) && symbol.length <= MAX_TRADING_SYMBOL_LENGTH && !RESERVED_TRADING_SYMBOLS.has(symbol);
793
+ }
794
+ /**
795
+ * Asserts that a pair is in the contract's canonical order (`token0 < token1` by plain string
796
+ * comparison), which every write DTO requires. A pair a caller took from a stale quote or
797
+ * position can be reversed; signing it would sign something other than what was priced.
798
+ *
799
+ * @throws GSwapSDKError VALIDATION_ERROR (`details.type` = `NON_CANONICAL_PAIR`) otherwise.
800
+ */
801
+ function assertCanonicalPair(token0, token1) {
802
+ if (!(token0 < token1)) throw new GSwapSDKError(`The pair ${token0}/${token1} is not in canonical order: token0 must sort before token1.`, "VALIDATION_ERROR", {
803
+ type: "NON_CANONICAL_PAIR",
804
+ token0,
805
+ token1
806
+ });
807
+ }
354
808
 
355
809
  //#endregion
356
810
  //#region src/classes/assets.ts
@@ -411,62 +865,139 @@ function isRawUserAsset(value) {
411
865
  }
412
866
 
413
867
  //#endregion
414
- //#region src/utils/transport.ts
415
- async function requestWithTimeout(requestor, url, options, timeoutMs) {
416
- const controller = new AbortController();
417
- const timer = setTimeout(() => controller.abort(), timeoutMs);
418
- const externalSignal = options.signal;
419
- const abortFromCaller = () => controller.abort();
420
- externalSignal === null || externalSignal === void 0 || externalSignal.addEventListener("abort", abortFromCaller, { once: true });
421
- try {
422
- return await requestor(url, {
423
- ...options,
424
- signal: controller.signal
425
- });
426
- } catch (error) {
427
- if (controller.signal.aborted) throw new GSwapSDKError(`Request timed out after ${timeoutMs} ms.`, "REQUEST_TIMEOUT", {
428
- url,
429
- timeoutMs,
430
- cause: error
431
- });
432
- throw error;
433
- } finally {
434
- clearTimeout(timer);
435
- externalSignal === null || externalSignal === void 0 || externalSignal.removeEventListener("abort", abortFromCaller);
436
- }
437
- }
438
- async function readResponseBody(response) {
439
- const text = await response.text();
440
- return parseJson(text) ?? text;
868
+ //#region src/classes/dex_fees.ts
869
+ /** DEX methods the backend accepts a fee quote for (its allow-list enumerates the same six). */
870
+ const DEX_FEE_METHODS = [
871
+ "Swap",
872
+ "RequestTrade",
873
+ "AddLiquidity",
874
+ "RemoveLiquidity",
875
+ "CollectPositionFees",
876
+ "CreatePool"
877
+ ];
878
+ /** The chain method name the signed DryRun wrapper is signed under. */
879
+ const DRY_RUN_METHOD = "DryRun";
880
+ /** `uniqueKey` prefix of the DryRun WRAPPER (distinct from the inner DTO's key). */
881
+ const DRY_RUN_UNIQUE_KEY_PREFIX = "galaconnect-operation-";
882
+ /** True when `method` is a DEX method a fee can be quoted for. */
883
+ function isDexFeeMethod(method) {
884
+ return typeof method === "string" && DEX_FEE_METHODS.includes(method);
441
885
  }
442
- /** Read a case-insensitive response header from either supported header shape. */
443
- function readHeader(response, name) {
444
- var _Object$entries$find;
445
- const headers = response.headers;
446
- if (headers === void 0) return void 0;
447
- if (hasHeaderGetter(headers)) {
448
- const value = headers.get(name);
449
- return value === null || value === "" ? void 0 : value;
450
- }
451
- const value = (_Object$entries$find = Object.entries(headers).find(([key]) => key.toLowerCase() === name.toLowerCase())) === null || _Object$entries$find === void 0 ? void 0 : _Object$entries$find[1];
452
- return value === void 0 || value === "" ? void 0 : value;
886
+ /** Current-contract typed DEX messages use trading symbols instead of TokenClassKey objects. */
887
+ function isCurrentContractDexMessage(message, method) {
888
+ return isDexFeeMethod(method) && typeof (message === null || message === void 0 ? void 0 : message["token0"]) === "string" && typeof message["token1"] === "string";
453
889
  }
454
- /** Read a non-negative Retry-After header as milliseconds. */
455
- function readRetryAfter(response) {
456
- const raw = readHeader(response, "retry-after");
457
- if (raw === void 0) return void 0;
458
- const seconds = Number(raw);
459
- return Number.isFinite(seconds) && seconds >= 0 ? seconds * 1e3 : void 0;
890
+ /**
891
+ * Reduces an EIP-712 approval `message` to the DTO the chain actually receives: drops the signing
892
+ * envelope (`signature`, `prefix`, `types`, `domain`) and transport metadata (`trace`).
893
+ */
894
+ function toChainDto(message) {
895
+ const chainDto = { ...message };
896
+ for (const field of [
897
+ "signature",
898
+ "prefix",
899
+ "types",
900
+ "domain",
901
+ "trace"
902
+ ]) delete chainDto[field];
903
+ return chainDto;
460
904
  }
461
- /** Read a non-negative integer block number header. */
462
- function readBlockNumberHeader(response, headerName = "x-block-number") {
463
- const raw = readHeader(response, headerName);
464
- if (raw === void 0) return void 0;
465
- const blockNumber = Number(raw);
466
- return Number.isInteger(blockNumber) && blockNumber >= 0 ? blockNumber : void 0;
905
+ /**
906
+ * Signs the DryRun WRAPPER the gateway accepts for every contract:
907
+ * `{ callerPublicKey, method, dto: <inner DTO object>, uniqueKey, signature }`. The inner DTO
908
+ * travels unsigned and as an object; no `signerPublicKey` is sent because the signature already
909
+ * recovers it (the gateway rejects it as REDUNDANT_SIGNER_PUBLIC_KEY). The wrapper carries its own
910
+ * `uniqueKey`, distinct from the DTO's.
911
+ */
912
+ function buildDryRunWrapper(signer, callerPublicKey, method, dto, uniqueKeyPrefix = DRY_RUN_UNIQUE_KEY_PREFIX) {
913
+ return signer.signObject(DRY_RUN_METHOD, {
914
+ callerPublicKey,
915
+ method,
916
+ dto,
917
+ uniqueKey: `${uniqueKeyPrefix}${globalThis.crypto.randomUUID()}`
918
+ });
467
919
  }
468
- function hasHeaderGetter(headers) {
469
- return "get" in headers && typeof headers.get === "function";
920
+ /**
921
+ * Network-fee quote for GalaChain DEX methods, derived from a server-side DryRun.
922
+ *
923
+ * The client only SIGNS the DryRun wrapper (with whatever {@link GalaChainSigner} it is given) and
924
+ * the backend forwards it to the gateway, where the simulated writes carry the fee; the backend
925
+ * never holds a key. Intended for wallets: the DTO quoted is the typed message a dApp handed over.
926
+ *
927
+ * @example
928
+ * ```ts
929
+ * const fees = new DexFees({ dexBackendBaseUrl }, new PrivateKeySigner(sessionKey));
930
+ * const quote = await fees.quote(publicKey, 'RequestTrade', toChainDto(message), {
931
+ * contract: 'dex-contract',
932
+ * });
933
+ * const feeInGala = quote?.fees[0]?.feeInGala;
934
+ * ```
935
+ */
936
+ var DexFees = class {
937
+ signer;
938
+ requestor;
939
+ baseUrl;
940
+ requestTimeoutMs;
941
+ constructor(client, signer) {
942
+ this.signer = signer;
943
+ this.requestor = client.httpRequestor ?? fetch.bind(globalThis);
944
+ this.baseUrl = client.dexBackendBaseUrl.replace(/\/$/u, "");
945
+ this.requestTimeoutMs = client.requestTimeoutMs ?? 3e4;
946
+ }
947
+ /**
948
+ * Quotes the network fee for `method` applied to `message`. The message is reduced with
949
+ * {@link toChainDto} first, so an EIP-712 approval message can be passed as is.
950
+ *
951
+ * Resolves `null` when the BACKEND could not be reached or answered unusably (offline, 4xx/5xx,
952
+ * malformed body): a transport outcome, not a fee. Deciding what to charge when the chain is
953
+ * silent is the backend's job (it serves `source: 'default'`), so the client never substitutes
954
+ * a fee of its own, with ONE exception that mirrors the wallet: a `dex-contract` quote answered
955
+ * 403 or 404 means the contract has no fee route yet and resolves `{ source: 'default', '0' }`.
956
+ * Never throws on transport, so a fee lookup cannot block or break a signing screen.
957
+ *
958
+ * @throws GSwapSDKError NO_SIGNER when constructed without a signer; any error the signer
959
+ * throws (for example a locked wallet) propagates unchanged.
960
+ */
961
+ async quote(callerPublicKey, method, message, options = {}) {
962
+ if (this.signer === void 0) throw GSwapSDKError.noSignerError();
963
+ const channel = options.channel ?? "asset";
964
+ const contract = options.contract ?? "dexv3-contract";
965
+ const wrapper = await buildDryRunWrapper(this.signer, callerPublicKey, method, toChainDto(message));
966
+ const url = `${this.baseUrl}/wallet/connect/galachain/api/${encodeURIComponent(channel)}/${encodeURIComponent(contract)}/${encodeURIComponent(method)}/fee`;
967
+ let status;
968
+ let ok;
969
+ let body;
970
+ try {
971
+ const response = await requestWithTimeout(this.requestor, url, {
972
+ method: "POST",
973
+ headers: {
974
+ "Content-Type": "application/json",
975
+ Accept: "application/json"
976
+ },
977
+ body: JSON.stringify(wrapper)
978
+ }, this.requestTimeoutMs);
979
+ status = response.status;
980
+ ok = response.ok;
981
+ body = ok ? await readResponseBody(response) : void 0;
982
+ } catch {
983
+ return null;
984
+ }
985
+ if (!ok) {
986
+ if (contract === "dex-contract" && (status === 403 || status === 404)) return {
987
+ source: "default",
988
+ fees: [{ feeInGala: "0" }]
989
+ };
990
+ return null;
991
+ }
992
+ return readQuote(body);
993
+ }
994
+ };
995
+ function readQuote(body) {
996
+ var _fees$;
997
+ if (typeof body !== "object" || body === null) return null;
998
+ const fees = body.fees;
999
+ if (!Array.isArray(fees) || typeof ((_fees$ = fees[0]) === null || _fees$ === void 0 ? void 0 : _fees$.feeInGala) !== "string") return null;
1000
+ return body;
470
1001
  }
471
1002
 
472
1003
  //#endregion
@@ -480,6 +1011,12 @@ var SubmittedTransaction = class {
480
1011
  mode;
481
1012
  /** The synchronous gateway result, or `undefined` when `mode === 'queued'`. */
482
1013
  result;
1014
+ /**
1015
+ * The gateway's whole JSON answer as it came (`{ status, message, error, data }`), for an app
1016
+ * that hands the backend's own envelope to its callers; `undefined` for a transaction built from
1017
+ * a uniqueKey alone.
1018
+ */
1019
+ envelope;
483
1020
  backendBaseUrl;
484
1021
  requestor;
485
1022
  requestTimeoutMs;
@@ -502,6 +1039,7 @@ var SubmittedTransaction = class {
502
1039
  this.blockNumber = options.blockNumber ?? null;
503
1040
  this.mode = options.mode;
504
1041
  this.result = options.result;
1042
+ this.envelope = options.envelope;
505
1043
  this.backendBaseUrl = options.dexBackendBaseUrl.replace(/\/$/u, "");
506
1044
  this.requestor = options.httpRequestor;
507
1045
  this.requestTimeoutMs = options.requestTimeoutMs ?? 3e4;
@@ -521,63 +1059,15 @@ var SubmittedTransaction = class {
521
1059
  if (this.method === "RequestTrade") return this.confirmApplied(options);
522
1060
  return this.confirmPosition(options);
523
1061
  }
524
- async confirmApplied(options) {
525
- const schedule = createConfirmationSchedule(options, 3e3);
526
- await schedule.waitForRead();
527
- const url = `${this.backendBaseUrl}/v1/chain/apply-results/${encodeURIComponent(this.uniqueKey)}?channel=asset&contract=dex-contract`;
528
- let lastError;
529
- while (true) {
530
- let response;
531
- let body;
532
- try {
533
- response = await requestWithTimeout(this.requestor, url, { method: "GET" }, this.requestTimeoutMs);
534
- body = await readResponseBody(response);
535
- } catch (error) {
536
- lastError = error;
537
- if (!await schedule.waitForRead()) break;
538
- continue;
539
- }
540
- if (response.status === 200) return this.readAppliedResult(response, body, url);
541
- if (response.status === 404 || response.status === 429 || isTransient(response.status)) {
542
- if (response.status === 429) {
543
- const retryAfter = readRetryAfter(response);
544
- if (retryAfter !== void 0) {
545
- if (!await schedule.waitForRead(retryAfter)) break;
546
- continue;
547
- }
548
- }
549
- } else throw new GSwapSDKError(bodyMessage(body), "CONFIRMATION_FAILED", {
550
- status: response.status,
551
- body,
552
- url
553
- });
554
- if (!await schedule.waitForRead()) break;
555
- }
556
- throw GSwapSDKError.confirmationTimeoutError(this.uniqueKey, lastError);
557
- }
558
- readAppliedResult(response, body, url) {
559
- const record = asRecord$1(body);
560
- const status = record === null || record === void 0 ? void 0 : record["Status"];
561
- const applyTransactionId = readHeader(response, "x-apply-transaction-id") ?? null;
562
- const blockNumber = readBlockNumberHeader(response, "x-apply-block-number") ?? null;
563
- if (status === 0) throw GSwapSDKError.fromChainError(getStringProperty(record, "ErrorKey") ?? "CHAIN_ERROR", getStringProperty(record, "Message") ?? "The chain rejected this trade.", {
564
- uniqueKey: this.uniqueKey,
565
- applyTransactionId,
566
- blockNumber,
567
- status: response.status,
568
- body,
569
- url
570
- });
571
- if (status !== 1) throw malformedConfirmation(response, body, url);
572
- const result = parseTradeExecution(record === null || record === void 0 ? void 0 : record["Data"]);
573
- if (result === void 0) throw malformedConfirmation(response, body, url);
574
- return {
1062
+ confirmApplied(options) {
1063
+ return confirmAppliedTrade({
1064
+ dexBackendBaseUrl: this.backendBaseUrl,
1065
+ httpRequestor: this.requestor,
1066
+ requestTimeoutMs: this.requestTimeoutMs
1067
+ }, {
575
1068
  uniqueKey: this.uniqueKey,
576
- transactionId: applyTransactionId,
577
- blockNumber,
578
- requestTransactionId: this.transactionId,
579
- result
580
- };
1069
+ requestTransactionId: this.transactionId
1070
+ }, options);
581
1071
  }
582
1072
  /** Confirm a liquidity write against the position's observable effect.
583
1073
  *
@@ -589,86 +1079,33 @@ var SubmittedTransaction = class {
589
1079
  */
590
1080
  async confirmPosition(options) {
591
1081
  if (this.positionConfirmation === void 0) throw GSwapSDKError.noWalletAddressError();
592
- const schedule = createConfirmationSchedule(options, 0);
1082
+ const callerSignal = options === null || options === void 0 ? void 0 : options.signal;
1083
+ const abortError = (cause) => new GSwapSDKError("Transaction confirmation was aborted.", "CONFIRMATION_ABORTED", {
1084
+ uniqueKey: this.uniqueKey,
1085
+ ...cause === void 0 ? {} : { cause }
1086
+ });
1087
+ const schedule = createConfirmationSchedule(options, 0, abortError);
593
1088
  await schedule.waitForRead();
594
1089
  while (true) {
595
1090
  const controller = new AbortController();
596
- const timer = setTimeout(() => controller.abort(), this.requestTimeoutMs);
1091
+ const disarm = armTimeout(controller, this.requestTimeoutMs);
1092
+ const forwardAbort = () => controller.abort();
1093
+ callerSignal === null || callerSignal === void 0 || callerSignal.addEventListener("abort", forwardAbort, { once: true });
597
1094
  try {
598
1095
  const result = await this.positionConfirmation(controller.signal);
599
1096
  if (result.confirmed) return result.position;
1097
+ } catch (error) {
1098
+ throwIfAborted(callerSignal, abortError);
1099
+ throw error;
600
1100
  } finally {
601
- clearTimeout(timer);
1101
+ disarm();
1102
+ callerSignal === null || callerSignal === void 0 || callerSignal.removeEventListener("abort", forwardAbort);
602
1103
  }
603
1104
  if (!await schedule.waitForRead()) break;
604
1105
  }
605
1106
  throw GSwapSDKError.confirmationTimeoutError(this.uniqueKey);
606
1107
  }
607
1108
  };
608
- function createConfirmationSchedule(options, defaultInitialDelayMs) {
609
- const timeoutMs = Math.max(0, (options === null || options === void 0 ? void 0 : options.timeoutMs) ?? 6e4);
610
- const pollIntervalMs = Math.max(500, (options === null || options === void 0 ? void 0 : options.pollIntervalMs) ?? 2e3);
611
- const initialDelayMs = Math.max(0, (options === null || options === void 0 ? void 0 : options.initialDelayMs) ?? defaultInitialDelayMs);
612
- const deadline = Date.now() + timeoutMs;
613
- let firstRead = true;
614
- let attempt = 0;
615
- return { async waitForRead(retryAfterMs) {
616
- if (firstRead) {
617
- firstRead = false;
618
- const remaining = Math.max(0, deadline - Date.now());
619
- await delay(Math.min(initialDelayMs, remaining));
620
- return true;
621
- }
622
- const backoff = Math.min(8e3, pollIntervalMs * 2 ** Math.min(attempt, 5));
623
- attempt += 1;
624
- const waitMs = retryAfterMs === void 0 ? jitter(backoff) : retryAfterMs;
625
- if (Date.now() + waitMs > deadline) return false;
626
- await delay(waitMs);
627
- return true;
628
- } };
629
- }
630
- function parseTradeExecution(value) {
631
- const record = asRecord$1(value);
632
- if (record === void 0 || typeof record["token0"] !== "string" || typeof record["token1"] !== "string" || typeof record["fee"] !== "number") return;
633
- const optionalFields = [
634
- "sell0Qty",
635
- "sell1Qty",
636
- "buy0Qty",
637
- "buy1Qty",
638
- "tradingFees",
639
- "protocolFees",
640
- "totalFees"
641
- ];
642
- for (const field of optionalFields) if (field in record && typeof record[field] !== "string") return void 0;
643
- return {
644
- token0: record["token0"],
645
- token1: record["token1"],
646
- fee: record["fee"],
647
- ...Object.fromEntries(optionalFields.flatMap((field) => typeof record[field] === "string" ? [[field, record[field]]] : []))
648
- };
649
- }
650
- function malformedConfirmation(response, body, url) {
651
- return new GSwapSDKError("Transaction confirmation returned a malformed body", "CONFIRMATION_FAILED", {
652
- status: response.status,
653
- body,
654
- url
655
- });
656
- }
657
- function isTransient(status) {
658
- return status === 500 || status === 502 || status === 503 || status === 504;
659
- }
660
- function bodyMessage(body) {
661
- return getStringProperty(body, "message") ?? getStringProperty(body, "Message") ?? getStringProperty(getObjectProperty(body, "error"), "message") ?? getStringProperty(getObjectProperty(body, "error"), "Message") ?? (typeof body === "string" ? body : "Transaction confirmation failed");
662
- }
663
- function asRecord$1(value) {
664
- return typeof value === "object" && value !== null ? value : void 0;
665
- }
666
- function delay(milliseconds) {
667
- return new Promise((resolve) => setTimeout(resolve, milliseconds));
668
- }
669
- function jitter(milliseconds) {
670
- return Math.max(0, Math.round(milliseconds * (.9 + Math.random() * .2)));
671
- }
672
1109
 
673
1110
  //#endregion
674
1111
  //#region src/classes/gateway.ts
@@ -679,38 +1116,28 @@ var ChainGateway = class {
679
1116
  chainCallTimeoutMs;
680
1117
  requestTimeoutMs;
681
1118
  dexBackendBaseUrl;
1119
+ /** Wire format shared by the read services built on this gateway. */
1120
+ wire;
682
1121
  /** Create a chain gateway client. */
683
1122
  constructor(options) {
684
1123
  this.dexBackendBaseUrl = trimTrailingSlash$1(options.dexBackendBaseUrl);
685
1124
  this.requestor = options.httpRequestor ?? fetch.bind(globalThis);
686
1125
  this.walletAddress = options.walletAddress;
687
- this.chainCallTimeoutMs = options.chainCallTimeoutMs ?? 3e4;
1126
+ this.chainCallTimeoutMs = validateTimeoutMs(options.chainCallTimeoutMs ?? 3e4, "chainCallTimeoutMs");
688
1127
  this.requestTimeoutMs = this.chainCallTimeoutMs;
1128
+ this.wire = options.wire ?? {};
689
1129
  }
690
1130
  async submit(method, signedBody, options) {
691
1131
  if (method === "Trade") throw new GSwapSDKError("The synchronous Trade method was removed; swaps are queued through RequestTrade.", "METHOD_NOT_SUPPORTED", { method });
692
- const url = `${this.dexBackendBaseUrl}/v1/chain/asset/dex-contract/${method}`;
1132
+ const url = this.writeUrl(method);
693
1133
  const headers = { "content-type": "application/json" };
694
1134
  const walletAddress = (options === null || options === void 0 ? void 0 : options.walletAddress) ?? this.walletAddress;
695
1135
  if (walletAddress !== void 0) headers["X-Wallet-Address"] = walletAddress;
696
- let response;
697
- try {
698
- response = await requestWithTimeout(this.requestor, url, {
699
- method: "POST",
700
- headers,
701
- body: JSON.stringify(signedBody)
702
- }, this.chainCallTimeoutMs);
703
- } catch (error) {
704
- if (error instanceof GSwapSDKError && error.code === "REQUEST_TIMEOUT") throw new GSwapSDKError("The submission outcome is unknown because the gateway request timed out. Confirm before retrying.", "SUBMISSION_OUTCOME_UNKNOWN", {
705
- uniqueKey: getStringProperty(signedBody, "uniqueKey") ?? "",
706
- cause: error
707
- });
708
- throw error;
709
- }
1136
+ const response = await this.post(url, headers, signedBody);
710
1137
  const body = await readResponseBody(response);
711
1138
  if (!response.ok) throw gatewayError(response, body, url);
712
- const envelope = asRecord(body);
713
- const data = asRecord((envelope === null || envelope === void 0 ? void 0 : envelope["data"]) ?? (envelope === null || envelope === void 0 ? void 0 : envelope["Data"]));
1139
+ const envelope = asRecord$1(body);
1140
+ const data = asRecord$1((envelope === null || envelope === void 0 ? void 0 : envelope["data"]) ?? (envelope === null || envelope === void 0 ? void 0 : envelope["Data"]));
714
1141
  if (data === void 0) throw invalidGatewayResponse(response, body, url, "Expected a data object.");
715
1142
  const transactionId = getStringProperty(data, "transactionId");
716
1143
  const mode = getStringProperty(data, "mode");
@@ -725,7 +1152,7 @@ var ChainGateway = class {
725
1152
  if ("uniqueKey" in data && (typeof data["uniqueKey"] !== "string" || data["uniqueKey"] !== signedUniqueKey)) throw invalidGatewayResponse(response, body, url, "Gateway response data.uniqueKey must match the signed RequestTrade uniqueKey.");
726
1153
  } else if (mode === "queued" || !("result" in data)) throw invalidGatewayResponse(response, body, url, invalidModeMessage);
727
1154
  if (mode === "sync") {
728
- const embeddedResult = asRecord(data["result"]);
1155
+ const embeddedResult = asRecord$1(data["result"]);
729
1156
  if (embeddedResult !== void 0) {
730
1157
  const resultStatus = embeddedResult["Status"];
731
1158
  if (resultStatus !== void 0 && resultStatus !== 1) throw GSwapSDKError.fromChainError(getStringProperty(embeddedResult, "ErrorKey") ?? "CHAIN_ERROR", getStringProperty(embeddedResult, "Message") ?? "The chain rejected this write.", {
@@ -735,22 +1162,75 @@ var ChainGateway = class {
735
1162
  });
736
1163
  }
737
1164
  }
738
- const uniqueKey = getStringProperty(signedBody, "uniqueKey") ?? "";
739
- const headerTransactionId = readHeader(response, "x-transaction-id");
740
- const resolvedTransactionId = transactionId === void 0 || transactionId === "" ? headerTransactionId : transactionId;
741
- const blockNumber = bodyBlockNumber === null || bodyBlockNumber === void 0 ? readBlockNumberHeader(response) : isBlockNumber(bodyBlockNumber) ? bodyBlockNumber : void 0;
742
- return new SubmittedTransaction({
743
- method,
744
- uniqueKey,
745
- transactionId: resolvedTransactionId === void 0 || resolvedTransactionId === "" ? null : resolvedTransactionId,
746
- blockNumber: blockNumber ?? null,
747
- mode,
748
- result: mode === "queued" ? void 0 : data["result"],
749
- dexBackendBaseUrl: this.dexBackendBaseUrl,
750
- httpRequestor: this.requestor,
751
- requestTimeoutMs: this.chainCallTimeoutMs,
752
- ...(options === null || options === void 0 ? void 0 : options.positionConfirmation) === void 0 ? {} : { positionConfirmation: options.positionConfirmation }
753
- });
1165
+ const uniqueKey = getStringProperty(signedBody, "uniqueKey") ?? "";
1166
+ const headerTransactionId = readHeader(response, "x-transaction-id");
1167
+ const resolvedTransactionId = transactionId === void 0 || transactionId === "" ? headerTransactionId : transactionId;
1168
+ const blockNumber = bodyBlockNumber === null || bodyBlockNumber === void 0 ? readBlockNumberHeader(response) : isBlockNumber(bodyBlockNumber) ? bodyBlockNumber : void 0;
1169
+ return new SubmittedTransaction({
1170
+ method,
1171
+ uniqueKey,
1172
+ transactionId: resolvedTransactionId === void 0 || resolvedTransactionId === "" ? null : resolvedTransactionId,
1173
+ blockNumber: blockNumber ?? null,
1174
+ mode,
1175
+ result: mode === "queued" ? void 0 : data["result"],
1176
+ envelope: body,
1177
+ dexBackendBaseUrl: this.dexBackendBaseUrl,
1178
+ httpRequestor: this.requestor,
1179
+ requestTimeoutMs: this.chainCallTimeoutMs,
1180
+ ...(options === null || options === void 0 ? void 0 : options.positionConfirmation) === void 0 ? {} : { positionConfirmation: options.positionConfirmation }
1181
+ });
1182
+ }
1183
+ /**
1184
+ * Submit a signed v2 DTO as a SHADOW (dark) write: the same route and body as {@link submit},
1185
+ * plus `X-Wallet-Address` and `X-Shadow: true`, so defi-backend marks the transaction private
1186
+ * before it reaches the contract. The wallet address is required (the backend resolves the
1187
+ * owner from it).
1188
+ *
1189
+ * Unlike `submit`, EVERY HTTP status resolves: the result is the raw `{ httpStatus, body }`,
1190
+ * because shadow failures are classified from the status together with the body's `code`
1191
+ * (`SHADOW_UNAVAILABLE`, `SHADOW_DISABLED`, `SHADOW_UNSUPPORTED_TYPE`, `SHADOW_RELAY_REJECTED`);
1192
+ * see `readShadowSubmitOutcome`. Only a transport failure rejects, and a timeout rejects
1193
+ * with `SUBMISSION_OUTCOME_UNKNOWN`.
1194
+ *
1195
+ * @example
1196
+ * ```ts
1197
+ * const result = await gateway.submitShadow('RequestTrade', signedDto, { walletAddress });
1198
+ * const outcome = readShadowSubmitOutcome('RequestTrade', signedDto, result);
1199
+ * ```
1200
+ */
1201
+ async submitShadow(method, signedBody, options) {
1202
+ if (method === "Trade") throw new GSwapSDKError("The synchronous Trade method was removed; swaps are queued through RequestTrade.", "METHOD_NOT_SUPPORTED", { method });
1203
+ const walletAddress = (options === null || options === void 0 ? void 0 : options.walletAddress) ?? this.walletAddress;
1204
+ if (walletAddress === void 0) throw new GSwapSDKError("A shadow submission needs a walletAddress (the X-Wallet-Address header): pass one to the GSwap constructor or to this call.", "NO_WALLET_ADDRESS");
1205
+ const headers = {
1206
+ "content-type": "application/json",
1207
+ "X-Wallet-Address": walletAddress,
1208
+ "X-Shadow": "true"
1209
+ };
1210
+ const response = await this.post(this.writeUrl(method), headers, signedBody);
1211
+ return {
1212
+ httpStatus: response.status,
1213
+ body: await readResponseBody(response)
1214
+ };
1215
+ }
1216
+ writeUrl(method) {
1217
+ return `${this.dexBackendBaseUrl}/v1/chain/asset/dex-contract/${method}`;
1218
+ }
1219
+ /** POSTs a signed body; a timeout means the outcome is unknown, never "not submitted". */
1220
+ async post(url, headers, signedBody) {
1221
+ try {
1222
+ return await requestWithTimeout(this.requestor, url, {
1223
+ method: "POST",
1224
+ headers,
1225
+ body: JSON.stringify(signedBody)
1226
+ }, this.chainCallTimeoutMs);
1227
+ } catch (error) {
1228
+ if (error instanceof GSwapSDKError && error.code === "REQUEST_TIMEOUT") throw new GSwapSDKError("The submission outcome is unknown because the gateway request timed out. Confirm before retrying.", "SUBMISSION_OUTCOME_UNKNOWN", {
1229
+ uniqueKey: getStringProperty(signedBody, "uniqueKey") ?? "",
1230
+ cause: error
1231
+ });
1232
+ throw error;
1233
+ }
754
1234
  }
755
1235
  /** Expose the requestor for sibling read services that share this gateway transport. */
756
1236
  get httpRequestor() {
@@ -768,10 +1248,10 @@ function invalidGatewayResponse(response, body, url, message) {
768
1248
  });
769
1249
  }
770
1250
  function gatewayError(response, body, url) {
771
- const object = asRecord(body);
1251
+ const object = asRecord$1(body);
772
1252
  const nested = getObjectProperty(body, "error");
773
1253
  const code = getStringProperty(object, "code") ?? getStringProperty(nested, "code") ?? getStringProperty(nested, "ErrorKey") ?? "HTTP_ERROR";
774
- const message = getStringProperty(object, "message") ?? getStringProperty(nested, "message") ?? getStringProperty(nested, "Message") ?? (typeof body === "string" ? body : `Gateway request failed with HTTP ${response.status}`);
1254
+ const message = getStringProperty(object, "message") ?? getStringProperty(nested, "message") ?? getStringProperty(nested, "Message") ?? `Gateway request failed with HTTP ${response.status}`;
775
1255
  const retryAfterMs = response.status === 429 ? readRetryAfter(response) : void 0;
776
1256
  if (code === "HTTP_ERROR") return new GSwapSDKError(message, code, {
777
1257
  status: response.status,
@@ -780,7 +1260,7 @@ function gatewayError(response, body, url) {
780
1260
  });
781
1261
  return GSwapSDKError.fromGatewayBounce(code, message, response.status, retryAfterMs);
782
1262
  }
783
- function asRecord(value) {
1263
+ function asRecord$1(value) {
784
1264
  return typeof value === "object" && value !== null ? value : void 0;
785
1265
  }
786
1266
  function isBlockNumber(value) {
@@ -871,6 +1351,11 @@ var GdexStreamError = class extends Error {
871
1351
  if (data !== void 0) this.data = data;
872
1352
  }
873
1353
  };
1354
+ function readSubscriptionId(value) {
1355
+ const id = typeof value === "object" && value !== null ? value.subscriptionId : void 0;
1356
+ if (typeof id !== "string") throw new GdexStreamError(-32603, "GDEX subscribe response is missing its subscriptionId");
1357
+ return id;
1358
+ }
874
1359
  /**
875
1360
  * Client for the gSwap realtime stream (gdex WebSocket Protocol v1): JSON-RPC 2.0 over one raw
876
1361
  * WebSocket, with automatic reconnect and resubscribe.
@@ -887,6 +1372,7 @@ var GdexStreamError = class extends Error {
887
1372
  var GdexStreamService = class {
888
1373
  url;
889
1374
  requestTimeoutMs;
1375
+ apiKey;
890
1376
  webSocketConstructor;
891
1377
  backoff;
892
1378
  socket;
@@ -909,6 +1395,10 @@ var GdexStreamService = class {
909
1395
  const implementation = config.webSocketImpl ?? globalThis.WebSocket;
910
1396
  if (typeof implementation !== "function") throw new Error("GdexStreamService requires a standards-compatible WebSocket constructor. Node.js versions before 22 must provide one with config.webSocketImpl (for example, from the \"ws\" package).");
911
1397
  this.webSocketConstructor = implementation;
1398
+ if (config.apiKey !== void 0) {
1399
+ if (config.apiKey === "" || config.webSocketImpl === void 0) throw new GSwapSDKError("GdexStreamConfig.apiKey needs a non-empty key and an explicit webSocketImpl that accepts upgrade headers as its third argument (the \"ws\" package, or React Native). A browser WebSocket cannot send x-api-key, so the key would be silently dropped.", "VALIDATION_ERROR");
1400
+ this.apiKey = config.apiKey;
1401
+ }
912
1402
  }
913
1403
  /** Open the connection. Resolves at once when already connected. */
914
1404
  connect() {
@@ -936,6 +1426,9 @@ var GdexStreamService = class {
936
1426
  }
937
1427
  /** Raw JSON-RPC escape hatch. */
938
1428
  request(method, params) {
1429
+ return this.send(method, params);
1430
+ }
1431
+ send(method, params, onResult) {
939
1432
  if (!this.isConnected() || !this.socket) return Promise.reject(/* @__PURE__ */ new Error("GDEX stream is not connected. Call connect() first."));
940
1433
  const id = ++this.nextRequestId;
941
1434
  const frame = {
@@ -952,7 +1445,8 @@ var GdexStreamService = class {
952
1445
  this.pending.set(id, {
953
1446
  resolve: (value) => resolve(value),
954
1447
  reject,
955
- timeout
1448
+ timeout,
1449
+ onResult
956
1450
  });
957
1451
  try {
958
1452
  var _this$socket2;
@@ -995,7 +1489,11 @@ var GdexStreamService = class {
995
1489
  subscribeSwaps(cb, opts = {}) {
996
1490
  return this.subscribe("swap", cb, opts);
997
1491
  }
998
- /** State changes of one pool. */
1492
+ /**
1493
+ * State changes of one pool (price, tick, reserves, liquidity). These events carry no acting wallet:
1494
+ * on v2 `creator` is whoever created the pool, not whoever just traded or added liquidity. For
1495
+ * "who did it", use {@link subscribeLiquidity} (`owner`) or {@link subscribeSwaps} (`sender`).
1496
+ */
999
1497
  subscribePool(selector, cb, opts = {}) {
1000
1498
  return this.subscribe("pool", cb, {
1001
1499
  ...selector,
@@ -1033,7 +1531,7 @@ var GdexStreamService = class {
1033
1531
  openSocket() {
1034
1532
  return new Promise((resolve, reject) => {
1035
1533
  let opened = false;
1036
- const socket = new this.webSocketConstructor(this.url, GDEX_SUBPROTOCOL);
1534
+ const socket = this.apiKey === void 0 ? new this.webSocketConstructor(this.url, GDEX_SUBPROTOCOL) : new this.webSocketConstructor(this.url, GDEX_SUBPROTOCOL, { headers: { "x-api-key": this.apiKey } });
1037
1535
  this.socket = socket;
1038
1536
  socket.onopen = () => {
1039
1537
  if (this.socket !== socket) return;
@@ -1078,7 +1576,16 @@ var GdexStreamService = class {
1078
1576
  const code = typeof message.error.code === "number" ? message.error.code : -32603;
1079
1577
  const errorMessage = typeof message.error.message === "string" ? message.error.message : "GDEX request failed";
1080
1578
  pending.reject(new GdexStreamError(code, errorMessage, message.error.data));
1081
- } else pending.resolve(message.result);
1579
+ } else {
1580
+ try {
1581
+ var _pending$onResult;
1582
+ (_pending$onResult = pending.onResult) === null || _pending$onResult === void 0 || _pending$onResult.call(pending, message.result);
1583
+ } catch (error) {
1584
+ pending.reject(error instanceof Error ? error : new Error(String(error)));
1585
+ return;
1586
+ }
1587
+ pending.resolve(message.result);
1588
+ }
1082
1589
  return;
1083
1590
  }
1084
1591
  if (message.method !== "gdex_subscription" || !this.isRecord(message.params)) return;
@@ -1098,21 +1605,24 @@ var GdexStreamService = class {
1098
1605
  channel,
1099
1606
  ...options
1100
1607
  };
1101
- const result = await this.request("gdex_subscribe", params);
1102
- let active;
1103
1608
  const handle = {
1104
- subscriptionId: result.subscriptionId,
1105
- unsubscribe: async () => this.unsubscribe(active)
1609
+ subscriptionId: "",
1610
+ unsubscribe: async () => this.unsubscribe(subscription)
1106
1611
  };
1107
- active = {
1108
- serverId: result.subscriptionId,
1612
+ const subscription = {
1613
+ serverId: "",
1109
1614
  params,
1110
1615
  callback,
1111
1616
  handle,
1112
1617
  active: true
1113
1618
  };
1114
- this.activeSubscriptions.add(active);
1115
- this.subscriptionsByServerId.set(active.serverId, active);
1619
+ await this.send("gdex_subscribe", params, (value) => {
1620
+ const serverId = readSubscriptionId(value);
1621
+ subscription.serverId = serverId;
1622
+ handle.subscriptionId = serverId;
1623
+ this.activeSubscriptions.add(subscription);
1624
+ this.subscriptionsByServerId.set(serverId, subscription);
1625
+ });
1116
1626
  return handle;
1117
1627
  }
1118
1628
  async unsubscribe(subscription) {
@@ -1155,19 +1665,19 @@ var GdexStreamService = class {
1155
1665
  ...subscription.params,
1156
1666
  snapshot: true
1157
1667
  };
1158
- const result = await this.resubscribeWithRateLimitRetry(params);
1159
- if (!subscription.active) {
1160
- await this.request("gdex_unsubscribe", { subscriptionId: result.subscriptionId });
1161
- continue;
1162
- }
1163
- subscription.serverId = result.subscriptionId;
1164
- subscription.handle.subscriptionId = result.subscriptionId;
1165
- this.subscriptionsByServerId.set(result.subscriptionId, subscription);
1668
+ const result = await this.resubscribeWithRateLimitRetry(params, (value) => {
1669
+ const serverId = readSubscriptionId(value);
1670
+ if (!subscription.active) return;
1671
+ subscription.serverId = serverId;
1672
+ subscription.handle.subscriptionId = serverId;
1673
+ this.subscriptionsByServerId.set(serverId, subscription);
1674
+ });
1675
+ if (!subscription.active) await this.request("gdex_unsubscribe", { subscriptionId: result.subscriptionId });
1166
1676
  }
1167
1677
  }
1168
- async resubscribeWithRateLimitRetry(params) {
1678
+ async resubscribeWithRateLimitRetry(params, onResult) {
1169
1679
  for (let attempt = 0;; attempt++) try {
1170
- return await this.request("gdex_subscribe", params);
1680
+ return await this.send("gdex_subscribe", params, onResult);
1171
1681
  } catch (error) {
1172
1682
  if (!(error instanceof GdexStreamError) || error.code !== RATE_LIMITED || attempt >= MAX_RESUBSCRIBE_RATE_RETRIES) throw error;
1173
1683
  const retryAfterMs = this.readRetryAfterMs(error.data);
@@ -1211,28 +1721,35 @@ var GdexStreamService = class {
1211
1721
  //#endregion
1212
1722
  //#region src/version.ts
1213
1723
  /** Package version embedded by the bundler, with a source-test fallback. */
1214
- const SDK_VERSION = "1.1.0";
1724
+ const SDK_VERSION = "1.2.0-rc.1";
1215
1725
 
1216
1726
  //#endregion
1217
1727
  //#region src/classes/http_client.ts
1218
1728
  var HttpClient = class {
1219
1729
  httpRequestor;
1220
1730
  timeoutMs;
1221
- /** Create an HTTP client around fetch or an injected requestor. */
1222
- constructor(httpRequestor = fetch, timeoutMs = 3e4) {
1731
+ wire;
1732
+ /**
1733
+ * Create an HTTP client around fetch or an injected requestor. `wire` selects the query
1734
+ * encoding and the `User-Agent` (see {@link WireOptions}).
1735
+ */
1736
+ constructor(httpRequestor = fetch, timeoutMs = 3e4, wire = {}) {
1223
1737
  this.httpRequestor = httpRequestor;
1224
1738
  this.timeoutMs = timeoutMs;
1739
+ this.wire = wire;
1225
1740
  }
1226
1741
  async sendRequest(method, baseUrl, basePath, endpoint, body, options) {
1227
1742
  const url = `${baseUrl}${basePath}${endpoint}`;
1228
1743
  debugLog(`Sending request to ${url} with body:`, body);
1744
+ const hasBody = Boolean(body);
1229
1745
  const response = await requestWithTimeout(this.httpRequestor, url, {
1230
1746
  method,
1231
1747
  headers: {
1232
- "Content-Type": "application/json",
1233
- "User-Agent": `GalaChain-SDK/${SDK_VERSION}`
1748
+ ...hasBody ? { "Content-Type": "application/json" } : {},
1749
+ ...this.wire.userAgent === false ? {} : { "User-Agent": this.wire.userAgent ?? `GalaChain-SDK/${"1.2.0-rc.1"}` },
1750
+ ...this.wire.headers
1234
1751
  },
1235
- ...body ? { body: JSON.stringify(body) } : {},
1752
+ ...hasBody ? { body: JSON.stringify(body) } : {},
1236
1753
  ...(options === null || options === void 0 ? void 0 : options.signal) === void 0 ? {} : { signal: options.signal }
1237
1754
  }, this.timeoutMs);
1238
1755
  if (!response.ok) throw await GSwapSDKError.fromErrorResponse(url, response);
@@ -1246,12 +1763,65 @@ var HttpClient = class {
1246
1763
  }
1247
1764
  /** Send a GET request with URL-encoded query parameters and decode its response. */
1248
1765
  async sendGetRequest(baseUrl, basePath, endpoint, params, options) {
1249
- const searchParams = params ? new URLSearchParams(params) : void 0;
1250
- const endpointWithParams = searchParams ? `${endpoint}?${searchParams.toString()}` : endpoint;
1766
+ const endpointWithParams = params ? `${endpoint}?${buildQueryString(params, this.wire.queryEncoding)}` : endpoint;
1251
1767
  return this.sendRequest("GET", baseUrl, basePath, endpointWithParams, void 0, options);
1252
1768
  }
1253
1769
  };
1254
1770
 
1771
+ //#endregion
1772
+ //#region src/utils/lenient.ts
1773
+ /** A usable row. */
1774
+ function entryOk(value) {
1775
+ return {
1776
+ ok: true,
1777
+ value
1778
+ };
1779
+ }
1780
+ /** An unusable row, with the reason it is reported under. */
1781
+ function entryRejected(reason) {
1782
+ return {
1783
+ ok: false,
1784
+ reason
1785
+ };
1786
+ }
1787
+ /**
1788
+ * Reads a list answer row by row: a row that cannot be used is skipped and reported, instead of
1789
+ * rejecting the whole list because of one bad row. An answer that is not a list at all is still
1790
+ * `INVALID_CHAIN_RESPONSE`: nothing in it can be trusted.
1791
+ */
1792
+ function readEntries(source, data, parse, onInvalidEntry) {
1793
+ if (!Array.isArray(data)) throw new GSwapSDKError(`Backend returned an invalid ${source} list.`, "INVALID_CHAIN_RESPONSE", { data });
1794
+ const rows = [];
1795
+ data.forEach((entry, index) => {
1796
+ const parsed = parse(entry);
1797
+ if (parsed.ok) {
1798
+ rows.push(parsed.value);
1799
+ return;
1800
+ }
1801
+ try {
1802
+ onInvalidEntry === null || onInvalidEntry === void 0 || onInvalidEntry({
1803
+ source,
1804
+ index,
1805
+ entry,
1806
+ reason: parsed.reason
1807
+ });
1808
+ } catch {}
1809
+ });
1810
+ return rows;
1811
+ }
1812
+ /** A plain JSON object (not null, not an array). */
1813
+ function asEntryRecord(value) {
1814
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
1815
+ }
1816
+ /** A finite number, or a non-empty numeric string, as a number; otherwise `undefined`. */
1817
+ function finiteNumberOf(value) {
1818
+ if (typeof value === "number") return Number.isFinite(value) ? value : void 0;
1819
+ if (typeof value === "string" && value.trim() !== "") {
1820
+ const parsed = Number(value);
1821
+ return Number.isFinite(parsed) ? parsed : void 0;
1822
+ }
1823
+ }
1824
+
1255
1825
  //#endregion
1256
1826
  //#region src/utils/ordering.ts
1257
1827
  /** Return two trading symbols in the contract's plain-string order. */
@@ -1330,39 +1900,250 @@ function parseTokenClassKey(tokenClassKey) {
1330
1900
  };
1331
1901
  }
1332
1902
 
1903
+ //#endregion
1904
+ //#region src/classes/symbols.ts
1905
+ const SYMBOL_CACHE_TTL_MS = 6e4;
1906
+ /** Symbol registry client for the current GalaChainDex contract. */
1907
+ var Symbols = class {
1908
+ gateway;
1909
+ options;
1910
+ cachedSymbols;
1911
+ cachedAt = 0;
1912
+ refreshInFlight;
1913
+ http;
1914
+ /**
1915
+ * Create a symbol service backed by the swap backend. `onInvalidEntry` is told about every
1916
+ * registry row that was skipped as unusable (see {@link InvalidEntryHandler}).
1917
+ */
1918
+ constructor(gateway, options = {}) {
1919
+ this.gateway = gateway;
1920
+ this.options = options;
1921
+ this.http = new HttpClient(gateway.httpRequestor, gateway.requestTimeoutMs, gateway.wire);
1922
+ }
1923
+ /**
1924
+ * List all registered GalaChainDex trading symbols, cached for 60 seconds. A registry row that
1925
+ * is not a usable symbol is skipped (and reported to `onInvalidEntry`) instead of failing the
1926
+ * whole list.
1927
+ */
1928
+ async list() {
1929
+ if (this.cachedSymbols !== void 0 && Date.now() - this.cachedAt < SYMBOL_CACHE_TTL_MS) return this.cachedSymbols;
1930
+ if (this.refreshInFlight !== void 0) return this.refreshInFlight;
1931
+ this.refreshInFlight = this.http.sendGetRequest(this.gateway.dexBackendBaseUrl, "/v2/trade", "/symbols").then((response) => readEntries("symbols", response.data, parseTradingSymbol, this.options.onInvalidEntry)).then((symbols) => {
1932
+ this.cachedSymbols = symbols;
1933
+ this.cachedAt = Date.now();
1934
+ return symbols;
1935
+ }).finally(() => {
1936
+ this.refreshInFlight = void 0;
1937
+ });
1938
+ return this.refreshInFlight;
1939
+ }
1940
+ /** Invalidate the cached symbol registry after a write changes registration state.
1941
+ *
1942
+ * @example
1943
+ * ```ts
1944
+ * gSwap.symbols.invalidate();
1945
+ * ```
1946
+ */
1947
+ invalidate() {
1948
+ this.cachedSymbols = void 0;
1949
+ this.cachedAt = 0;
1950
+ }
1951
+ /** Force one registry refresh, bypassing the cache.
1952
+ *
1953
+ * @example
1954
+ * ```ts
1955
+ * const symbols = await gSwap.symbols.refresh();
1956
+ * ```
1957
+ */
1958
+ async refresh() {
1959
+ this.invalidate();
1960
+ this.refreshInFlight = void 0;
1961
+ return this.list();
1962
+ }
1963
+ /** Resolve a symbol or token class key to its registered symbol metadata. */
1964
+ async resolve(ref) {
1965
+ let symbols = await this.list();
1966
+ const composite = typeof ref === "string" ? isCompositeKey(ref) ? compositeKeyOf(parseTokenClassKey(ref)) : void 0 : compositeKeyOf(ref);
1967
+ const resolved = symbols.find((entry) => entry.symbol === ref || composite !== void 0 && compositeKeyOf(entry) === composite);
1968
+ if (resolved === void 0) {
1969
+ symbols = await this.refresh();
1970
+ const refreshed = symbols.find((entry) => entry.symbol === ref || composite !== void 0 && compositeKeyOf(entry) === composite);
1971
+ if (refreshed === void 0) throw GSwapSDKError.unknownTokenError(ref);
1972
+ return refreshed;
1973
+ }
1974
+ return resolved;
1975
+ }
1976
+ /**
1977
+ * The trading symbol to put in a token's first CreatePool.
1978
+ *
1979
+ * 1. The symbol ALREADY registered for exactly this class key, if any: proposing another one
1980
+ * fails the gateway's registry-conflict check.
1981
+ * 2. Otherwise `displaySymbol` (the token's curated symbol), when it is proposable
1982
+ * ({@link isProposableTradingSymbol}).
1983
+ * 3. Otherwise a `VALIDATION_ERROR`: a symbol is never derived from the key (BNB's collection is
1984
+ * `GBNB`, a launchpad token's is the reserved word `Token`).
1985
+ *
1986
+ * `registryFailure: 'ignore'` treats an unreachable registry as an empty one and falls through
1987
+ * to step 2 (the gateway still rejects a conflicting symbol); the default `'throw'` propagates
1988
+ * the failure.
1989
+ *
1990
+ * @example
1991
+ * ```ts
1992
+ * const symbol = await gSwap.symbols.proposeSymbol(
1993
+ * { collection: 'GALA_TEST1', category: 'Unit', type: 'none', additionalKey: 'none' },
1994
+ * 'GALATEST',
1995
+ * );
1996
+ * ```
1997
+ */
1998
+ async proposeSymbol(classKey, displaySymbol, options = {}) {
1999
+ let registry = [];
2000
+ try {
2001
+ registry = await this.list();
2002
+ } catch (error) {
2003
+ if (options.registryFailure !== "ignore") throw error;
2004
+ }
2005
+ const wanted = compositeKeyOf(classKey);
2006
+ const registered = registry.find((entry) => entry.collection === classKey.collection && entry.category === classKey.category && entry.type === classKey.type && entry.additionalKey === classKey.additionalKey);
2007
+ if (registered !== void 0) return registered.symbol;
2008
+ if (isProposableTradingSymbol(displaySymbol)) return displaySymbol;
2009
+ throw new GSwapSDKError(`No valid trading symbol for token class ${wanted}: "${displaySymbol}" is not an acceptable symbol and none is registered`, "VALIDATION_ERROR", {
2010
+ classKey,
2011
+ displaySymbol
2012
+ });
2013
+ }
2014
+ /** Resolve and put two token references in canonical trading-symbol order. */
2015
+ async orderPair(a, b) {
2016
+ const [resolvedA, resolvedB] = await Promise.all([this.resolve(a), this.resolve(b)]);
2017
+ const [token0Symbol, token1Symbol] = orderSymbols(resolvedA.symbol, resolvedB.symbol);
2018
+ return {
2019
+ token0: token0Symbol === resolvedA.symbol ? resolvedA : resolvedB,
2020
+ token1: token1Symbol === resolvedB.symbol ? resolvedB : resolvedA,
2021
+ flipped: token0Symbol !== resolvedA.symbol
2022
+ };
2023
+ }
2024
+ };
2025
+ /** Reads one registry row: all four class-key parts, a symbol and non-negative integer decimals. */
2026
+ function parseTradingSymbol(value) {
2027
+ const entry = asEntryRecord(value);
2028
+ if (entry === void 0) return entryRejected("the row is not an object");
2029
+ const { symbol, collection, category, type, additionalKey, decimals } = entry;
2030
+ if (typeof symbol !== "string") return entryRejected("symbol is not a string");
2031
+ if (symbol.length === 0) return entryRejected("symbol is empty");
2032
+ if (typeof collection !== "string") return entryRejected("collection is not a string");
2033
+ if (typeof category !== "string") return entryRejected("category is not a string");
2034
+ if (typeof type !== "string") return entryRejected("type is not a string");
2035
+ if (typeof additionalKey !== "string") return entryRejected("additionalKey is not a string");
2036
+ if (typeof decimals !== "number" || !Number.isInteger(decimals) || decimals < 0) return entryRejected("decimals is not a non-negative integer");
2037
+ return entryOk({
2038
+ symbol,
2039
+ collection,
2040
+ category,
2041
+ type,
2042
+ additionalKey,
2043
+ decimals
2044
+ });
2045
+ }
2046
+ function isCompositeKey(ref) {
2047
+ return ref.includes("|") || ref.includes("$");
2048
+ }
2049
+ /** A token reference as the caller wrote it: a string verbatim, a class key as its `$` composite. */
2050
+ function rawTokenOf(ref) {
2051
+ return typeof ref === "string" ? ref : compositeKeyOf(ref);
2052
+ }
2053
+ /** The trading symbol of a reference, or `SYMBOL_NOT_FOUND` when the resolver has none. */
2054
+ async function tradingSymbolOf(resolver, ref) {
2055
+ const resolved = await resolver.resolve(ref);
2056
+ if (resolved.symbol.length > 0) return resolved.symbol;
2057
+ throw new GSwapSDKError("Token could not be resolved to a trading symbol.", "SYMBOL_NOT_FOUND", { token: ref });
2058
+ }
2059
+ /** What a read request names a token by: the reference itself in pass-through mode, else its symbol. */
2060
+ async function queryTokenOf(resolver, ref) {
2061
+ return resolver.passthrough === true ? rawTokenOf(ref) : tradingSymbolOf(resolver, ref);
2062
+ }
2063
+ /**
2064
+ * The offline resolver for apps that already hold the backend's canonical trading symbols (from
2065
+ * `/quote`, `/add-liq-estimate`, a position row) and must not make a registry request of their own.
2066
+ *
2067
+ * - `resolve` accepts a bare trading symbol and returns it as is; a class key (object, `$` or `|`
2068
+ * form) is not a symbol and is refused as `UNKNOWN_TOKEN`. `createPool` relies on that refusal to
2069
+ * fall back to the symbols its caller supplies.
2070
+ * - `orderPair` orders two symbols by plain string comparison, as the contract does.
2071
+ * - Reads are pass-through (see {@link TokenResolver.passthrough}): the caller's references go on
2072
+ * the wire verbatim, composite keys included, and the backend orients the pair.
2073
+ *
2074
+ * @example
2075
+ * ```ts
2076
+ * const swaps = new Swaps(gateway, new CanonicalSymbols(), signer, walletAddress);
2077
+ * await swaps.swap('GALA', 'GUSDC', 3000, { exactIn: '10' });
2078
+ * ```
2079
+ */
2080
+ var CanonicalSymbols = class {
2081
+ passthrough = true;
2082
+ /** Resolve a bare trading symbol to itself. */
2083
+ resolve(ref) {
2084
+ if (typeof ref === "string" && !isCompositeKey(ref)) return Promise.resolve({
2085
+ symbol: ref,
2086
+ collection: "",
2087
+ category: "",
2088
+ type: "",
2089
+ additionalKey: "",
2090
+ decimals: 0
2091
+ });
2092
+ return Promise.reject(GSwapSDKError.unknownTokenError(ref));
2093
+ }
2094
+ /** Put two symbols in canonical (string) order. */
2095
+ async orderPair(a, b) {
2096
+ const [resolvedA, resolvedB] = await Promise.all([this.resolve(a), this.resolve(b)]);
2097
+ const [first] = orderSymbols(resolvedA.symbol, resolvedB.symbol);
2098
+ const flipped = first !== resolvedA.symbol;
2099
+ return {
2100
+ token0: flipped ? resolvedB : resolvedA,
2101
+ token1: flipped ? resolvedA : resolvedB,
2102
+ flipped
2103
+ };
2104
+ }
2105
+ /** There is no registry state to drop. */
2106
+ invalidate() {}
2107
+ };
2108
+
1333
2109
  //#endregion
1334
2110
  //#region src/classes/pools.ts
1335
2111
  /** Read-only pool and current-price access for the current GalaChainDex contract. */
1336
2112
  var Pools = class {
1337
2113
  gateway;
1338
2114
  symbols;
2115
+ options;
1339
2116
  /**
1340
- * Creates a pool service.
2117
+ * Creates a pool service. `options.onInvalidEntry` is told about every pool row that was
2118
+ * skipped as unusable (see {@link InvalidEntryHandler}).
1341
2119
  *
1342
2120
  * @example
1343
2121
  * ```ts
1344
2122
  * const pools = new Pools(gateway, symbols);
1345
2123
  * ```
1346
2124
  */
1347
- constructor(gateway, symbols) {
2125
+ constructor(gateway, symbols, options = {}) {
1348
2126
  this.gateway = gateway;
1349
2127
  this.symbols = symbols;
1350
- this.http = new HttpClient(gateway.httpRequestor, gateway.requestTimeoutMs ?? 3e4);
2128
+ this.options = options;
2129
+ this.http = new HttpClient(gateway.httpRequestor, gateway.requestTimeoutMs ?? 3e4, gateway.wire);
1351
2130
  }
1352
2131
  http;
1353
2132
  /**
1354
- * Fetches every current-contract pool from the swap backend.
2133
+ * Fetches current-contract pools from the swap backend, optionally searched, sorted and paged
2134
+ * by the backend. A row that is not a usable pool is skipped and reported to `onInvalidEntry`
2135
+ * instead of failing the whole list; an answer that is not a list at all is
2136
+ * `INVALID_CHAIN_RESPONSE`.
1355
2137
  *
1356
2138
  * @example
1357
2139
  * ```ts
1358
- * const pools = await service.getPools();
1359
- * console.log(pools.length);
2140
+ * const top = await service.getPools({ sortBy: 'tvl', sortOrder: 'desc', page: 1, limit: 8 });
2141
+ * console.log(top.length);
1360
2142
  * ```
1361
2143
  */
1362
2144
  async getPools(options) {
1363
- const response = await this.http.sendGetRequest(this.gateway.dexBackendBaseUrl, "/v2/trade", "/pools", void 0, (options === null || options === void 0 ? void 0 : options.signal) === void 0 ? void 0 : { signal: options.signal });
1364
- if (!Array.isArray(response.data) || !response.data.every(isPoolListInfo)) throw new GSwapSDKError("Backend returned an invalid pool list.", "INVALID_CHAIN_RESPONSE", { data: response.data });
1365
- return response.data;
2145
+ const response = await this.http.sendGetRequest(this.gateway.dexBackendBaseUrl, "/v2/trade", "/pools", poolListParams(options), (options === null || options === void 0 ? void 0 : options.signal) === void 0 ? void 0 : { signal: options.signal });
2146
+ return readEntries("pools", response.data, parsePoolListInfo, (options === null || options === void 0 ? void 0 : options.onInvalidEntry) ?? this.options.onInvalidEntry);
1366
2147
  }
1367
2148
  /**
1368
2149
  * Fetches one pool from the backend, preserving whether the request order was flipped.
@@ -1401,8 +2182,13 @@ var Pools = class {
1401
2182
  */
1402
2183
  async getCompositePool(token0, token1, fee) {
1403
2184
  validateFee(fee);
1404
- const first = await resolveSymbol$2(this.symbols, token0);
1405
- const second = await resolveSymbol$2(this.symbols, token1);
2185
+ if (this.symbols.passthrough === true) return this.getBackend("/composite-pool", {
2186
+ token0: rawTokenOf(token0),
2187
+ token1: rawTokenOf(token1),
2188
+ fee: String(fee)
2189
+ });
2190
+ const first = await tradingSymbolOf(this.symbols, token0);
2191
+ const second = await tradingSymbolOf(this.symbols, token1);
1406
2192
  const ordered = readOrderedSymbols(orderSymbols(first, second));
1407
2193
  return this.getBackend("/composite-pool", {
1408
2194
  token0: ordered.token0,
@@ -1412,8 +2198,8 @@ var Pools = class {
1412
2198
  }
1413
2199
  async backendParams(token0, token1, fee) {
1414
2200
  return {
1415
- token0: await resolveSymbol$2(this.symbols, token0),
1416
- token1: await resolveSymbol$2(this.symbols, token1),
2201
+ token0: await queryTokenOf(this.symbols, token0),
2202
+ token1: await queryTokenOf(this.symbols, token1),
1417
2203
  fee: String(fee)
1418
2204
  };
1419
2205
  }
@@ -1425,53 +2211,449 @@ var Pools = class {
1425
2211
  throw new GSwapSDKError("Pool request failed.", "HTTP_ERROR", { cause: error });
1426
2212
  }
1427
2213
  }
1428
- };
1429
- async function resolveSymbol$2(symbols, token) {
1430
- const resolved = await symbols.resolve(token);
1431
- if (resolved.symbol.length > 0) return resolved.symbol;
1432
- throw new GSwapSDKError("Token could not be resolved to a trading symbol.", "SYMBOL_NOT_FOUND", { token });
2214
+ };
2215
+ const SORT_KEYS = [
2216
+ "tvl",
2217
+ "volume1d",
2218
+ "volume30d"
2219
+ ];
2220
+ const MAX_SEARCH_LENGTH = 64;
2221
+ const MAX_PAGE_SIZE = 1e3;
2222
+ /** The pool-list query, in the order the apps write it: search, sortBy, sortOrder, page, limit. */
2223
+ function poolListParams(options) {
2224
+ if (options === void 0) return void 0;
2225
+ const params = {};
2226
+ if (options.search !== void 0) {
2227
+ if (options.search.length > MAX_SEARCH_LENGTH) throw new GSwapSDKError(`search must be at most ${MAX_SEARCH_LENGTH} characters.`, "VALIDATION_ERROR", { search: options.search });
2228
+ params["search"] = options.search;
2229
+ }
2230
+ if (options.sortBy !== void 0) {
2231
+ if (!SORT_KEYS.includes(options.sortBy)) throw new GSwapSDKError(`sortBy must be one of ${SORT_KEYS.join(", ")}.`, "VALIDATION_ERROR", { sortBy: options.sortBy });
2232
+ params["sortBy"] = options.sortBy;
2233
+ }
2234
+ if (options.sortOrder !== void 0) {
2235
+ if (options.sortOrder !== "asc" && options.sortOrder !== "desc") throw new GSwapSDKError("sortOrder must be asc or desc.", "VALIDATION_ERROR", { sortOrder: options.sortOrder });
2236
+ params["sortOrder"] = options.sortOrder;
2237
+ }
2238
+ if (options.page !== void 0) {
2239
+ if (!Number.isInteger(options.page) || options.page < 1) throw new GSwapSDKError("page must be an integer of at least 1.", "VALIDATION_ERROR", { page: options.page });
2240
+ params["page"] = String(options.page);
2241
+ }
2242
+ if (options.limit !== void 0) {
2243
+ if (!Number.isInteger(options.limit) || options.limit < 1 || options.limit > MAX_PAGE_SIZE) throw new GSwapSDKError(`limit must be an integer from 1 to ${MAX_PAGE_SIZE}.`, "VALIDATION_ERROR", { limit: options.limit });
2244
+ params["limit"] = String(options.limit);
2245
+ }
2246
+ return Object.keys(params).length === 0 ? void 0 : params;
2247
+ }
2248
+ const OPTIONAL_POOL_STRINGS = [
2249
+ "poolName",
2250
+ "token0CompositeKey",
2251
+ "token1CompositeKey",
2252
+ "token0Image",
2253
+ "token1Image",
2254
+ "creator",
2255
+ "price",
2256
+ "sqrtPrice",
2257
+ "token0Tvl",
2258
+ "token1Tvl"
2259
+ ];
2260
+ const OPTIONAL_POOL_NUMBERS = [
2261
+ "tickSpacing",
2262
+ "protocolFees",
2263
+ "tradingFees",
2264
+ "tick",
2265
+ "volume1d",
2266
+ "volume7d",
2267
+ "volume30d",
2268
+ "trades1d",
2269
+ "fee24h",
2270
+ "token0Price",
2271
+ "token1Price"
2272
+ ];
2273
+ /** Fields the apps accept as a number or a numeric string (the wallet does `Number(row.x)`). */
2274
+ const NUMERIC_POOL_FIELDS = [
2275
+ "fee",
2276
+ "tvlUsd",
2277
+ "apr1d"
2278
+ ];
2279
+ /**
2280
+ * Reads one pool-list row the way the apps do: the identity (`token0`, `token1`, `poolRef`) and
2281
+ * `fee` must be usable, every other field is optional but, when present, must have its type.
2282
+ * `fee`, `tvlUsd` and `apr1d` may arrive as numeric strings and are returned as numbers. Fields
2283
+ * the SDK does not know are kept.
2284
+ */
2285
+ function parsePoolListInfo(entry) {
2286
+ const row = asEntryRecord(entry);
2287
+ if (row === void 0) return entryRejected("the row is not an object");
2288
+ for (const key of [
2289
+ "token0",
2290
+ "token1",
2291
+ "poolRef"
2292
+ ]) if (typeof row[key] !== "string") return entryRejected(`${key} is not a string`);
2293
+ if (row["contractVersion"] !== void 0 && row["contractVersion"] !== "v2") return entryRejected("contractVersion is not v2");
2294
+ const normalized = { ...row };
2295
+ for (const key of NUMERIC_POOL_FIELDS) {
2296
+ const raw = row[key];
2297
+ if (raw === void 0 && key !== "fee") continue;
2298
+ const value = finiteNumberOf(raw);
2299
+ if (value === void 0) return entryRejected(`${key} is not a number`);
2300
+ normalized[key] = value;
2301
+ }
2302
+ for (const key of OPTIONAL_POOL_STRINGS) if (row[key] !== void 0 && typeof row[key] !== "string") return entryRejected(`${key} is not a string`);
2303
+ for (const key of OPTIONAL_POOL_NUMBERS) {
2304
+ const value = row[key];
2305
+ if (value !== void 0 && (typeof value !== "number" || !Number.isFinite(value))) return entryRejected(`${key} is not a number`);
2306
+ }
2307
+ return entryOk(normalized);
2308
+ }
2309
+
2310
+ //#endregion
2311
+ //#region src/utils/unique_key.ts
2312
+ /**
2313
+ * The `uniqueKey` prefix frontend_v2 and wallet_extension have always sent
2314
+ * (`galaswap-operation-<uuid>`). defi-backend does not depend on it: the chain-write verifier
2315
+ * only requires a non-empty key of at most 256 characters, and apply-results accepts
2316
+ * `[A-Za-z0-9._:-]{1,128}`; the only code that mints a prefixed key itself is the server-side v1
2317
+ * trade builder, which nothing reads back by prefix.
2318
+ */
2319
+ const DEFAULT_UNIQUE_KEY_PREFIX = "galaswap-operation-";
2320
+ /** A canonical UUID is 36 characters; the apply-results key limit is 128. */
2321
+ const MAX_UNIQUE_KEY_PREFIX_LENGTH = 92;
2322
+ const UNIQUE_KEY_PREFIX_PATTERN = /^[A-Za-z0-9._:-]*$/u;
2323
+ /**
2324
+ * Validates a caller-supplied `uniqueKey` prefix. The whole key (`<prefix><uuid>`) must stay a
2325
+ * valid apply-results key, otherwise `confirm()` could never read the outcome of the write.
2326
+ *
2327
+ * @throws GSwapSDKError VALIDATION_ERROR when the prefix has characters outside `A-Za-z0-9._:-` or
2328
+ * is longer than 92 characters.
2329
+ */
2330
+ function resolveUniqueKeyPrefix(prefix) {
2331
+ if (prefix === void 0) return DEFAULT_UNIQUE_KEY_PREFIX;
2332
+ if (!UNIQUE_KEY_PREFIX_PATTERN.test(prefix) || prefix.length > MAX_UNIQUE_KEY_PREFIX_LENGTH) throw new GSwapSDKError(`uniqueKeyPrefix must be at most ${MAX_UNIQUE_KEY_PREFIX_LENGTH} characters of A-Z, a-z, 0-9, ".", "_", ":" or "-".`, "VALIDATION_ERROR", { uniqueKeyPrefix: prefix });
2333
+ return prefix;
2334
+ }
2335
+ /** The swap backend's apply-results key constraint (`apply-results.dto.ts`). */
2336
+ const UNIQUE_KEY_PATTERN = /^[A-Za-z0-9._:-]{1,128}$/u;
2337
+ /**
2338
+ * Validates a caller-supplied write `uniqueKey`: 1 to 128 characters of `A-Za-z0-9._:-`, the
2339
+ * shape the apply-results route accepts, so the write's outcome can always be read back with
2340
+ * `confirm()`. A key is one attempt's identity: reuse it only to retry that same attempt.
2341
+ *
2342
+ * @throws GSwapSDKError VALIDATION_ERROR otherwise.
2343
+ */
2344
+ function assertUniqueKey(uniqueKey) {
2345
+ if (typeof uniqueKey !== "string" || !UNIQUE_KEY_PATTERN.test(uniqueKey)) throw new GSwapSDKError("uniqueKey must be 1-128 characters of A-Z, a-z, 0-9, \".\", \"_\", \":\" or \"-\".", "VALIDATION_ERROR", { uniqueKey });
2346
+ }
2347
+ /** A fresh per-attempt key: `<prefix><random UUID>`. */
2348
+ function createUniqueKey(prefix) {
2349
+ return `${prefix}${globalThis.crypto.randomUUID()}`;
2350
+ }
2351
+
2352
+ //#endregion
2353
+ //#region src/utils/dto_builders.ts
2354
+ const decimalString = (amount) => new bignumber_js.default(amount).toFixed();
2355
+ /**
2356
+ * Builds the RequestTrade DTO: `exactIn` sells that much of the input token (`sell0Qty` or
2357
+ * `sell1Qty`), `exactOut` buys that much of the output token (`buy1Qty` or `buy0Qty`), plus the
2358
+ * side's slippage bound and an optional `priceLimit`. Key order: `token0, token1, fee, uniqueKey,
2359
+ * <quantity>, amountOutMinimum | amountInMaximum, priceLimit`.
2360
+ *
2361
+ * @example
2362
+ * ```ts
2363
+ * const dto = buildRequestTradeDto({
2364
+ * token0: quote.token0Symbol, token1: quote.token1Symbol,
2365
+ * tokenInIsToken0: quote.tokenInIsToken0, fee: quote.fee,
2366
+ * uniqueKey: createUniqueKey('galaswap-operation-'),
2367
+ * exactIn: '100', amountOutMinimum: '14',
2368
+ * });
2369
+ * ```
2370
+ */
2371
+ function buildRequestTradeDto(input) {
2372
+ validateFee(input.fee);
2373
+ assertCanonicalPair(input.token0, input.token1);
2374
+ assertUniqueKey(input.uniqueKey);
2375
+ const dto = {
2376
+ token0: input.token0,
2377
+ token1: input.token1,
2378
+ fee: input.fee,
2379
+ uniqueKey: input.uniqueKey
2380
+ };
2381
+ if ("exactIn" in input) {
2382
+ const exactIn = input.exactIn;
2383
+ if (exactIn === void 0) throw new GSwapSDKError("exactIn is required for an exact-input swap.", "VALIDATION_ERROR");
2384
+ validateNumericAmount(exactIn, "exactIn");
2385
+ dto[input.tokenInIsToken0 ? "sell0Qty" : "sell1Qty"] = decimalString(exactIn);
2386
+ if (input.amountOutMinimum !== void 0) {
2387
+ validateNumericAmount(input.amountOutMinimum, "amountOutMinimum", true);
2388
+ dto["amountOutMinimum"] = decimalString(input.amountOutMinimum);
2389
+ }
2390
+ } else {
2391
+ const exactOut = input.exactOut;
2392
+ if (exactOut === void 0) throw new GSwapSDKError("exactOut is required for an exact-output swap.", "VALIDATION_ERROR");
2393
+ validateNumericAmount(exactOut, "exactOut");
2394
+ dto[input.tokenInIsToken0 ? "buy1Qty" : "buy0Qty"] = decimalString(exactOut);
2395
+ if (input.amountInMaximum !== void 0) {
2396
+ validateNumericAmount(input.amountInMaximum, "amountInMaximum", true);
2397
+ dto["amountInMaximum"] = decimalString(input.amountInMaximum);
2398
+ }
2399
+ }
2400
+ if (input.priceLimit !== void 0) {
2401
+ validateNumericAmount(input.priceLimit, "priceLimit");
2402
+ dto["priceLimit"] = decimalString(input.priceLimit);
2403
+ }
2404
+ return dto;
2405
+ }
2406
+ function assertPoolPosition(input) {
2407
+ validateFee(input.fee);
2408
+ assertCanonicalPair(input.token0, input.token1);
2409
+ assertTickRange(input.tickLower, input.tickUpper, input.fee);
2410
+ assertUniqueKey(input.uniqueKey);
2411
+ }
2412
+ /**
2413
+ * Builds the AddLiquidity DTO: exactly one of `depositQuantityToken0` / `depositQuantityToken1`
2414
+ * (the other side is derived on chain). Key order: `token0, token1, fee, tickLower, tickUpper,
2415
+ * uniqueKey, <deposit>`.
2416
+ */
2417
+ function buildAddLiquidityDto(input) {
2418
+ assertPoolPosition(input);
2419
+ const side = onlyOne(input.depositQuantityToken0, input.depositQuantityToken1, "depositQuantityToken0", "depositQuantityToken1");
2420
+ if (side === void 0) throw new GSwapSDKError("Provide exactly one of depositQuantityToken0 or depositQuantityToken1.", "VALIDATION_ERROR");
2421
+ validateNumericAmount(side.amount, side.name);
2422
+ return {
2423
+ token0: input.token0,
2424
+ token1: input.token1,
2425
+ fee: input.fee,
2426
+ tickLower: input.tickLower,
2427
+ tickUpper: input.tickUpper,
2428
+ uniqueKey: input.uniqueKey,
2429
+ [side.name]: decimalString(side.amount)
2430
+ };
2431
+ }
2432
+ /**
2433
+ * Builds the RemoveLiquidity DTO: at most one of `withdrawalQuantityToken0` /
2434
+ * `withdrawalQuantityToken1`; omitting both closes the position and sweeps its fees.
2435
+ */
2436
+ function buildRemoveLiquidityDto(input) {
2437
+ assertPoolPosition(input);
2438
+ const side = onlyOne(input.withdrawalQuantityToken0, input.withdrawalQuantityToken1, "withdrawalQuantityToken0", "withdrawalQuantityToken1", true);
2439
+ if (side !== void 0) validateNumericAmount(side.amount, side.name);
2440
+ return {
2441
+ token0: input.token0,
2442
+ token1: input.token1,
2443
+ fee: input.fee,
2444
+ tickLower: input.tickLower,
2445
+ tickUpper: input.tickUpper,
2446
+ uniqueKey: input.uniqueKey,
2447
+ ...side === void 0 ? {} : { [side.name]: decimalString(side.amount) }
2448
+ };
2449
+ }
2450
+ /** Builds the CollectPositionFees DTO (the contract sweeps all of a position's fees). */
2451
+ function buildCollectPositionFeesDto(input) {
2452
+ assertPoolPosition(input);
2453
+ return {
2454
+ token0: input.token0,
2455
+ token1: input.token1,
2456
+ fee: input.fee,
2457
+ tickLower: input.tickLower,
2458
+ tickUpper: input.tickUpper,
2459
+ uniqueKey: input.uniqueKey
2460
+ };
2461
+ }
2462
+ /**
2463
+ * Builds the CreatePool DTO. Key order: `token0Key, token1Key, fee, startingPrice |
2464
+ * startingSqrtPrice, token0Symbol, token1Symbol, uniqueKey, name, isPrivate, privateAccess`.
2465
+ * Prices are those of the canonical pair (token1 per token0); a price quoted the other way round
2466
+ * is inverted by the caller before it gets here.
2467
+ */
2468
+ function buildCreatePoolDto(input) {
2469
+ validateFee(input.fee);
2470
+ assertUniqueKey(input.uniqueKey);
2471
+ assertCanonicalPair(input.token0Symbol, input.token1Symbol);
2472
+ for (const symbol of [input.token0Symbol, input.token1Symbol]) if (!isProposableTradingSymbol(symbol)) throw new GSwapSDKError("Invalid trading symbol: must be 1-32 ASCII letters and digits and not a reserved word (Token, Unit, none).", "VALIDATION_ERROR", { symbol });
2473
+ for (const key of [input.token0Key, input.token1Key]) assertClassKey(key);
2474
+ const price = onlyOne(input.startingPrice, input.startingSqrtPrice, "startingPrice", "startingSqrtPrice");
2475
+ if (price === void 0) throw new GSwapSDKError("Provide exactly one of startingPrice or startingSqrtPrice.", "VALIDATION_ERROR");
2476
+ if (typeof price.amount === "number") throw new GSwapSDKError(`Invalid ${price.name}: use a decimal string or BigNumber, not a JavaScript number`, "VALIDATION_ERROR");
2477
+ const parsed = new bignumber_js.default(price.amount);
2478
+ if (!parsed.isFinite() || parsed.isLessThanOrEqualTo(0)) throw new GSwapSDKError("Starting price must be finite and positive.", "VALIDATION_ERROR");
2479
+ return {
2480
+ token0Key: input.token0Key,
2481
+ token1Key: input.token1Key,
2482
+ fee: input.fee,
2483
+ [price.name]: parsed.toFixed(),
2484
+ token0Symbol: input.token0Symbol,
2485
+ token1Symbol: input.token1Symbol,
2486
+ uniqueKey: input.uniqueKey,
2487
+ ...input.name === void 0 || input.name === "" ? {} : { name: input.name },
2488
+ ...input.isPrivate === void 0 ? {} : { isPrivate: input.isPrivate },
2489
+ ...input.privateAccess === void 0 ? {} : { privateAccess: input.privateAccess }
2490
+ };
2491
+ }
2492
+ function assertClassKey(key) {
2493
+ for (const part of [
2494
+ "collection",
2495
+ "category",
2496
+ "type",
2497
+ "additionalKey"
2498
+ ]) if (typeof key[part] !== "string" || key[part].length === 0) throw new GSwapSDKError(`Token class key is missing ${part}.`, "VALIDATION_ERROR", { key });
2499
+ }
2500
+ /**
2501
+ * Of two mutually exclusive fields, the one that is set (`undefined` when neither is);
2502
+ * both set is a `VALIDATION_ERROR`, and so is neither unless `allowNeither`.
2503
+ */
2504
+ function onlyOne(first, second, firstName, secondName, allowNeither = false) {
2505
+ if (first !== void 0 && second !== void 0) throw new GSwapSDKError(allowNeither ? `Provide at most one of ${firstName} or ${secondName}.` : `Provide exactly one of ${firstName} or ${secondName}.`, "VALIDATION_ERROR");
2506
+ if (first !== void 0) return {
2507
+ name: firstName,
2508
+ amount: first
2509
+ };
2510
+ if (second !== void 0) return {
2511
+ name: secondName,
2512
+ amount: second
2513
+ };
2514
+ }
2515
+ function invalidDto(method, field, expected) {
2516
+ return new GSwapSDKError(`${method} DTO field ${field} must be ${expected}.`, "VALIDATION_ERROR", {
2517
+ type: "INVALID_DTO",
2518
+ method,
2519
+ field
2520
+ });
2521
+ }
2522
+ /**
2523
+ * Rebuilds a DTO from its own fields with the builder of its method: the canonical form of the
2524
+ * DTO, with the same validation. Key order and decimal text are the builder's.
2525
+ */
2526
+ function rebuildV2Dto(method, dto) {
2527
+ const text = (field) => {
2528
+ const value = dto[field];
2529
+ if (typeof value !== "string") throw invalidDto(method, field, "a string");
2530
+ return value;
2531
+ };
2532
+ const integer = (field) => {
2533
+ const value = dto[field];
2534
+ if (typeof value !== "number") throw invalidDto(method, field, "a number");
2535
+ return value;
2536
+ };
2537
+ const optionalText = (field) => dto[field] === void 0 ? void 0 : text(field);
2538
+ const classKey = (field) => {
2539
+ const value = dto[field];
2540
+ if (typeof value !== "object" || value === null) throw invalidDto(method, field, "a class key");
2541
+ const key = value;
2542
+ const part = (name) => {
2543
+ if (typeof key[name] !== "string") throw invalidDto(method, `${field}.${name}`, "a string");
2544
+ return key[name];
2545
+ };
2546
+ return {
2547
+ collection: part("collection"),
2548
+ category: part("category"),
2549
+ type: part("type"),
2550
+ additionalKey: part("additionalKey")
2551
+ };
2552
+ };
2553
+ const base = () => ({
2554
+ token0: text("token0"),
2555
+ token1: text("token1"),
2556
+ fee: integer("fee")
2557
+ });
2558
+ const uniqueKey = text("uniqueKey");
2559
+ const position = () => ({
2560
+ ...base(),
2561
+ uniqueKey,
2562
+ tickLower: integer("tickLower"),
2563
+ tickUpper: integer("tickUpper")
2564
+ });
2565
+ switch (method) {
2566
+ case "RequestTrade": {
2567
+ const qty = [
2568
+ "sell0Qty",
2569
+ "sell1Qty",
2570
+ "buy0Qty",
2571
+ "buy1Qty"
2572
+ ].filter((f) => dto[f] !== void 0);
2573
+ const [field] = qty;
2574
+ if (qty.length !== 1 || field === void 0) throw invalidDto(method, "sell0Qty|sell1Qty|buy0Qty|buy1Qty", "present exactly once");
2575
+ const tokenInIsToken0 = field === "sell0Qty" || field === "buy1Qty";
2576
+ const shared = {
2577
+ ...base(),
2578
+ uniqueKey,
2579
+ tokenInIsToken0
2580
+ };
2581
+ const priceLimit = optionalText("priceLimit");
2582
+ return field.startsWith("sell") ? buildRequestTradeDto({
2583
+ ...shared,
2584
+ exactIn: text(field),
2585
+ amountOutMinimum: optionalText("amountOutMinimum"),
2586
+ priceLimit
2587
+ }) : buildRequestTradeDto({
2588
+ ...shared,
2589
+ exactOut: text(field),
2590
+ amountInMaximum: optionalText("amountInMaximum"),
2591
+ priceLimit
2592
+ });
2593
+ }
2594
+ case "AddLiquidity": {
2595
+ const token0Side = optionalText("depositQuantityToken0");
2596
+ const token1Side = optionalText("depositQuantityToken1");
2597
+ return buildAddLiquidityDto({
2598
+ ...position(),
2599
+ ...token0Side === void 0 ? { depositQuantityToken1: token1Side } : { depositQuantityToken0: token0Side }
2600
+ });
2601
+ }
2602
+ case "RemoveLiquidity": return buildRemoveLiquidityDto({
2603
+ ...position(),
2604
+ withdrawalQuantityToken0: optionalText("withdrawalQuantityToken0"),
2605
+ withdrawalQuantityToken1: optionalText("withdrawalQuantityToken1")
2606
+ });
2607
+ case "CollectPositionFees": return buildCollectPositionFeesDto(position());
2608
+ case "CreatePool": {
2609
+ const price = optionalText("startingPrice");
2610
+ const sqrtPrice = optionalText("startingSqrtPrice");
2611
+ const privateAccess = dto["privateAccess"];
2612
+ if (privateAccess !== void 0 && (!Array.isArray(privateAccess) || privateAccess.some((entry) => typeof entry !== "string"))) throw invalidDto(method, "privateAccess", "an array of strings");
2613
+ if (dto["isPrivate"] !== void 0 && typeof dto["isPrivate"] !== "boolean") throw invalidDto(method, "isPrivate", "a boolean");
2614
+ return buildCreatePoolDto({
2615
+ token0Key: classKey("token0Key"),
2616
+ token1Key: classKey("token1Key"),
2617
+ token0Symbol: text("token0Symbol"),
2618
+ token1Symbol: text("token1Symbol"),
2619
+ fee: integer("fee"),
2620
+ uniqueKey,
2621
+ name: optionalText("name"),
2622
+ isPrivate: dto["isPrivate"],
2623
+ privateAccess,
2624
+ ...price === void 0 ? { startingSqrtPrice: sqrtPrice } : { startingPrice: price }
2625
+ });
2626
+ }
2627
+ }
1433
2628
  }
1434
- function isPoolListInfo(value) {
1435
- if (typeof value !== "object" || value === null) return false;
1436
- const row = value;
1437
- return row["contractVersion"] === "v2" && typeof row["token0"] === "string" && typeof row["token1"] === "string" && typeof row["poolName"] === "string" && typeof row["poolRef"] === "string" && [
1438
- "fee",
1439
- "tickSpacing",
1440
- "protocolFees",
1441
- "tradingFees"
1442
- ].every((key) => isFiniteNumber(row[key])) && [
1443
- "token0CompositeKey",
1444
- "token1CompositeKey",
1445
- "creator",
1446
- "price",
1447
- "sqrtPrice",
1448
- "token0Tvl",
1449
- "token1Tvl"
1450
- ].every((key) => isOptionalString(row[key])) && [
1451
- "tick",
1452
- "tvlUsd",
1453
- "volume1d",
1454
- "volume7d",
1455
- "volume30d",
1456
- "trades1d",
1457
- "fee24h",
1458
- "apr1d",
1459
- "token0Price",
1460
- "token1Price"
1461
- ].every((key) => isOptionalNumber(row[key]));
1462
- }
1463
- function isFiniteNumber(value) {
1464
- return typeof value === "number" && Number.isFinite(value);
1465
- }
1466
- function isOptionalString(value) {
1467
- return value === void 0 || typeof value === "string";
1468
- }
1469
- function isOptionalNumber(value) {
1470
- return value === void 0 || isFiniteNumber(value);
2629
+ /**
2630
+ * Asserts that `dto` is EXACTLY what the builder of `method` produces for its own fields — the
2631
+ * same fields, canonical decimal text, builder key order, nothing extra (no `signature`). The
2632
+ * `submit*` methods use it so that what an app showed a user (a DTO it built) is byte for byte
2633
+ * what gets signed; a DTO edited by hand, or built elsewhere, is refused rather than signed.
2634
+ *
2635
+ * @throws GSwapSDKError VALIDATION_ERROR (`details.type` = `INVALID_DTO` or `DTO_NOT_CANONICAL`).
2636
+ */
2637
+ function assertCanonicalV2Dto(method, dto) {
2638
+ const rebuilt = rebuildV2Dto(method, dto);
2639
+ if (JSON.stringify(rebuilt) !== JSON.stringify(dto)) throw new GSwapSDKError(`The ${method} DTO is not in the form the SDK builders produce; build it with the matching build function and submit that.`, "VALIDATION_ERROR", {
2640
+ type: "DTO_NOT_CANONICAL",
2641
+ method
2642
+ });
1471
2643
  }
1472
2644
 
1473
2645
  //#endregion
1474
2646
  //#region src/classes/positions.ts
2647
+ /** The pool position a liquidity DTO names, as its confirmation needs it. */
2648
+ function identityOf(dto) {
2649
+ return {
2650
+ token0: dto.token0,
2651
+ token1: dto.token1,
2652
+ fee: dto.fee,
2653
+ tickLower: dto.tickLower,
2654
+ tickUpper: dto.tickUpper
2655
+ };
2656
+ }
1475
2657
  /**
1476
2658
  * Manages current-contract concentrated-liquidity positions.
1477
2659
  *
@@ -1487,26 +2669,32 @@ var Positions = class {
1487
2669
  symbols;
1488
2670
  signer;
1489
2671
  walletAddress;
1490
- constructor(gateway, symbols, signer, walletAddress) {
2672
+ constructor(gateway, symbols, signer, walletAddress, options = {}) {
1491
2673
  this.gateway = gateway;
1492
2674
  this.symbols = symbols;
1493
2675
  this.signer = signer;
1494
2676
  this.walletAddress = walletAddress;
1495
- this.http = new HttpClient(gateway.httpRequestor, gateway.requestTimeoutMs ?? 3e4);
2677
+ this.http = new HttpClient(gateway.httpRequestor, gateway.requestTimeoutMs ?? 3e4, gateway.wire);
2678
+ this.uniqueKeyPrefix = resolveUniqueKeyPrefix(options.uniqueKeyPrefix);
2679
+ this.onInvalidEntry = options.onInvalidEntry;
1496
2680
  }
1497
2681
  http;
2682
+ uniqueKeyPrefix;
2683
+ onInvalidEntry;
1498
2684
  /**
1499
2685
  * Gets all current-contract positions owned by an address.
1500
2686
  *
1501
2687
  * @param owner - GalaChain owner identity.
1502
- * @returns Enriched positions from the v2 trade backend.
2688
+ * @returns Enriched positions from the v2 trade backend, token images included. A row that is
2689
+ * not a usable position is skipped and reported to `onInvalidEntry`.
1503
2690
  * @example
1504
2691
  * ```typescript
1505
2692
  * const positions = await gSwap.positions.getUserPositions('client|012345678901234567890123');
1506
2693
  * ```
1507
2694
  */
1508
2695
  async getUserPositions(owner) {
1509
- return (await this.get("/positions", { user: owner })).data.map((position) => this.mapPosition(position));
2696
+ const response = await this.get("/positions", { user: owner });
2697
+ return readEntries("positions", response.data, parsePosition, this.onInvalidEntry);
1510
2698
  }
1511
2699
  /**
1512
2700
  * Gets one position, normalizing a caller-supplied reversed pair to canonical symbol order.
@@ -1523,17 +2711,65 @@ var Positions = class {
1523
2711
  * ```
1524
2712
  */
1525
2713
  async getPosition(args, signal) {
2714
+ const response = await this.getNullable("/position", await this.positionParams(args), signal);
2715
+ return response === null ? null : readPositionBody(response.data);
2716
+ }
2717
+ /**
2718
+ * Reads one position and classifies the outcome instead of throwing: `found` with the position
2719
+ * (fees included), `not_found` (the backend's HTTP 404, which it sends only when the chain
2720
+ * itself has no such position) or `unknown` (any other failure, carried as `error`). Use it
2721
+ * to confirm a write: `unknown` is "no evidence either way", never a confirmed absence.
2722
+ *
2723
+ * Only the caller's own abort rethrows. With a pass-through resolver ({@link CanonicalSymbols})
2724
+ * the pair and ticks go to the backend exactly as given and the backend orients them.
2725
+ *
2726
+ * @example
2727
+ * ```typescript
2728
+ * const read = await gSwap.positions.readPosition({
2729
+ * token0: 'GALA', token1: 'GUSDC', owner: 'client|012345678901234567890123',
2730
+ * fee: 3000, tickLower: -19200, tickUpper: 12000,
2731
+ * });
2732
+ * if (read.kind === 'found') console.log(read.data.fees0);
2733
+ * ```
2734
+ */
2735
+ async readPosition(args, signal) {
2736
+ try {
2737
+ const position = await this.getPosition(args, signal);
2738
+ return position === null ? { kind: "not_found" } : {
2739
+ kind: "found",
2740
+ data: position
2741
+ };
2742
+ } catch (error) {
2743
+ if ((signal === null || signal === void 0 ? void 0 : signal.aborted) === true) throw error;
2744
+ return {
2745
+ kind: "unknown",
2746
+ error: error instanceof GSwapSDKError ? error : new GSwapSDKError("Position request failed.", "HTTP_ERROR", { cause: error })
2747
+ };
2748
+ }
2749
+ }
2750
+ /**
2751
+ * The `/position` query: canonical symbols and canonical ticks after a client-side flip, or,
2752
+ * for a pass-through resolver, the caller's references and ticks untouched.
2753
+ */
2754
+ async positionParams(args) {
2755
+ if (this.symbols.passthrough === true) return {
2756
+ token0: rawTokenOf(args.token0),
2757
+ token1: rawTokenOf(args.token1),
2758
+ fee: `${args.fee}`,
2759
+ owner: args.owner,
2760
+ tickLower: `${args.tickLower}`,
2761
+ tickUpper: `${args.tickUpper}`
2762
+ };
1526
2763
  const pair = await this.symbols.orderPair(args.token0, args.token1);
1527
2764
  const ticks = this.canonicalTicks(args.tickLower, args.tickUpper, pair.flipped);
1528
- const response = await this.getNullable("/position", {
2765
+ return {
1529
2766
  token0: pair.token0.symbol,
1530
2767
  token1: pair.token1.symbol,
1531
2768
  fee: `${args.fee}`,
1532
2769
  owner: args.owner,
1533
2770
  tickLower: `${ticks.tickLower}`,
1534
2771
  tickUpper: `${ticks.tickUpper}`
1535
- }, signal);
1536
- return response === null ? null : this.mapPosition(response.data);
2772
+ };
1537
2773
  }
1538
2774
  /**
1539
2775
  * Estimates the other token amount and resulting liquidity for a deposit.
@@ -1551,8 +2787,8 @@ var Positions = class {
1551
2787
  async estimateAddLiquidity(args) {
1552
2788
  validateNumericAmount(args.amount, "amount");
1553
2789
  return (await this.get("/add-liq-estimate", {
1554
- token0: this.requestToken(args.token0),
1555
- token1: this.requestToken(args.token1),
2790
+ token0: rawTokenOf(args.token0),
2791
+ token1: rawTokenOf(args.token1),
1556
2792
  fee: `${args.fee}`,
1557
2793
  tickLower: `${args.tickLower}`,
1558
2794
  tickUpper: `${args.tickUpper}`,
@@ -1576,8 +2812,8 @@ var Positions = class {
1576
2812
  async estimateRemoveLiquidity(args) {
1577
2813
  validateNumericAmount(args.liquidity, "liquidity");
1578
2814
  return (await this.get("/remove-liq-estimate", {
1579
- token0: this.requestToken(args.token0),
1580
- token1: this.requestToken(args.token1),
2815
+ token0: rawTokenOf(args.token0),
2816
+ token1: rawTokenOf(args.token1),
1581
2817
  fee: `${args.fee}`,
1582
2818
  tickLower: `${args.tickLower}`,
1583
2819
  tickUpper: `${args.tickUpper}`,
@@ -1599,30 +2835,54 @@ var Positions = class {
1599
2835
  * ```
1600
2836
  */
1601
2837
  async addLiquidityByTicks(args) {
2838
+ return this.submitAddLiquidity(await this.buildAddLiquidity(args));
2839
+ }
2840
+ /**
2841
+ * Builds the AddLiquidity DTO that {@link addLiquidityByTicks} would sign, without signing or
2842
+ * sending it: to show in an approval popup, or to sign elsewhere. Submit exactly this DTO with
2843
+ * {@link submitAddLiquidity}. The pure counterpart, with canonical inputs and no resolution,
2844
+ * is `buildAddLiquidityDto`.
2845
+ *
2846
+ * @example
2847
+ * ```typescript
2848
+ * const dto = await gSwap.positions.buildAddLiquidity({
2849
+ * token0: 'GALA', token1: 'GUSDC', fee: 3000,
2850
+ * tickLower: -19200, tickUpper: 12000, amount: '100', amountIsToken0: true,
2851
+ * });
2852
+ * ```
2853
+ */
2854
+ async buildAddLiquidity(args) {
1602
2855
  validateFee(args.fee);
1603
2856
  const pair = await this.symbols.orderPair(args.token0, args.token1);
1604
2857
  const ticks = this.canonicalTicks(args.tickLower, args.tickUpper, pair.flipped);
1605
2858
  assertTickRange(ticks.tickLower, ticks.tickUpper, args.fee);
1606
2859
  validateNumericAmount(args.amount, "amount");
1607
- const amount = new bignumber_js.default(args.amount).toFixed();
1608
- const dto = {
2860
+ const base = {
1609
2861
  token0: pair.token0.symbol,
1610
2862
  token1: pair.token1.symbol,
1611
2863
  fee: args.fee,
1612
2864
  tickLower: ticks.tickLower,
1613
2865
  tickUpper: ticks.tickUpper,
1614
- uniqueKey: this.uniqueKey(),
1615
- ...pair.flipped !== args.amountIsToken0 ? { depositQuantityToken0: amount } : { depositQuantityToken1: amount }
2866
+ uniqueKey: this.uniqueKeyFor(args.uniqueKey)
1616
2867
  };
2868
+ return pair.flipped !== args.amountIsToken0 ? buildAddLiquidityDto({
2869
+ ...base,
2870
+ depositQuantityToken0: args.amount
2871
+ }) : buildAddLiquidityDto({
2872
+ ...base,
2873
+ depositQuantityToken1: args.amount
2874
+ });
2875
+ }
2876
+ /**
2877
+ * Signs and submits a DTO built by {@link buildAddLiquidity} or `buildAddLiquidityDto`; it must
2878
+ * be exactly what the builder produces. The returned transaction confirms by proving the
2879
+ * position's liquidity grew.
2880
+ */
2881
+ async submitAddLiquidity(dto) {
2882
+ assertCanonicalV2Dto("AddLiquidity", dto);
1617
2883
  return this.submit("AddLiquidity", dto, {
1618
2884
  kind: "add",
1619
- identity: {
1620
- token0: pair.token0.symbol,
1621
- token1: pair.token1.symbol,
1622
- fee: args.fee,
1623
- tickLower: ticks.tickLower,
1624
- tickUpper: ticks.tickUpper
1625
- }
2885
+ identity: identityOf(dto)
1626
2886
  });
1627
2887
  }
1628
2888
  /**
@@ -1657,7 +2917,8 @@ var Positions = class {
1657
2917
  tickLower,
1658
2918
  tickUpper,
1659
2919
  amount: args.amount,
1660
- amountIsToken0: args.amountIsToken0
2920
+ amountIsToken0: args.amountIsToken0,
2921
+ uniqueKey: args.uniqueKey
1661
2922
  });
1662
2923
  }
1663
2924
  /**
@@ -1675,6 +2936,14 @@ var Positions = class {
1675
2936
  * ```
1676
2937
  */
1677
2938
  async removeLiquidity(args) {
2939
+ return this.submitRemoveLiquidity(await this.buildRemoveLiquidity(args));
2940
+ }
2941
+ /**
2942
+ * Builds the RemoveLiquidity DTO that {@link removeLiquidity} would sign, without signing or
2943
+ * sending it. Submit exactly this DTO with {@link submitRemoveLiquidity}. The pure counterpart
2944
+ * is `buildRemoveLiquidityDto`.
2945
+ */
2946
+ async buildRemoveLiquidity(args) {
1678
2947
  validateFee(args.fee);
1679
2948
  const pair = await this.symbols.orderPair(args.token0, args.token1);
1680
2949
  const ticks = this.canonicalTicks(args.tickLower, args.tickUpper, pair.flipped);
@@ -1682,26 +2951,29 @@ var Positions = class {
1682
2951
  if (args.amount0 !== void 0 && args.amount1 !== void 0) throw new GSwapSDKError("Provide at most one of amount0 or amount1.", "VALIDATION_ERROR");
1683
2952
  if (args.amount0 !== void 0) validateNumericAmount(args.amount0, "amount0");
1684
2953
  if (args.amount1 !== void 0) validateNumericAmount(args.amount1, "amount1");
1685
- const withdrawal = args.amount0 !== void 0 ? pair.flipped ? { withdrawalQuantityToken1: new bignumber_js.default(args.amount0).toFixed() } : { withdrawalQuantityToken0: new bignumber_js.default(args.amount0).toFixed() } : args.amount1 !== void 0 ? pair.flipped ? { withdrawalQuantityToken0: new bignumber_js.default(args.amount1).toFixed() } : { withdrawalQuantityToken1: new bignumber_js.default(args.amount1).toFixed() } : {};
1686
- const dto = {
2954
+ const [canonical0, canonical1] = pair.flipped ? [args.amount1, args.amount0] : [args.amount0, args.amount1];
2955
+ return buildRemoveLiquidityDto({
1687
2956
  token0: pair.token0.symbol,
1688
2957
  token1: pair.token1.symbol,
1689
2958
  fee: args.fee,
1690
2959
  tickLower: ticks.tickLower,
1691
2960
  tickUpper: ticks.tickUpper,
1692
- uniqueKey: this.uniqueKey(),
1693
- ...withdrawal
1694
- };
1695
- const isFullClose = args.amount0 === void 0 && args.amount1 === void 0;
2961
+ uniqueKey: this.uniqueKeyFor(args.uniqueKey),
2962
+ withdrawalQuantityToken0: canonical0,
2963
+ withdrawalQuantityToken1: canonical1
2964
+ });
2965
+ }
2966
+ /**
2967
+ * Signs and submits a DTO built by {@link buildRemoveLiquidity} or `buildRemoveLiquidityDto`;
2968
+ * it must be exactly what the builder produces. A DTO with no withdrawal quantity closes the
2969
+ * position, and the returned transaction then confirms by proving the position is gone.
2970
+ */
2971
+ async submitRemoveLiquidity(dto) {
2972
+ assertCanonicalV2Dto("RemoveLiquidity", dto);
2973
+ const isFullClose = dto.withdrawalQuantityToken0 === void 0 && dto.withdrawalQuantityToken1 === void 0;
1696
2974
  return this.submit("RemoveLiquidity", dto, {
1697
2975
  kind: isFullClose ? "removeFull" : "removePartial",
1698
- identity: {
1699
- token0: pair.token0.symbol,
1700
- token1: pair.token1.symbol,
1701
- fee: args.fee,
1702
- tickLower: ticks.tickLower,
1703
- tickUpper: ticks.tickUpper
1704
- }
2976
+ identity: identityOf(dto)
1705
2977
  });
1706
2978
  }
1707
2979
  /**
@@ -1718,27 +2990,36 @@ var Positions = class {
1718
2990
  * ```
1719
2991
  */
1720
2992
  async collectPositionFees(args) {
2993
+ return this.submitCollectPositionFees(await this.buildCollectPositionFees(args));
2994
+ }
2995
+ /**
2996
+ * Builds the CollectPositionFees DTO that {@link collectPositionFees} would sign, without
2997
+ * signing or sending it. Submit exactly this DTO with {@link submitCollectPositionFees}. The
2998
+ * pure counterpart is `buildCollectPositionFeesDto`.
2999
+ */
3000
+ async buildCollectPositionFees(args) {
1721
3001
  validateFee(args.fee);
1722
3002
  const pair = await this.symbols.orderPair(args.token0, args.token1);
1723
3003
  const ticks = this.canonicalTicks(args.tickLower, args.tickUpper, pair.flipped);
1724
3004
  assertTickRange(ticks.tickLower, ticks.tickUpper, args.fee);
1725
- const dto = {
3005
+ return buildCollectPositionFeesDto({
1726
3006
  token0: pair.token0.symbol,
1727
3007
  token1: pair.token1.symbol,
1728
3008
  fee: args.fee,
1729
3009
  tickLower: ticks.tickLower,
1730
3010
  tickUpper: ticks.tickUpper,
1731
- uniqueKey: this.uniqueKey()
1732
- };
3011
+ uniqueKey: this.uniqueKeyFor(args.uniqueKey)
3012
+ });
3013
+ }
3014
+ /**
3015
+ * Signs and submits a DTO built by {@link buildCollectPositionFees} or
3016
+ * `buildCollectPositionFeesDto`; it must be exactly what the builder produces.
3017
+ */
3018
+ async submitCollectPositionFees(dto) {
3019
+ assertCanonicalV2Dto("CollectPositionFees", dto);
1733
3020
  return this.submit("CollectPositionFees", dto, {
1734
3021
  kind: "collect",
1735
- identity: {
1736
- token0: pair.token0.symbol,
1737
- token1: pair.token1.symbol,
1738
- fee: args.fee,
1739
- tickLower: ticks.tickLower,
1740
- tickUpper: ticks.tickUpper
1741
- }
3022
+ identity: identityOf(dto)
1742
3023
  });
1743
3024
  }
1744
3025
  /**
@@ -1763,7 +3044,15 @@ var Positions = class {
1763
3044
  * ```
1764
3045
  */
1765
3046
  async createPool(args) {
1766
- var _this$symbols$invalid, _this$symbols;
3047
+ return this.submitCreatePool(await this.buildCreatePool(args));
3048
+ }
3049
+ /**
3050
+ * Builds the CreatePool DTO that {@link createPool} would sign, without signing or sending it:
3051
+ * symbols resolved or proposed, the pair put in canonical order and the price inverted when it
3052
+ * was reversed. Submit exactly this DTO with {@link submitCreatePool}. The pure counterpart,
3053
+ * with canonical inputs, is `buildCreatePoolDto`.
3054
+ */
3055
+ async buildCreatePool(args) {
1767
3056
  const hasPrice = args.startingPrice !== void 0;
1768
3057
  if (hasPrice === (args.startingSqrtPrice !== void 0)) throw new GSwapSDKError("Provide exactly one of startingPrice or startingSqrtPrice.", "VALIDATION_ERROR");
1769
3058
  validateFee(args.fee);
@@ -1775,22 +3064,38 @@ var Positions = class {
1775
3064
  const sourcePrice = new bignumber_js.default(sourcePriceInput);
1776
3065
  if (!sourcePrice.isFinite() || sourcePrice.isLessThanOrEqualTo(0)) throw new GSwapSDKError("Starting price must be finite and positive.", "VALIDATION_ERROR");
1777
3066
  const canonicalPrice = flipped ? new bignumber_js.default(1).dividedBy(sourcePrice) : sourcePrice;
1778
- const dto = {
3067
+ const shared = {
1779
3068
  token0Key: token0.classKey,
1780
3069
  token1Key: token1.classKey,
1781
3070
  token0Symbol: token0.symbol,
1782
3071
  token1Symbol: token1.symbol,
1783
3072
  fee: args.fee,
1784
- uniqueKey: this.uniqueKey(),
1785
- ...hasPrice ? { startingPrice: canonicalPrice.toFixed() } : { startingSqrtPrice: canonicalPrice.toFixed() },
1786
- ...args.isPrivate === void 0 ? {} : { isPrivate: args.isPrivate },
1787
- ...args.privateAccess === void 0 ? {} : { privateAccess: args.privateAccess }
3073
+ uniqueKey: this.uniqueKeyFor(args.uniqueKey),
3074
+ name: args.name,
3075
+ isPrivate: args.isPrivate,
3076
+ privateAccess: args.privateAccess
1788
3077
  };
3078
+ return hasPrice ? buildCreatePoolDto({
3079
+ ...shared,
3080
+ startingPrice: canonicalPrice
3081
+ }) : buildCreatePoolDto({
3082
+ ...shared,
3083
+ startingSqrtPrice: canonicalPrice
3084
+ });
3085
+ }
3086
+ /**
3087
+ * Signs and submits a DTO built by {@link buildCreatePool} or `buildCreatePoolDto`; it must be
3088
+ * exactly what the builder produces. The returned transaction confirms by the pool existing, and
3089
+ * the cached symbol registry is dropped, since the pool may have registered new symbols.
3090
+ */
3091
+ async submitCreatePool(dto) {
3092
+ var _this$symbols$invalid, _this$symbols;
3093
+ assertCanonicalV2Dto("CreatePool", dto);
1789
3094
  const transaction = await this.submit("CreatePool", dto, {
1790
3095
  kind: "createPool",
1791
- token0: token0.symbol,
1792
- token1: token1.symbol,
1793
- fee: args.fee
3096
+ token0: dto.token0Symbol,
3097
+ token1: dto.token1Symbol,
3098
+ fee: dto.fee
1794
3099
  });
1795
3100
  (_this$symbols$invalid = (_this$symbols = this.symbols).invalidate) === null || _this$symbols$invalid === void 0 || _this$symbols$invalid.call(_this$symbols);
1796
3101
  return transaction;
@@ -1896,30 +3201,6 @@ var Positions = class {
1896
3201
  if (!(error instanceof GSwapSDKError) || error.details === void 0) return false;
1897
3202
  return error.details["status"] === 404;
1898
3203
  }
1899
- mapPosition(position) {
1900
- return {
1901
- token0Symbol: position.token0Symbol,
1902
- token1Symbol: position.token1Symbol,
1903
- ...position.token0CompositeKey === void 0 ? {} : { token0CompositeKey: position.token0CompositeKey },
1904
- ...position.token1CompositeKey === void 0 ? {} : { token1CompositeKey: position.token1CompositeKey },
1905
- fee: position.fee,
1906
- tickLower: position.tickLower,
1907
- tickUpper: position.tickUpper,
1908
- liquidity: position.liquidity,
1909
- amount0: position.amount0,
1910
- amount1: position.amount1,
1911
- inRange: position.inRange,
1912
- ...position.currentTick === void 0 ? {} : { currentTick: position.currentTick },
1913
- ...position.sqrtPrice === void 0 ? {} : { sqrtPrice: position.sqrtPrice },
1914
- pool: position.poolRef ?? position.pool ?? "",
1915
- owner: position.owner,
1916
- ...position.fees0 === void 0 ? {} : { fees0: position.fees0 },
1917
- ...position.fees1 === void 0 ? {} : { fees1: position.fees1 }
1918
- };
1919
- }
1920
- requestToken(ref) {
1921
- return typeof ref === "string" ? ref : compositeKeyOf(ref);
1922
- }
1923
3204
  canonicalTicks(tickLower, tickUpper, flipped) {
1924
3205
  return flipped ? {
1925
3206
  tickLower: -tickUpper,
@@ -1937,8 +3218,11 @@ var Positions = class {
1937
3218
  rejectNumber(value, parameterName) {
1938
3219
  if (typeof value === "number") throw new GSwapSDKError(`Invalid ${parameterName}: use a decimal string or BigNumber, not a JavaScript number`, "VALIDATION_ERROR");
1939
3220
  }
1940
- uniqueKey() {
1941
- return `gswap-sdk-${globalThis.crypto.randomUUID()}`;
3221
+ /** The caller's `uniqueKey` (validated), else a fresh one. */
3222
+ uniqueKeyFor(supplied) {
3223
+ if (supplied === void 0) return createUniqueKey(this.uniqueKeyPrefix);
3224
+ assertUniqueKey(supplied);
3225
+ return supplied;
1942
3226
  }
1943
3227
  async resolveCreateToken(ref, symbolOverride) {
1944
3228
  try {
@@ -1950,6 +3234,7 @@ var Positions = class {
1950
3234
  registeredSymbol: resolved.symbol
1951
3235
  });
1952
3236
  const suppliedClassKey = this.tryParseClassKey(ref);
3237
+ if (suppliedClassKey === void 0 && resolved.collection === "") throw new GSwapSDKError("createPool needs each token as a class key; a bare trading symbol does not carry one.", "VALIDATION_ERROR", { token: ref });
1953
3238
  return {
1954
3239
  symbol: resolved.symbol,
1955
3240
  classKey: suppliedClassKey ?? parseTokenClassKey({
@@ -1968,7 +3253,7 @@ var Positions = class {
1968
3253
  throw error;
1969
3254
  }
1970
3255
  if (symbolOverride === void 0 || symbolOverride === "") throw new GSwapSDKError("Token is not registered in the trading-symbol registry yet; pass token0Symbol/token1Symbol (the token class symbol) to propose one. A symbol cannot be derived from a class key.", "VALIDATION_ERROR", { token: ref });
1971
- if (!/^[A-Za-z0-9]+$/u.test(symbolOverride)) throw new GSwapSDKError("Invalid trading symbol: must be ASCII letters and digits only.", "VALIDATION_ERROR", { symbol: symbolOverride });
3256
+ if (!isProposableTradingSymbol(symbolOverride)) throw new GSwapSDKError("Invalid trading symbol: must be 1-32 ASCII letters and digits and not a reserved word (Token, Unit, none).", "VALIDATION_ERROR", { symbol: symbolOverride });
1972
3257
  return {
1973
3258
  symbol: symbolOverride,
1974
3259
  classKey
@@ -1983,27 +3268,94 @@ var Positions = class {
1983
3268
  }
1984
3269
  }
1985
3270
  };
3271
+ const REQUIRED_POSITION_STRINGS = [
3272
+ "token0Symbol",
3273
+ "token1Symbol",
3274
+ "owner",
3275
+ "liquidity",
3276
+ "amount0",
3277
+ "amount1"
3278
+ ];
3279
+ const REQUIRED_POSITION_NUMBERS = [
3280
+ "fee",
3281
+ "tickLower",
3282
+ "tickUpper"
3283
+ ];
3284
+ const OPTIONAL_POSITION_STRINGS = [
3285
+ "token0CompositeKey",
3286
+ "token1CompositeKey",
3287
+ "token0Image",
3288
+ "token1Image",
3289
+ "sqrtPrice",
3290
+ "fees0",
3291
+ "fees1"
3292
+ ];
3293
+ /**
3294
+ * Reads one position row of `/positions` or `/position`: the pair, owner, range, liquidity and
3295
+ * amounts must be usable; the composite keys, token images, current tick and price, and the
3296
+ * escrowed fees are carried when present. `pool` is the pool reference (`poolRef` wins over
3297
+ * `pool`; `''` when the row names neither).
3298
+ */
3299
+ function parsePosition(value) {
3300
+ const wire = asEntryRecord(value);
3301
+ if (wire === void 0) return entryRejected("the row is not an object");
3302
+ for (const key of REQUIRED_POSITION_STRINGS) if (typeof wire[key] !== "string") return entryRejected(`${key} is not a string`);
3303
+ for (const key of REQUIRED_POSITION_NUMBERS) {
3304
+ const number = wire[key];
3305
+ if (typeof number !== "number" || !Number.isFinite(number)) return entryRejected(`${key} is not a number`);
3306
+ }
3307
+ if (typeof wire["inRange"] !== "boolean") return entryRejected("inRange is not a boolean");
3308
+ for (const key of OPTIONAL_POSITION_STRINGS) if (wire[key] !== void 0 && typeof wire[key] !== "string") return entryRejected(`${key} is not a string`);
3309
+ const currentTick = wire["currentTick"];
3310
+ if (currentTick !== void 0 && (typeof currentTick !== "number" || !Number.isFinite(currentTick))) return entryRejected("currentTick is not a number");
3311
+ const pool = wire["poolRef"] ?? wire["pool"] ?? "";
3312
+ if (typeof pool !== "string") return entryRejected("pool is not a string");
3313
+ const optional = Object.fromEntries([...OPTIONAL_POSITION_STRINGS, "currentTick"].flatMap((key) => wire[key] === void 0 ? [] : [[key, wire[key]]]));
3314
+ return entryOk({
3315
+ token0Symbol: wire["token0Symbol"],
3316
+ token1Symbol: wire["token1Symbol"],
3317
+ fee: wire["fee"],
3318
+ tickLower: wire["tickLower"],
3319
+ tickUpper: wire["tickUpper"],
3320
+ liquidity: wire["liquidity"],
3321
+ amount0: wire["amount0"],
3322
+ amount1: wire["amount1"],
3323
+ inRange: wire["inRange"],
3324
+ pool,
3325
+ owner: wire["owner"],
3326
+ ...optional
3327
+ });
3328
+ }
3329
+ /** The position inside a `/position` answer, or `INVALID_CHAIN_RESPONSE` when it is not one. */
3330
+ function readPositionBody(data) {
3331
+ const parsed = parsePosition(data);
3332
+ if (parsed.ok) return parsed.value;
3333
+ throw new GSwapSDKError("Backend returned an invalid position.", "INVALID_CHAIN_RESPONSE", {
3334
+ data,
3335
+ reason: parsed.reason
3336
+ });
3337
+ }
1986
3338
 
1987
3339
  //#endregion
1988
3340
  //#region src/classes/quoting.ts
1989
3341
  /** Read-only quote service backed by the v2 offline quote engine. */
1990
3342
  var Quoting = class {
1991
- dexBackendBaseUrl;
1992
- http;
3343
+ gateway;
1993
3344
  symbols;
1994
3345
  /**
1995
3346
  * Creates a quote service.
1996
3347
  *
1997
3348
  * @example
1998
3349
  * ```ts
1999
- * const quoting = new Quoting('https://swap-backend.stage.defi.ovh.gala.com', http, symbols);
3350
+ * const quoting = new Quoting(gateway, symbols); // a ChainGateway, or any { dexBackendBaseUrl, httpRequestor }
2000
3351
  * ```
2001
3352
  */
2002
- constructor(dexBackendBaseUrl, http, symbols) {
2003
- this.dexBackendBaseUrl = dexBackendBaseUrl;
2004
- this.http = http;
3353
+ constructor(gateway, symbols) {
3354
+ this.gateway = gateway;
2005
3355
  this.symbols = symbols;
3356
+ this.http = new HttpClient(gateway.httpRequestor, gateway.requestTimeoutMs ?? 3e4, gateway.wire);
2006
3357
  }
3358
+ http;
2007
3359
  /**
2008
3360
  * Quotes the output for a positive exact-input amount.
2009
3361
  *
@@ -2015,7 +3367,7 @@ var Quoting = class {
2015
3367
  */
2016
3368
  async quoteExactInput(tokenIn, tokenOut, amountIn, fee) {
2017
3369
  validateNumericAmount(amountIn, "amountIn");
2018
- return this.quote(tokenIn, tokenOut, { amountIn: toDecimalString$1(amountIn) }, fee);
3370
+ return this.quote(tokenIn, tokenOut, { amountIn: toDecimalString(amountIn) }, fee);
2019
3371
  }
2020
3372
  /**
2021
3373
  * Quotes the input for a positive exact-output amount.
@@ -2028,18 +3380,18 @@ var Quoting = class {
2028
3380
  */
2029
3381
  async quoteExactOutput(tokenIn, tokenOut, amountOut, fee) {
2030
3382
  validateNumericAmount(amountOut, "amountOut");
2031
- return this.quote(tokenIn, tokenOut, { amountOut: toDecimalString$1(amountOut) }, fee);
3383
+ return this.quote(tokenIn, tokenOut, { amountOut: toDecimalString(amountOut) }, fee);
2032
3384
  }
2033
3385
  async quote(tokenIn, tokenOut, amount, fee) {
2034
3386
  validateOptionalFee(fee);
2035
3387
  const params = {
2036
- tokenIn: await resolveSymbol$1(this.symbols, tokenIn),
2037
- tokenOut: await resolveSymbol$1(this.symbols, tokenOut),
3388
+ tokenIn: await queryTokenOf(this.symbols, tokenIn),
3389
+ tokenOut: await queryTokenOf(this.symbols, tokenOut),
2038
3390
  ...amount
2039
3391
  };
2040
3392
  if (fee !== void 0) params["fee"] = String(fee);
2041
3393
  try {
2042
- return mapQuote((await this.http.sendGetRequest(this.dexBackendBaseUrl, "/v2/trade", "/quote", params)).data);
3394
+ return mapQuote((await this.http.sendGetRequest(this.gateway.dexBackendBaseUrl, "/v2/trade", "/quote", params)).data);
2043
3395
  } catch (error) {
2044
3396
  throw mapQuoteError(error, tokenIn, tokenOut, fee);
2045
3397
  }
@@ -2101,12 +3453,19 @@ function optionalPositiveDecimal(value) {
2101
3453
  return false;
2102
3454
  }
2103
3455
  }
3456
+ /**
3457
+ * The ways the backend says a pair has no market at all: a token the DEX contract never
3458
+ * registered ("has no trading symbol on the new DEX contract"), no pool at ANY fee tier ("No
3459
+ * pools found with sufficient liquidity"), or a missing pool ("Pool does not exist on the new
3460
+ * DEX contract"). A pool that exists but is too thin says "Insufficient liquidity: ..." instead.
3461
+ */
3462
+ const NO_MARKET_MESSAGE = /no pool|no pools|pool does not exist|has no trading symbol on the new DEX contract/iu;
2104
3463
  function mapQuoteError(error, tokenIn, tokenOut, fee) {
2105
3464
  if (error instanceof GSwapSDKError) {
2106
3465
  const status = readStatus(error.details);
2107
3466
  const message = readMessage(error.details) ?? error.message;
2108
- if (status === 404 || /no pool|no pools|pool does not exist/iu.test(message)) return GSwapSDKError.noPoolAvailableError(tokenIn, tokenOut, fee);
2109
- if (status === 400 && /insufficient liquidity/iu.test(message)) return GSwapSDKError.insufficientLiquidityError(tokenIn, tokenOut, fee);
3467
+ if (status === 404 || NO_MARKET_MESSAGE.test(message)) return GSwapSDKError.noPoolAvailableError(tokenIn, tokenOut, fee, error);
3468
+ if (status === 400 && /insufficient liquidity/iu.test(message)) return GSwapSDKError.insufficientLiquidityError(tokenIn, tokenOut, fee, error);
2110
3469
  return error;
2111
3470
  }
2112
3471
  return new GSwapSDKError("Quote request failed.", "HTTP_ERROR", {
@@ -2131,107 +3490,112 @@ function readMessage(details) {
2131
3490
  function validateOptionalFee(fee) {
2132
3491
  if (fee !== void 0 && !ALL_FEE_TIERS.some((tier) => Number(tier) === Number(fee))) throw new GSwapSDKError(`Invalid fee tier: ${fee}`, "VALIDATION_ERROR", { fee });
2133
3492
  }
2134
- function toDecimalString$1(amount) {
3493
+ function toDecimalString(amount) {
2135
3494
  return new bignumber_js.default(amount).toFixed();
2136
3495
  }
2137
- async function resolveSymbol$1(symbols, token) {
2138
- const resolved = await symbols.resolve(token);
2139
- if (resolved.symbol.length > 0) return resolved.symbol;
2140
- throw new GSwapSDKError("Token could not be resolved to a trading symbol.", "SYMBOL_NOT_FOUND", { token });
2141
- }
2142
3496
 
2143
3497
  //#endregion
2144
- //#region src/classes/symbols.ts
2145
- const SYMBOL_CACHE_TTL_MS = 6e4;
2146
- /** Symbol registry client for the current GalaChainDex contract. */
2147
- var Symbols = class {
2148
- gateway;
2149
- cachedSymbols;
2150
- cachedAt = 0;
2151
- refreshInFlight;
2152
- http;
2153
- /** Create a symbol service backed by the swap backend. */
2154
- constructor(gateway) {
2155
- this.gateway = gateway;
2156
- this.http = new HttpClient(gateway.httpRequestor, gateway.requestTimeoutMs);
2157
- }
2158
- /** List all registered GalaChainDex trading symbols, cached for 60 seconds. */
2159
- async list() {
2160
- if (this.cachedSymbols !== void 0 && Date.now() - this.cachedAt < SYMBOL_CACHE_TTL_MS) return this.cachedSymbols;
2161
- if (this.refreshInFlight !== void 0) return this.refreshInFlight;
2162
- this.refreshInFlight = this.http.sendGetRequest(this.gateway.dexBackendBaseUrl, "/v2/trade", "/symbols").then((response) => parseSymbols(response.data)).then((symbols) => {
2163
- this.cachedSymbols = symbols;
2164
- this.cachedAt = Date.now();
2165
- return symbols;
2166
- }).finally(() => {
2167
- this.refreshInFlight = void 0;
2168
- });
2169
- return this.refreshInFlight;
2170
- }
2171
- /** Invalidate the cached symbol registry after a write changes registration state.
2172
- *
2173
- * @example
2174
- * ```ts
2175
- * gSwap.symbols.invalidate();
2176
- * ```
2177
- */
2178
- invalidate() {
2179
- this.cachedSymbols = void 0;
2180
- this.cachedAt = 0;
2181
- }
2182
- /** Force one registry refresh, bypassing the cache.
2183
- *
2184
- * @example
2185
- * ```ts
2186
- * const symbols = await gSwap.symbols.refresh();
2187
- * ```
2188
- */
2189
- async refresh() {
2190
- this.invalidate();
2191
- this.refreshInFlight = void 0;
2192
- return this.list();
2193
- }
2194
- /** Resolve a symbol or token class key to its registered symbol metadata. */
2195
- async resolve(ref) {
2196
- let symbols = await this.list();
2197
- const composite = typeof ref === "string" ? isCompositeKey(ref) ? compositeKeyOf(parseTokenClassKey(ref)) : void 0 : compositeKeyOf(ref);
2198
- const resolved = symbols.find((entry) => entry.symbol === ref || composite !== void 0 && compositeKeyOf(entry) === composite);
2199
- if (resolved === void 0) {
2200
- symbols = await this.refresh();
2201
- const refreshed = symbols.find((entry) => entry.symbol === ref || composite !== void 0 && compositeKeyOf(entry) === composite);
2202
- if (refreshed === void 0) throw GSwapSDKError.unknownTokenError(ref);
2203
- return refreshed;
3498
+ //#region src/classes/shadow_submit.ts
3499
+ /** Maps an HTTP status to a failure kind: 503 retryable, 400 disabled, anything else unknown. */
3500
+ function classifyShadowSubmitStatus(status) {
3501
+ if (status === 503) return "unavailable";
3502
+ if (status === 400) return "disabled";
3503
+ return "unknown";
3504
+ }
3505
+ /**
3506
+ * Prefers the body `code` (stable across status remaps) and falls back to the HTTP status.
3507
+ * `SHADOW_UNAVAILABLE` is the only retryable case; `SHADOW_RELAY_REJECTED` is a terminal gateway
3508
+ * rejection; the two other 400 codes and any unknown 400 map to "disabled".
3509
+ */
3510
+ function classifyShadowSubmitError(status, code) {
3511
+ if (code === "SHADOW_UNAVAILABLE") return "unavailable";
3512
+ if (code === "SHADOW_RELAY_REJECTED") return "rejected";
3513
+ if (code === "SHADOW_DISABLED" || code === "SHADOW_UNSUPPORTED_TYPE") return "disabled";
3514
+ return classifyShadowSubmitStatus(status);
3515
+ }
3516
+ /**
3517
+ * Normalizes a shadow write's raw answer.
3518
+ *
3519
+ * A 2xx envelope with `error === false` is a success when it is a queued RequestTrade that
3520
+ * acknowledges the EXACT signed `uniqueKey`, or a synchronous (`mode: 'sync'`) liquidity write.
3521
+ * Everything else is classified as a failure from the status and the body's `code`.
3522
+ *
3523
+ * @throws GSwapSDKError INVALID_GATEWAY_RESPONSE when a RequestTrade success does not queue the
3524
+ * signed `uniqueKey` (a synchronous or mismatched answer must never be accepted as a swap).
3525
+ */
3526
+ function readShadowSubmitOutcome(method, signedBody, result) {
3527
+ const { httpStatus, body } = result;
3528
+ const envelope = asRecord(body);
3529
+ if (httpStatus >= 200 && httpStatus < 300 && (envelope === null || envelope === void 0 ? void 0 : envelope["error"]) === false) {
3530
+ const data = asRecord(envelope["data"]);
3531
+ const rawTransactionId = typeof (data === null || data === void 0 ? void 0 : data["transactionId"]) === "string" ? data["transactionId"] : void 0;
3532
+ if (method === "RequestTrade") {
3533
+ const requestedKey = signedBody["uniqueKey"];
3534
+ if ((data === null || data === void 0 ? void 0 : data["mode"]) !== "queued" || typeof requestedKey !== "string" || data["uniqueKey"] !== requestedKey) throw new GSwapSDKError(`The swap backend did not queue the request ${typeof requestedKey === "string" ? requestedKey : ""}.`, "INVALID_GATEWAY_RESPONSE", {
3535
+ httpStatus,
3536
+ body
3537
+ });
3538
+ return {
3539
+ ok: true,
3540
+ bundleId: rawTransactionId ?? "",
3541
+ queued: {
3542
+ uniqueKey: requestedKey,
3543
+ requestTransactionId: rawTransactionId ?? null
3544
+ }
3545
+ };
2204
3546
  }
2205
- return resolved;
2206
- }
2207
- /** Resolve and put two token references in canonical trading-symbol order. */
2208
- async orderPair(a, b) {
2209
- const [resolvedA, resolvedB] = await Promise.all([this.resolve(a), this.resolve(b)]);
2210
- const [token0Symbol, token1Symbol] = orderSymbols(resolvedA.symbol, resolvedB.symbol);
2211
- return {
2212
- token0: token0Symbol === resolvedA.symbol ? resolvedA : resolvedB,
2213
- token1: token1Symbol === resolvedB.symbol ? resolvedB : resolvedA,
2214
- flipped: token0Symbol !== resolvedA.symbol
3547
+ if ((data === null || data === void 0 ? void 0 : data["mode"]) === "sync") return {
3548
+ ok: true,
3549
+ bundleId: rawTransactionId ?? ""
2215
3550
  };
2216
3551
  }
2217
- };
2218
- function parseSymbols(value) {
2219
- if (!Array.isArray(value) || !value.every(isTradingSymbol)) throw new GSwapSDKError("Backend returned an invalid symbol list.", "INVALID_CHAIN_RESPONSE", { data: value });
2220
- return value;
2221
- }
2222
- function isTradingSymbol(value) {
2223
- if (typeof value !== "object" || value === null) return false;
2224
- const entry = value;
2225
- return typeof entry["symbol"] === "string" && typeof entry["collection"] === "string" && typeof entry["category"] === "string" && typeof entry["type"] === "string" && typeof entry["additionalKey"] === "string" && typeof entry["decimals"] === "number" && Number.isInteger(entry["decimals"]) && entry["decimals"] >= 0;
3552
+ const kind = classifyShadowSubmitError(httpStatus, getStringProperty(body, "code"));
3553
+ if (kind === "rejected") return {
3554
+ ok: false,
3555
+ kind: "rejected",
3556
+ retryable: false,
3557
+ message: getStringProperty(body, "message") ?? ""
3558
+ };
3559
+ return kind === "unavailable" ? {
3560
+ ok: false,
3561
+ kind,
3562
+ retryable: true
3563
+ } : {
3564
+ ok: false,
3565
+ kind,
3566
+ retryable: false
3567
+ };
2226
3568
  }
2227
- function isCompositeKey(ref) {
2228
- return ref.includes("|") || ref.includes("$");
3569
+ function asRecord(value) {
3570
+ return typeof value === "object" && value !== null ? value : void 0;
2229
3571
  }
2230
3572
 
2231
3573
  //#endregion
2232
3574
  //#region src/classes/swaps.ts
2233
- /** The swap backend's apply-results key constraint (`apply-results.dto.ts`). */
2234
- const UNIQUE_KEY_PATTERN = /^[A-Za-z0-9._:-]{1,128}$/u;
3575
+ /**
3576
+ * The route of a quote: its fee tier and pool, with `tokenIn` / `tokenOut` taken from
3577
+ * `tokenInIsToken0`, nothing re-derived. Use it to swap in exactly the pool a quote priced:
3578
+ *
3579
+ * @example
3580
+ * ```ts
3581
+ * const route = swapRouteFromQuote(quote);
3582
+ * await swaps.swap(route.tokenIn, route.tokenOut, route.fee, { exactIn: '100' }, { pool: route.pool });
3583
+ * ```
3584
+ */
3585
+ function swapRouteFromQuote(quote) {
3586
+ validateFee(quote.fee);
3587
+ assertCanonicalPair(quote.token0Symbol, quote.token1Symbol);
3588
+ const [tokenIn, tokenOut] = quote.tokenInIsToken0 ? [quote.token0Symbol, quote.token1Symbol] : [quote.token1Symbol, quote.token0Symbol];
3589
+ return {
3590
+ tokenIn,
3591
+ tokenOut,
3592
+ fee: quote.fee,
3593
+ pool: {
3594
+ token0: quote.token0Symbol,
3595
+ token1: quote.token1Symbol
3596
+ }
3597
+ };
3598
+ }
2235
3599
  /** Executes queued current-contract RequestTrade operations through the Chain Gateway. */
2236
3600
  var Swaps = class {
2237
3601
  gateway;
@@ -2246,12 +3610,14 @@ var Swaps = class {
2246
3610
  * const swaps = new Swaps(gateway, symbols, signer, 'client|alice');
2247
3611
  * ```
2248
3612
  */
2249
- constructor(gateway, symbols, signer, walletAddress) {
3613
+ constructor(gateway, symbols, signer, walletAddress, options = {}) {
2250
3614
  this.gateway = gateway;
2251
3615
  this.symbols = symbols;
2252
3616
  this.signer = signer;
2253
3617
  this.walletAddress = walletAddress;
3618
+ this.uniqueKeyPrefix = resolveUniqueKeyPrefix(options.uniqueKeyPrefix);
2254
3619
  }
3620
+ uniqueKeyPrefix;
2255
3621
  /**
2256
3622
  * Signs and submits an exact-input or exact-output RequestTrade DTO. The returned transaction
2257
3623
  * is QUEUED; call `await tx.confirm()` to obtain the executed trade or chain rejection.
@@ -2264,13 +3630,97 @@ var Swaps = class {
2264
3630
  * });
2265
3631
  * ```
2266
3632
  */
2267
- async swap(tokenIn, tokenOut, fee, amount) {
3633
+ async swap(tokenIn, tokenOut, fee, amount, options) {
3634
+ return this.submitSwap(await this.buildSwap(tokenIn, tokenOut, fee, amount, options));
3635
+ }
3636
+ /**
3637
+ * Signs and submits a SHADOW (dark) swap: the same RequestTrade as {@link swap}, relayed with
3638
+ * `X-Shadow: true` so defi-backend marks it private before it reaches the contract. Requires a
3639
+ * `walletAddress`. Resolves the normalized outcome instead of throwing on a gateway refusal:
3640
+ * `ok: false` carries `unavailable` (retryable), `disabled`, `rejected` (with the gateway's
3641
+ * message) or `unknown`. A success carries `queued`, to be confirmed with
3642
+ * `confirmSwap(queued.uniqueKey)`; never fall back to a public swap on failure.
3643
+ *
3644
+ * @example
3645
+ * ```ts
3646
+ * const outcome = await swaps.swapShadow('GALA', 'GUSDC', 3000, { exactIn: '100' });
3647
+ * if (outcome.ok && outcome.queued) await swaps.confirmSwap(outcome.queued.uniqueKey);
3648
+ * ```
3649
+ */
3650
+ async swapShadow(tokenIn, tokenOut, fee, amount, options) {
3651
+ return this.submitSwapShadow(await this.buildSwap(tokenIn, tokenOut, fee, amount, options));
3652
+ }
3653
+ /**
3654
+ * Builds the RequestTrade DTO that {@link swap} would sign, without signing or sending it: to
3655
+ * show in an approval popup, to remember its `uniqueKey`, or to sign elsewhere. Submit exactly
3656
+ * this DTO with {@link submitSwap}. The pure counterpart, with canonical inputs and no
3657
+ * resolution, is `buildRequestTradeDto`.
3658
+ *
3659
+ * @example
3660
+ * ```ts
3661
+ * const dto = await swaps.buildSwap('GALA', 'GUSDC', 3000, { exactIn: '100' });
3662
+ * showApproval(dto);
3663
+ * const submitted = await swaps.submitSwap(dto);
3664
+ * ```
3665
+ */
3666
+ async buildSwap(tokenIn, tokenOut, fee, amount, options) {
2268
3667
  validateFee(fee);
2269
- const dto = await this.buildRequestTradeDto(tokenIn, tokenOut, fee, amount);
3668
+ const uniqueKey = (options === null || options === void 0 ? void 0 : options.uniqueKey) ?? createUniqueKey(this.uniqueKeyPrefix);
3669
+ assertUniqueKey(uniqueKey);
3670
+ const tokenInSymbol = await tradingSymbolOf(this.symbols, tokenIn);
3671
+ const tokenOutSymbol = await tradingSymbolOf(this.symbols, tokenOut);
3672
+ const ordered = this.orientPool(tokenInSymbol, tokenOutSymbol, options === null || options === void 0 ? void 0 : options.pool);
3673
+ return buildRequestTradeDto({
3674
+ token0: ordered.token0,
3675
+ token1: ordered.token1,
3676
+ fee,
3677
+ tokenInIsToken0: tokenInSymbol === ordered.token0,
3678
+ uniqueKey,
3679
+ ...amount
3680
+ });
3681
+ }
3682
+ /**
3683
+ * Signs and submits a DTO built by {@link buildSwap} or `buildRequestTradeDto`. The DTO must be
3684
+ * exactly what the builder produces (no signature, canonical decimals, builder key order), so
3685
+ * what an app showed is what gets signed.
3686
+ *
3687
+ * @example
3688
+ * ```ts
3689
+ * const submitted = await swaps.submitSwap(dto);
3690
+ * const applied = await submitted.confirm();
3691
+ * ```
3692
+ */
3693
+ async submitSwap(dto) {
3694
+ const signed = await this.signRequestTrade(dto);
3695
+ return this.gateway.submit("RequestTrade", signed, this.submitOptions());
3696
+ }
3697
+ /** Signs and shadow-submits a DTO built by {@link buildSwap}; see {@link swapShadow}. */
3698
+ async submitSwapShadow(dto) {
3699
+ const signed = await this.signRequestTrade(dto);
3700
+ const result = await this.gateway.submitShadow("RequestTrade", signed, this.submitOptions());
3701
+ return readShadowSubmitOutcome("RequestTrade", signed, result);
3702
+ }
3703
+ submitOptions() {
3704
+ return this.walletAddress === void 0 ? {} : { walletAddress: this.walletAddress };
3705
+ }
3706
+ async signRequestTrade(dto) {
3707
+ assertCanonicalV2Dto("RequestTrade", dto);
2270
3708
  if (this.signer === void 0) throw GSwapSDKError.noSignerError();
2271
- const signedDto = await this.signer.signObject("RequestTrade", dto);
2272
- const submitOptions = this.walletAddress === void 0 ? {} : { walletAddress: this.walletAddress };
2273
- return this.gateway.submit("RequestTrade", signedDto, submitOptions);
3709
+ return this.signer.signObject("RequestTrade", dto);
3710
+ }
3711
+ /** The pool's two symbols in contract order: the quote's own when pinned, else string order. */
3712
+ orientPool(tokenInSymbol, tokenOutSymbol, pool) {
3713
+ if (pool === void 0) return readOrderedSymbols(orderSymbols(tokenInSymbol, tokenOutSymbol));
3714
+ if (!(tokenInSymbol === pool.token0 && tokenOutSymbol === pool.token1 || tokenInSymbol === pool.token1 && tokenOutSymbol === pool.token0)) throw new GSwapSDKError("tokenIn and tokenOut must be the two tokens of the pinned pool.", "VALIDATION_ERROR", {
3715
+ type: "POOL_MISMATCH",
3716
+ tokenIn: tokenInSymbol,
3717
+ tokenOut: tokenOutSymbol,
3718
+ pool
3719
+ });
3720
+ return {
3721
+ token0: pool.token0,
3722
+ token1: pool.token1
3723
+ };
2274
3724
  }
2275
3725
  /**
2276
3726
  * Confirm a swap by its durable `uniqueKey` without resubmitting it. This is the recovery
@@ -2282,11 +3732,11 @@ var Swaps = class {
2282
3732
  *
2283
3733
  * @example
2284
3734
  * ```ts
2285
- * const applied = await swaps.confirmSwap('gswap-sdk-2f1c…');
3735
+ * const applied = await swaps.confirmSwap('galaswap-operation-2f1c…');
2286
3736
  * ```
2287
3737
  */
2288
3738
  async confirmSwap(uniqueKey, options) {
2289
- if (!UNIQUE_KEY_PATTERN.test(uniqueKey)) throw new GSwapSDKError("uniqueKey must be 1-128 characters of A-Z, a-z, 0-9, \".\", \"_\", \":\" or \"-\".", "VALIDATION_ERROR", { uniqueKey });
3739
+ assertUniqueKey(uniqueKey);
2290
3740
  return new SubmittedTransaction({
2291
3741
  method: "RequestTrade",
2292
3742
  uniqueKey,
@@ -2301,47 +3751,7 @@ var Swaps = class {
2301
3751
  ...options
2302
3752
  });
2303
3753
  }
2304
- async buildRequestTradeDto(tokenIn, tokenOut, fee, amount) {
2305
- const tokenInSymbol = await resolveSymbol(this.symbols, tokenIn);
2306
- const tokenOutSymbol = await resolveSymbol(this.symbols, tokenOut);
2307
- const ordered = readOrderedSymbols(orderSymbols(tokenInSymbol, tokenOutSymbol));
2308
- const tokenInIsToken0 = tokenInSymbol === ordered.token0;
2309
- const dto = {
2310
- token0: ordered.token0,
2311
- token1: ordered.token1,
2312
- fee,
2313
- uniqueKey: `gswap-sdk-${globalThis.crypto.randomUUID()}`
2314
- };
2315
- if ("exactIn" in amount) {
2316
- const exactIn = amount.exactIn;
2317
- if (exactIn === void 0) throw new GSwapSDKError("exactIn is required for an exact-input swap.", "VALIDATION_ERROR");
2318
- validateNumericAmount(exactIn, "exactIn");
2319
- dto[tokenInIsToken0 ? "sell0Qty" : "sell1Qty"] = toDecimalString(exactIn);
2320
- if (amount.amountOutMinimum !== void 0) {
2321
- validateNumericAmount(amount.amountOutMinimum, "amountOutMinimum", true);
2322
- dto["amountOutMinimum"] = toDecimalString(amount.amountOutMinimum);
2323
- }
2324
- } else {
2325
- const exactOut = amount.exactOut;
2326
- if (exactOut === void 0) throw new GSwapSDKError("exactOut is required for an exact-output swap.", "VALIDATION_ERROR");
2327
- validateNumericAmount(exactOut, "exactOut");
2328
- dto[tokenInIsToken0 ? "buy1Qty" : "buy0Qty"] = toDecimalString(exactOut);
2329
- if (amount.amountInMaximum !== void 0) {
2330
- validateNumericAmount(amount.amountInMaximum, "amountInMaximum", true);
2331
- dto["amountInMaximum"] = toDecimalString(amount.amountInMaximum);
2332
- }
2333
- }
2334
- return dto;
2335
- }
2336
3754
  };
2337
- function toDecimalString(amount) {
2338
- return new bignumber_js.default(amount).toFixed();
2339
- }
2340
- async function resolveSymbol(symbols, token) {
2341
- const resolved = await symbols.resolve(token);
2342
- if (resolved.symbol.length > 0) return resolved.symbol;
2343
- throw new GSwapSDKError("Token could not be resolved to a trading symbol.", "SYMBOL_NOT_FOUND", { token });
2344
- }
2345
3755
 
2346
3756
  //#endregion
2347
3757
  //#region src/classes/gswap.ts
@@ -2358,8 +3768,11 @@ var GSwap = class {
2358
3768
  positions;
2359
3769
  swaps;
2360
3770
  assets;
3771
+ /** Network-fee quotes (DryRun) signed with this client's signer. */
3772
+ fees;
2361
3773
  dexBackendWsUrl;
2362
3774
  webSocketImpl;
3775
+ streamApiKey;
2363
3776
  streamClient;
2364
3777
  /**
2365
3778
  * Create an SDK client for prod or stage.
@@ -2376,21 +3789,30 @@ var GSwap = class {
2376
3789
  this.chainCallTimeoutMs = (options === null || options === void 0 ? void 0 : options.chainCallTimeoutMs) ?? 3e4;
2377
3790
  this.signer = options === null || options === void 0 ? void 0 : options.signer;
2378
3791
  this.walletAddress = options === null || options === void 0 ? void 0 : options.walletAddress;
2379
- const httpClient = new HttpClient(options === null || options === void 0 ? void 0 : options.httpRequestor, this.chainCallTimeoutMs);
3792
+ const httpClient = new HttpClient(options === null || options === void 0 ? void 0 : options.httpRequestor, this.chainCallTimeoutMs, options === null || options === void 0 ? void 0 : options.wire);
2380
3793
  this.gateway = new ChainGateway({
2381
3794
  dexBackendBaseUrl: this.dexBackendBaseUrl,
2382
3795
  chainCallTimeoutMs: this.chainCallTimeoutMs,
3796
+ ...(options === null || options === void 0 ? void 0 : options.wire) === void 0 ? {} : { wire: options.wire },
2383
3797
  ...(options === null || options === void 0 ? void 0 : options.httpRequestor) === void 0 ? {} : { httpRequestor: options.httpRequestor },
2384
3798
  ...this.walletAddress === void 0 ? {} : { walletAddress: this.walletAddress }
2385
3799
  });
2386
- this.symbols = new Symbols(this.gateway);
2387
- this.quoting = new Quoting(this.dexBackendBaseUrl, httpClient, this.symbols);
2388
- this.pools = new Pools(this.gateway, this.symbols);
2389
- this.positions = new Positions(this.gateway, this.symbols, this.signer, this.walletAddress);
2390
- this.swaps = new Swaps(this.gateway, this.symbols, this.signer, this.walletAddress);
3800
+ const lenient = { onInvalidEntry: options === null || options === void 0 ? void 0 : options.onInvalidEntry };
3801
+ this.symbols = new Symbols(this.gateway, lenient);
3802
+ const resolver = (options === null || options === void 0 ? void 0 : options.symbolResolution) === "canonical" ? new CanonicalSymbols() : this.symbols;
3803
+ this.quoting = new Quoting(this.gateway, resolver);
3804
+ this.pools = new Pools(this.gateway, resolver, lenient);
3805
+ const keyOptions = {
3806
+ uniqueKeyPrefix: options === null || options === void 0 ? void 0 : options.uniqueKeyPrefix,
3807
+ ...lenient
3808
+ };
3809
+ this.positions = new Positions(this.gateway, resolver, this.signer, this.walletAddress, keyOptions);
3810
+ this.swaps = new Swaps(this.gateway, resolver, this.signer, this.walletAddress, keyOptions);
2391
3811
  this.assets = new Assets(this.dexBackendBaseUrl, httpClient);
3812
+ this.fees = new DexFees(this.gateway, this.signer);
2392
3813
  this.dexBackendWsUrl = (options === null || options === void 0 ? void 0 : options.dexBackendWsUrl) ?? ((options === null || options === void 0 ? void 0 : options.dexBackendBaseUrl) === void 0 ? environment.dexBackendWsUrl : `${this.dexBackendBaseUrl.replace(/^http/u, "ws")}/ws`);
2393
3814
  this.webSocketImpl = options === null || options === void 0 ? void 0 : options.webSocketImpl;
3815
+ this.streamApiKey = options === null || options === void 0 ? void 0 : options.streamApiKey;
2394
3816
  }
2395
3817
  /**
2396
3818
  * The realtime stream client for this environment (created on first use, not connected until you
@@ -2406,7 +3828,8 @@ var GSwap = class {
2406
3828
  get stream() {
2407
3829
  this.streamClient ??= new GdexStreamService({
2408
3830
  url: this.dexBackendWsUrl,
2409
- ...this.webSocketImpl === void 0 ? {} : { webSocketImpl: this.webSocketImpl }
3831
+ ...this.webSocketImpl === void 0 ? {} : { webSocketImpl: this.webSocketImpl },
3832
+ ...this.streamApiKey === void 0 ? {} : { apiKey: this.streamApiKey }
2410
3833
  });
2411
3834
  return this.streamClient;
2412
3835
  }
@@ -2415,9 +3838,149 @@ function trimTrailingSlash(value) {
2415
3838
  return value.replace(/\/$/u, "");
2416
3839
  }
2417
3840
 
3841
+ //#endregion
3842
+ //#region src/classes/pending_swaps.ts
3843
+ /** Key the wallet_extension has always used; its storage value is a plain array of entries. */
3844
+ const PENDING_SWAPS_STORAGE_KEY = "pendingSwaps.v1";
3845
+ /** Entries older than this are dropped the next time a wallet's swaps are listed. */
3846
+ const PENDING_SWAP_EXPIRY_MS = 6048e5;
3847
+ /**
3848
+ * Remembers queued swaps so their outcome can be confirmed after the UI, popup or service worker
3849
+ * that submitted them is gone (`confirmAppliedTrade` with the stored `uniqueKey`).
3850
+ *
3851
+ * Guarantees:
3852
+ * - Persistence is best effort and NEVER throws: a failing or missing storage must not turn an
3853
+ * accepted request into a client-side resubmission. `list` resolves `[]` when storage fails.
3854
+ * - Operations of one store instance are serialized, so concurrent `remember` / `forget` calls
3855
+ * cannot lose each other's write (read-modify-write over an async adapter). Two store instances
3856
+ * over the same storage (for example popup and service worker) are not coordinated.
3857
+ * - Entries that do not match the expected shape are ignored and dropped on the next write.
3858
+ * - Entries expire after seven days (an entry exactly seven days old is expired; frontend_v2
3859
+ * kept it for that one millisecond); `list` removes them from storage, which frontend_v2 never
3860
+ * did on read.
3861
+ * - New entries go to the end (or `insertAt: 'start'`); `list` returns the wallet's entries in
3862
+ * stored order (or `listOrder: 'newest-first'`).
3863
+ * - The stored shape is wallet_extension's by default; `storageKey`, `darkFlagField`, `insertAt`
3864
+ * and `listOrder` reproduce frontend_v2's. JSON key order inside an entry is not preserved.
3865
+ *
3866
+ * Passing no `storage` yields a store that remembers nothing.
3867
+ *
3868
+ * @example
3869
+ * ```ts
3870
+ * const store = new PendingSwapStore({
3871
+ * get: async (key) => (await chrome.storage.local.get(key))[key],
3872
+ * set: (key, value) => chrome.storage.local.set({ [key]: value }),
3873
+ * });
3874
+ * await store.remember({ uniqueKey, requestTransactionId, wallet, token0Symbol: 'GALA',
3875
+ * token1Symbol: 'GUSDC', isDarkSwap: false, createdAt: Date.now() });
3876
+ * for (const swap of await store.list(wallet)) await confirmAppliedTrade(client, swap);
3877
+ * ```
3878
+ */
3879
+ var PendingSwapStore = class {
3880
+ storage;
3881
+ storageKey;
3882
+ darkFlagField;
3883
+ insertAt;
3884
+ listOrder;
3885
+ now;
3886
+ queue = Promise.resolve();
3887
+ constructor(storage, options = {}) {
3888
+ this.storage = storage;
3889
+ this.storageKey = options.storageKey ?? "pendingSwaps.v1";
3890
+ this.darkFlagField = options.darkFlagField ?? "isDarkSwap";
3891
+ this.insertAt = options.insertAt ?? "end";
3892
+ this.listOrder = options.listOrder ?? "stored";
3893
+ this.now = options.now ?? Date.now;
3894
+ }
3895
+ /** Saves (or replaces, by `uniqueKey`) a pending swap. */
3896
+ remember(swap) {
3897
+ return this.enqueue(async (storage) => {
3898
+ const others = (await this.read(storage)).filter((entry) => entry.uniqueKey !== swap.uniqueKey).map(this.toStored);
3899
+ const stored = this.toStored(swap);
3900
+ await storage.set(this.storageKey, this.insertAt === "end" ? [...others, stored] : [stored, ...others]);
3901
+ });
3902
+ }
3903
+ /** Removes a pending swap once its outcome is known. */
3904
+ forget(uniqueKey) {
3905
+ return this.enqueue(async (storage) => {
3906
+ const current = await this.read(storage);
3907
+ await storage.set(this.storageKey, current.filter((entry) => entry.uniqueKey !== uniqueKey).map(this.toStored));
3908
+ });
3909
+ }
3910
+ /** The wallet's unexpired pending swaps (see `listOrder`). Expired entries are pruned. */
3911
+ async list(wallet) {
3912
+ return await this.enqueue(async (storage) => {
3913
+ const current = await this.read(storage);
3914
+ const now = this.now();
3915
+ const fresh = current.filter((entry) => now - entry.createdAt < 6048e5);
3916
+ if (fresh.length !== current.length) await storage.set(this.storageKey, fresh.map(this.toStored));
3917
+ const mine = fresh.filter((entry) => entry.wallet === wallet);
3918
+ return this.listOrder === "newest-first" ? mine.sort((left, right) => right.createdAt - left.createdAt) : mine;
3919
+ }) ?? [];
3920
+ }
3921
+ enqueue(operation) {
3922
+ const storage = this.storage;
3923
+ if (storage === void 0) return Promise.resolve(void 0);
3924
+ const run = this.queue.then(async () => {
3925
+ try {
3926
+ return await operation(storage);
3927
+ } catch {
3928
+ return;
3929
+ }
3930
+ });
3931
+ this.queue = run;
3932
+ return run;
3933
+ }
3934
+ async read(storage) {
3935
+ const value = await storage.get(this.storageKey);
3936
+ if (!Array.isArray(value)) return [];
3937
+ const entries = [];
3938
+ for (const item of value) {
3939
+ const entry = this.fromStored(item);
3940
+ if (entry !== void 0) entries.push(entry);
3941
+ }
3942
+ return entries;
3943
+ }
3944
+ toStored = (swap) => ({
3945
+ uniqueKey: swap.uniqueKey,
3946
+ requestTransactionId: swap.requestTransactionId,
3947
+ wallet: swap.wallet,
3948
+ token0Symbol: swap.token0Symbol,
3949
+ token1Symbol: swap.token1Symbol,
3950
+ [this.darkFlagField]: swap.isDarkSwap,
3951
+ createdAt: swap.createdAt
3952
+ });
3953
+ fromStored(value) {
3954
+ if (value === null || typeof value !== "object") return void 0;
3955
+ const entry = value;
3956
+ const flag = entry[this.darkFlagField];
3957
+ const { uniqueKey, requestTransactionId, wallet, token0Symbol, token1Symbol, createdAt } = entry;
3958
+ if (typeof uniqueKey !== "string" || !(typeof requestTransactionId === "string" || requestTransactionId === null) || typeof wallet !== "string" || typeof token0Symbol !== "string" || typeof token1Symbol !== "string" || typeof flag !== "boolean" || typeof createdAt !== "number" || !Number.isFinite(createdAt)) return;
3959
+ return {
3960
+ uniqueKey,
3961
+ requestTransactionId,
3962
+ wallet,
3963
+ token0Symbol,
3964
+ token1Symbol,
3965
+ isDarkSwap: flag,
3966
+ createdAt
3967
+ };
3968
+ }
3969
+ };
3970
+
2418
3971
  //#endregion
2419
3972
  //#region src/utils/eip712.ts
2420
3973
  /**
3974
+ * Genuine EIP-712 typed-data signing for the four v2 dex-contract operations proven (2026-09-16,
3975
+ * live bisection against stage — see defi-backend's `chain-findings-artifact` memory /
3976
+ * https://claude.ai/artifact/3N9MevSjoQdU7gqnVB3dfU) to recover the `client|` identity correctly
3977
+ * via `eth_signTypedData_v4`, unlike `personal_sign` (which only ever resolves the bare `eth|`
3978
+ * form — see `BrowserWalletSigner`'s own doc comment). Mirrors frontend_v2's
3979
+ * `src/Services/eip712Types.ts` + `readWriteUtilsV2.ts`'s `EIP712_FIELD_COMPLETERS` exactly —
3980
+ * ported here rather than shared as a package because gswap-sdk has no frontend_v2 dependency
3981
+ * and should not gain one for four small functions.
3982
+ */
3983
+ /**
2421
3984
  * Derives the EIP-712 `types` for a DTO from its runtime shape.
2422
3985
  *
2423
3986
  * The generated `types` travel with the signed payload and are what BOTH sides hash, so they
@@ -2482,11 +4045,24 @@ function generateEIP712Types(typeName, params) {
2482
4045
  });
2483
4046
  return types;
2484
4047
  }
2485
- /** Matches every genuine-EIP-712 probe that has actually recovered `client|` on stage. */
2486
- const EIP712_DOMAIN = {
2487
- name: "ethereum",
2488
- chainId: 1
2489
- };
4048
+ /**
4049
+ * Builds the EIP-712 domain from the wallet's LIVE `eth_chainId` result, exactly like frontend_v2
4050
+ * (DEFI-1895): a wallet hashes whatever domain it is handed, and MetaMask refuses a `chainId` that
4051
+ * differs from its active network, so a hardcoded `1` breaks every wallet that is not on Mainnet.
4052
+ * defi-backend recovers the signer from the `domain` the payload carries (live-proven for chain
4053
+ * ids 1, 56 and 11155111), so following the wallet is correct on every network.
4054
+ *
4055
+ * @param chainId - The raw `eth_chainId` result: a `0x` hex string (the JSON-RPC form) or an integer.
4056
+ * @throws GSwapSDKError (`INVALID_CHAIN_ID`) when the wallet answers with anything else.
4057
+ */
4058
+ function eip712DomainFor(chainId) {
4059
+ const parsed = typeof chainId === "string" && /^0x[0-9a-fA-F]+$/u.test(chainId) ? Number(chainId) : typeof chainId === "number" ? chainId : NaN;
4060
+ if (!Number.isSafeInteger(parsed) || parsed <= 0) throw new GSwapSDKError("Wallet returned an invalid chain id.", "INVALID_CHAIN_ID", { chainId });
4061
+ return {
4062
+ name: "ethereum",
4063
+ chainId: parsed
4064
+ };
4065
+ }
2490
4066
  /**
2491
4067
  * The four v2 operations proven to recover the signer correctly via genuine EIP-712: `RequestTrade`
2492
4068
  * never had the problem (one numeric field). `AddLiquidity`/`RemoveLiquidity`/
@@ -2583,15 +4159,216 @@ function buildEIP712Payload(methodName, dto) {
2583
4159
  }
2584
4160
 
2585
4161
  //#endregion
2586
- //#region src/classes/signers.ts
4162
+ //#region src/utils/signing.ts
4163
+ /**
4164
+ * SDK-owned signing primitives.
4165
+ *
4166
+ * Everything the SDK needs to produce a GalaChain signature lives here, with no dependency on
4167
+ * `@gala-chain/api` (which drags `secp256k1` native bindings, `reflect-metadata`, `class-validator`
4168
+ * and its own `bignumber.js` major into every consumer). The only runtime dependencies are the
4169
+ * audited, pure-JS `@noble/curves` (secp256k1) and `@noble/hashes` (keccak256), which run
4170
+ * unchanged in browsers, MV3 service workers, Node and React Native/Hermes.
4171
+ *
4172
+ * Every function here is pinned byte-for-byte to the signatures produced by `@gala-chain/api`
4173
+ * (`serialize`, `signatures.getSignature`), `@gala-chain/connect`, the Gala Wallet extension and
4174
+ * frontend_v2 by the golden-vector suite in `tests/signing-golden.test.ts`.
4175
+ */
4176
+ const textEncoder$1 = new TextEncoder();
4177
+ function toDeterministicNode(node) {
4178
+ if (node === null || node === void 0) return node;
4179
+ if (node instanceof RegExp) return node.toString();
4180
+ const candidate = node;
4181
+ return typeof candidate.toJSON === "function" ? Reflect.apply(candidate.toJSON, node, []) : node;
4182
+ }
4183
+ function deterministicNode(rawNode) {
4184
+ const node = toDeterministicNode(rawNode);
4185
+ if (node === void 0) return void 0;
4186
+ if (typeof node !== "object" || node === null) return JSON.stringify(node);
4187
+ if (Array.isArray(node)) return `[${node.map((item) => deterministicNode(item) ?? "null").join(",")}]`;
4188
+ const record = node;
4189
+ const members = [];
4190
+ for (const key of Object.keys(record).sort()) {
4191
+ const value = deterministicNode(record[key]);
4192
+ if (value === void 0) continue;
4193
+ members.push(`${JSON.stringify(key)}:${value}`);
4194
+ }
4195
+ return `{${members.join(",")}}`;
4196
+ }
4197
+ /**
4198
+ * GalaChain's deterministic JSON: object keys sorted (UTF-16 code-unit order) at every depth, no
4199
+ * whitespace, `undefined`/function members dropped, `toJSON()` honored. Byte-identical to
4200
+ * `serialize` from `@gala-chain/api` / `json-stringify-deterministic` for every JSON-shaped
4201
+ * value, which is all a DTO ever is.
4202
+ *
4203
+ * @throws TypeError for circular structures and BigInt values (as `JSON.stringify` does) and
4204
+ * when the value itself has no JSON representation (`undefined`, a function, a symbol).
4205
+ */
4206
+ function serialize(value) {
4207
+ JSON.stringify(value);
4208
+ const result = deterministicNode(value);
4209
+ if (result === void 0) throw new TypeError("Cannot serialize a value with no JSON representation.");
4210
+ return result;
4211
+ }
4212
+ /**
4213
+ * The exact string GalaChain hashes for a DTO: the (optional, string) `prefix` field PREPENDED to
4214
+ * the deterministic serialization of the DTO WITHOUT `signature`, `multisig`, `trace` and `prefix`.
4215
+ * Equivalent to `@gala-chain/api` 3.x `getPayloadToSign` for every non-EIP-712 DTO.
4216
+ */
4217
+ function getPayloadToSign(dto) {
4218
+ const { prefix, ...rest } = dto;
4219
+ delete rest["signature"];
4220
+ delete rest["multisig"];
4221
+ delete rest["trace"];
4222
+ return (typeof prefix === "string" ? prefix : "") + serialize(rest);
4223
+ }
2587
4224
  const ETHEREUM_SIGNED_MESSAGE = String.fromCharCode(25) + "Ethereum Signed Message:\n";
2588
- /** The EIP-191 header for `dto`'s canonical JSON (UTF-8 byte length; any `prefix` on it is ignored). */
4225
+ /**
4226
+ * The EIP-191 header for `message`: `"\x19Ethereum Signed Message:\n"` + the message's UTF-8 BYTE
4227
+ * length. This is what a wallet's `personal_sign` prepends, and what the chain recomputes when it
4228
+ * recovers a personal-signed DTO.
4229
+ */
4230
+ function calculatePersonalSignPrefix(message) {
4231
+ return ETHEREUM_SIGNED_MESSAGE + String(textEncoder$1.encode(message).length);
4232
+ }
4233
+ /**
4234
+ * The EIP-191 header for `serialize(dto)` (any `prefix` already on the DTO is ignored). This is
4235
+ * deliberately the same string the signers hand a wallet to sign, so the header's length can never
4236
+ * disagree with the signed message. Wallet-neutral: it is the value both a browser wallet's
4237
+ * `personal_sign` and Gala Wallet's `gala_signChainDto` (personal-sign scheme) prepend.
4238
+ */
2589
4239
  function personalSignPrefixFor(dto) {
2590
4240
  const withoutPrefix = { ...dto };
2591
4241
  delete withoutPrefix["prefix"];
2592
- const message = (0, _gala_chain_api.serialize)(withoutPrefix);
2593
- return ETHEREUM_SIGNED_MESSAGE + String(new TextEncoder().encode(message).length);
4242
+ return calculatePersonalSignPrefix(serialize(withoutPrefix));
4243
+ }
4244
+ /**
4245
+ * The `@gala-chain/connect`-style prefix frontend_v2 puts in the body of an EIP-712 write: the
4246
+ * EIP-191 header whose number counts the prefix string ITSELF plus the body (the UTF-8 length of
4247
+ * `getPayloadToSign({ ...dto, prefix })`), found by fixpoint because adding the prefix changes the
4248
+ * length (a body of 99 bytes settles on 101).
4249
+ *
4250
+ * It is NOT a personal-sign prefix (a personal-sign write with this value recovers a phantom
4251
+ * signer; use `personalSignPrefixFor`). Under EIP-712 the field is an untyped extra that the
4252
+ * typed-data hash ignores and defi-backend recomputes, so it only exists to keep the submitted
4253
+ * body byte-identical to what frontend_v2 has always sent.
4254
+ */
4255
+ function fixpointPrefixFor(dto) {
4256
+ const withoutPrefix = { ...dto };
4257
+ delete withoutPrefix["prefix"];
4258
+ let payloadLength = textEncoder$1.encode(getPayloadToSign(withoutPrefix)).length;
4259
+ let prefix = ETHEREUM_SIGNED_MESSAGE + String(payloadLength);
4260
+ let previousLength = -1;
4261
+ while (payloadLength !== previousLength) {
4262
+ previousLength = payloadLength;
4263
+ prefix = ETHEREUM_SIGNED_MESSAGE + String(payloadLength);
4264
+ payloadLength = textEncoder$1.encode(getPayloadToSign({
4265
+ ...withoutPrefix,
4266
+ prefix
4267
+ })).length;
4268
+ }
4269
+ return prefix;
4270
+ }
4271
+ function invalidPrivateKey(reason) {
4272
+ return new GSwapSDKError(`Invalid private key: ${reason}`, "VALIDATION_ERROR", { type: "INVALID_PRIVATE_KEY" });
4273
+ }
4274
+ const BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
4275
+ function decodeBase64(input) {
4276
+ if (!/^[A-Za-z0-9+/]+={0,2}$/u.test(input) || input.length % 4 !== 0) return void 0;
4277
+ const unpadded = input.replace(/=+$/u, "");
4278
+ const bytes = [];
4279
+ let buffer = 0;
4280
+ let bits = 0;
4281
+ for (const char of unpadded) {
4282
+ buffer = buffer << 6 | BASE64_ALPHABET.indexOf(char);
4283
+ bits += 6;
4284
+ if (bits >= 8) {
4285
+ bits -= 8;
4286
+ bytes.push(buffer >> bits & 255);
4287
+ }
4288
+ }
4289
+ return Uint8Array.from(bytes);
4290
+ }
4291
+ function decodeHex(input) {
4292
+ const bytes = new Uint8Array(input.length / 2);
4293
+ for (let index = 0; index < bytes.length; index += 1) bytes[index] = parseInt(input.slice(index * 2, index * 2 + 2), 16);
4294
+ return bytes;
4295
+ }
4296
+ /**
4297
+ * Normalizes a secp256k1 private key to a fresh 32-byte array (the caller may zero it). Also
4298
+ * accepts 32 raw bytes, which are validated and copied. For strings it accepts what `@gala-chain/api`'s
4299
+ * `normalizePrivateKey` accepted for a usable key: 64 hex characters (optionally `0x`-prefixed;
4300
+ * a 63-character hex key is left-padded with a zero) or a 44-character base64 string.
4301
+ *
4302
+ * @throws GSwapSDKError (`VALIDATION_ERROR`) for any other shape or an out-of-range scalar.
4303
+ */
4304
+ function normalizePrivateKey(input) {
4305
+ if (typeof input !== "string") {
4306
+ if (!(input instanceof Uint8Array) || input.length !== 32) throw invalidPrivateKey("expected exactly 32 bytes.");
4307
+ if (!_noble_curves_secp256k1.secp256k1.utils.isValidSecretKey(input)) throw invalidPrivateKey("not a valid secp256k1 scalar.");
4308
+ return Uint8Array.from(input);
4309
+ }
4310
+ const withoutPrefix = input.startsWith("0x") ? input.slice(2) : input;
4311
+ let key;
4312
+ if (/^[0-9a-fA-F]{63,64}$/u.test(withoutPrefix)) key = decodeHex(withoutPrefix.length === 63 ? `0${withoutPrefix}` : withoutPrefix);
4313
+ else if (withoutPrefix.length === 44) key = decodeBase64(withoutPrefix);
4314
+ if ((key === null || key === void 0 ? void 0 : key.length) !== 32) throw invalidPrivateKey("expected 64 hex characters or a 44-character base64 string.");
4315
+ if (!_noble_curves_secp256k1.secp256k1.utils.isValidSecretKey(key)) throw invalidPrivateKey("not a valid secp256k1 scalar.");
4316
+ return key;
4317
+ }
4318
+ /**
4319
+ * Signs a 32-byte keccak256 hash and returns GalaChain's recoverable signature: 64 hex `r` +
4320
+ * 64 hex `s` + `1b`/`1c` (`v`), lowercase, no `0x`. Deterministic (RFC 6979) and low-S, exactly
4321
+ * what `@gala-chain/api` and the Gala Wallet produce. A DER signature carries no recovery byte
4322
+ * and cannot be used: the chain RECOVERS the signer from the signature.
4323
+ */
4324
+ function signHash(privateKey, hash) {
4325
+ if (hash.length !== 32) throw new GSwapSDKError(`secp256k1 can only sign a 32-byte hash (got ${hash.length}).`, "VALIDATION_ERROR");
4326
+ const signature = _noble_curves_secp256k1.secp256k1.sign(hash, privateKey, {
4327
+ prehash: false,
4328
+ lowS: true
4329
+ });
4330
+ return (0, _noble_hashes_utils.bytesToHex)(signature.toBytes("compact")) + (signature.recovery === 1 ? "1c" : "1b");
2594
4331
  }
4332
+ /**
4333
+ * The lowercase `0x` Ethereum address (last 20 bytes of keccak256 of the uncompressed public key)
4334
+ * of a normalized private key. The Gala `eth|` identity of a key is this address.
4335
+ */
4336
+ function ethAddressOfPrivateKey(privateKey) {
4337
+ const publicKey = _noble_curves_secp256k1.secp256k1.getPublicKey(privateKey, false);
4338
+ return `0x${(0, _noble_hashes_utils.bytesToHex)((0, _noble_hashes_sha3.keccak_256)(publicKey.slice(1)).slice(-20))}`;
4339
+ }
4340
+ /** keccak256 of a string's UTF-8 bytes. */
4341
+ function keccak256Utf8(data) {
4342
+ return (0, _noble_hashes_sha3.keccak_256)(textEncoder$1.encode(data));
4343
+ }
4344
+ /**
4345
+ * GalaChain NATIVE signature over an already-serialized DTO: keccak256 of the exact bytes, with no
4346
+ * prefix and no signature envelope. The caller owns serialization, so the bytes shown to a user
4347
+ * and the bytes signed can never diverge.
4348
+ */
4349
+ function signSerializedNative(privateKey, serializedDto) {
4350
+ return signHash(privateKey, keccak256Utf8(serializedDto));
4351
+ }
4352
+ /**
4353
+ * GalaChain NATIVE signature for a DTO: keccak256 of `getPayloadToSign(dto)`, signed with
4354
+ * secp256k1. Byte-identical to `signatures.getSignature` from `@gala-chain/api`.
4355
+ */
4356
+ function signNative(privateKey, dto) {
4357
+ return signSerializedNative(privateKey, getPayloadToSign(dto));
4358
+ }
4359
+ /**
4360
+ * Personal-sign hash and signature: keccak256(`prefix` + `message`), both hashed VERBATIM with no
4361
+ * further EIP-191 wrapping. Passing `prefix = calculatePersonalSignPrefix(message)` reproduces a
4362
+ * wallet's `personal_sign` of `message`. Gala Wallet's `gala_signChainDto` (personal-sign
4363
+ * scheme) hashes `prefix` + the exact string it is handed, which can differ from the bare message
4364
+ * (see the `signatureOverPrefixlessBody` vectors); that quirk is why the two inputs stay separate.
4365
+ */
4366
+ function signPersonalMessage(privateKey, prefix, message) {
4367
+ return signHash(privateKey, keccak256Utf8(prefix + message));
4368
+ }
4369
+
4370
+ //#endregion
4371
+ //#region src/classes/signers.ts
2595
4372
  function getWalletProvider() {
2596
4373
  var _globalObject$window;
2597
4374
  const wallet = (_globalObject$window = globalThis.window) === null || _globalObject$window === void 0 ? void 0 : _globalObject$window.gala;
@@ -2600,7 +4377,7 @@ function getWalletProvider() {
2600
4377
  }
2601
4378
  function getNativeSignature(value) {
2602
4379
  if (typeof value !== "string") throw new GSwapSDKError("Wallet returned an invalid signature.", "INVALID_SIGNATURE");
2603
- if (!/^[0-9a-fA-F]{130}$/u.test(value)) throw new GSwapSDKError("Wallet returned an invalid native signature.", "INVALID_SIGNATURE");
4380
+ if (!/^(?:0x)?[0-9a-fA-F]{130}$/u.test(value)) throw new GSwapSDKError("Wallet returned an invalid native signature.", "INVALID_SIGNATURE");
2604
4381
  return value;
2605
4382
  }
2606
4383
  function getPersonalSignature(value) {
@@ -2613,19 +4390,48 @@ function normalizeEthereumAccount(address, allowGalaPrefix) {
2613
4390
  type: "INVALID_WALLET_ADDRESS",
2614
4391
  value: address
2615
4392
  });
2616
- return `0x${normalized.slice(2).toLowerCase()}`;
4393
+ return normalized;
2617
4394
  }
2618
- function getErrorMessage(error) {
2619
- if (error instanceof Error) return error.message;
2620
- if (typeof error === "string") return error;
2621
- if (typeof error === "object" && error !== null && "message" in error) {
2622
- const message = error.message;
2623
- return typeof message === "string" ? message : "";
2624
- }
2625
- return "";
4395
+ /**
4396
+ * The account handed to Gala Wallet as the signer address. An Ethereum account is held to the
4397
+ * same strict check as ever (`0x` plus 40 hex digits; `eth|` plus 40 hex digits, with or without
4398
+ * `0x`, is normalized to it). A GalaChain id the wallet can also be asked to sign for is passed
4399
+ * through verbatim: `client|` plus 24 hex digits, or the bare 24 hex digits that frontend_v2 keeps
4400
+ * in `state.user.wallet` after a direct `client|` login. Nothing else is accepted.
4401
+ */
4402
+ function normalizeGalaWalletAccount(address) {
4403
+ const eth = /^eth\|(?:0x)?([0-9a-fA-F]{40})$/u.exec(address);
4404
+ if ((eth === null || eth === void 0 ? void 0 : eth[1]) !== void 0) return `0x${eth[1]}`;
4405
+ if (/^0x[0-9a-fA-F]{40}$/u.test(address) || /^(?:client\|)?[0-9a-fA-F]{24}$/u.test(address)) return address;
4406
+ throw new GSwapSDKError("Wallet address must be the selected Ethereum account (0x plus 40 hex digits, or eth|...) or a GalaChain id (client|... or 24 hex digits).", "VALIDATION_ERROR", {
4407
+ type: "INVALID_WALLET_ADDRESS",
4408
+ value: address
4409
+ });
4410
+ }
4411
+ /** An identity Gala Wallet can act as: an account form above, any `eth|`/`client|` alias, verbatim. */
4412
+ function assertWalletIdentity(identity) {
4413
+ if (!/^(?:eth\|(?:0x)?[0-9a-fA-F]{40}|0x[0-9a-fA-F]{40}|(?:client\|)?[0-9a-fA-F]{24})$/u.test(identity)) throw new GSwapSDKError("selectAddress must be an eth| or client| identity, or an account in one of the accepted forms.", "VALIDATION_ERROR", {
4414
+ type: "INVALID_WALLET_ADDRESS",
4415
+ value: identity
4416
+ });
2626
4417
  }
2627
- function isUnsupportedNativeSchemeError(error) {
2628
- return /unsupported|unknown scheme/i.test(getErrorMessage(error));
4418
+ /** Provider-wide queue: `setAddress` + the signature request must not interleave across signers. */
4419
+ const walletIdentityQueue = /* @__PURE__ */ new WeakMap();
4420
+ /**
4421
+ * Runs `signWith` with `identity` selected on a Gala Wallet that supports `setAddress`. Gala
4422
+ * Wallet holds ONE current account, so two signatures for different identities in flight at once
4423
+ * would sign as whichever selection came last; the pair (select, sign) is therefore serialized
4424
+ * per provider. A failed selection fails the signature (nothing is requested).
4425
+ */
4426
+ function withSelectedIdentity(wallet, identity, signWith) {
4427
+ const { setAddress } = wallet;
4428
+ if (identity === void 0 || typeof setAddress !== "function") return signWith();
4429
+ const run = (walletIdentityQueue.get(wallet) ?? Promise.resolve()).catch(() => void 0).then(async () => {
4430
+ await setAddress.call(wallet, identity);
4431
+ return signWith();
4432
+ });
4433
+ walletIdentityQueue.set(wallet, run);
4434
+ return run;
2629
4435
  }
2630
4436
  function personalSign(provider, dto, walletAddress, methodName) {
2631
4437
  const prefix = personalSignPrefixFor(dto);
@@ -2634,10 +4440,10 @@ function personalSign(provider, dto, walletAddress, methodName) {
2634
4440
  prefix
2635
4441
  };
2636
4442
  const params = methodName !== void 0 ? [
2637
- (0, _gala_chain_api.serialize)(payload),
4443
+ serialize(payload),
2638
4444
  walletAddress,
2639
4445
  methodName
2640
- ] : [(0, _gala_chain_api.serialize)(dto), walletAddress];
4446
+ ] : [serialize(dto), walletAddress];
2641
4447
  return provider.request({
2642
4448
  method: methodName !== void 0 ? "gala_signChainDto" : "personal_sign",
2643
4449
  params
@@ -2656,9 +4462,13 @@ function personalSign(provider, dto, walletAddress, methodName) {
2656
4462
  * byte-for-byte equivalent (same recovered address from the same signature against both
2657
4463
  * constructions) before this shipped, since gswap-sdk deliberately has no ethers dependency.
2658
4464
  */
2659
- function signEIP712(provider, methodName, dto, walletAddress) {
4465
+ async function signEIP712(provider, methodName, dto, walletAddress) {
2660
4466
  const { completedDto, types } = buildEIP712Payload(methodName, dto);
2661
- const prefix = personalSignPrefixFor(completedDto);
4467
+ const domain = eip712DomainFor(await provider.request({
4468
+ method: "eth_chainId",
4469
+ params: []
4470
+ }));
4471
+ const prefix = fixpointPrefixFor(completedDto);
2662
4472
  const message = {
2663
4473
  ...completedDto,
2664
4474
  prefix
@@ -2671,7 +4481,7 @@ function signEIP712(provider, methodName, dto, walletAddress) {
2671
4481
  type: "uint256"
2672
4482
  }];
2673
4483
  const payload = {
2674
- domain: EIP712_DOMAIN,
4484
+ domain,
2675
4485
  types: {
2676
4486
  ...types,
2677
4487
  EIP712Domain: eip712Domain
@@ -2679,15 +4489,16 @@ function signEIP712(provider, methodName, dto, walletAddress) {
2679
4489
  primaryType: methodName,
2680
4490
  message
2681
4491
  };
2682
- return provider.request({
4492
+ const value = await provider.request({
2683
4493
  method: "eth_signTypedData_v4",
2684
4494
  params: [walletAddress, JSON.stringify(payload)]
2685
- }).then((value) => ({
4495
+ });
4496
+ return {
2686
4497
  ...message,
4498
+ signature: getPersonalSignature(value),
2687
4499
  types,
2688
- domain: EIP712_DOMAIN,
2689
- signature: getPersonalSignature(value)
2690
- }));
4500
+ domain
4501
+ };
2691
4502
  }
2692
4503
  /**
2693
4504
  * A GalaChain native signer backed by a secp256k1 private key.
@@ -2696,7 +4507,7 @@ function signEIP712(provider, methodName, dto, walletAddress) {
2696
4507
  * or EIP-712 metadata. The private-key identity is recovered by the chain.
2697
4508
  */
2698
4509
  var PrivateKeySigner = class {
2699
- keyBuffer;
4510
+ privateKey;
2700
4511
  /**
2701
4512
  * Creates a native signer from a hex or base64 secp256k1 private key.
2702
4513
  *
@@ -2707,7 +4518,7 @@ var PrivateKeySigner = class {
2707
4518
  * ```
2708
4519
  */
2709
4520
  constructor(privateKey) {
2710
- this.keyBuffer = _gala_chain_api.signatures.normalizePrivateKey(privateKey);
4521
+ this.privateKey = normalizePrivateKey(privateKey);
2711
4522
  }
2712
4523
  /**
2713
4524
  * Signs the DTO with GalaChain native signing.
@@ -2722,7 +4533,7 @@ var PrivateKeySigner = class {
2722
4533
  * ```
2723
4534
  */
2724
4535
  signObject(_methodName, dto) {
2725
- const signature = _gala_chain_api.signatures.getSignature(dto, this.keyBuffer);
4536
+ const signature = signNative(this.privateKey, dto);
2726
4537
  return Promise.resolve({
2727
4538
  ...dto,
2728
4539
  signature
@@ -2730,18 +4541,66 @@ var PrivateKeySigner = class {
2730
4541
  }
2731
4542
  };
2732
4543
  /**
2733
- * A Gala Wallet signer using native GalaChain signing, with an opt-in personal-sign mode and
2734
- * automatic fallback for older wallets.
4544
+ * Creates the native-signing equivalent of {@link PrivateKeySigner} for apps that must not hold a
4545
+ * key in a long-lived object (an extension that reads its session key at sign time).
4546
+ *
4547
+ * `getPrivateKey` is called once per `signObject` call, never cached, never shared between calls
4548
+ * (concurrent signatures each fetch their own key), and the normalized key bytes are zeroed
4549
+ * before `signObject` settles, also on failure. A rejection or exception from `getPrivateKey` is
4550
+ * passed through unchanged, so the app's own "wallet is locked" error reaches its caller; a key
4551
+ * that is not a valid secp256k1 key string is a `VALIDATION_ERROR` (`INVALID_PRIVATE_KEY`).
4552
+ * Signatures are byte-identical to `new PrivateKeySigner(key).signObject(...)`.
4553
+ *
4554
+ * A JavaScript string cannot be wiped; the provider owns the lifetime of the string it returns.
4555
+ *
4556
+ * @example
4557
+ * ```typescript
4558
+ * const signer = createLazyPrivateKeySigner(
4559
+ * () => SecurityHelper.getSessionSecret(SecurityHelper.SESSION_PRIVATE_KEY_KEY),
4560
+ * { expectedAddress: account.address },
4561
+ * );
4562
+ * const gswap = new GSwap({ signer });
4563
+ * ```
4564
+ */
4565
+ function createLazyPrivateKeySigner(getPrivateKey, options = {}) {
4566
+ const expected = options.expectedAddress;
4567
+ if (expected !== void 0) normalizeEthereumAccount(expected, true);
4568
+ const expectedLower = expected === null || expected === void 0 ? void 0 : expected.replace(/^eth\|/u, "").toLowerCase();
4569
+ return { async signObject(_methodName, dto) {
4570
+ const provided = await getPrivateKey();
4571
+ if (typeof provided !== "string") throw new GSwapSDKError("Invalid private key: the provider returned no key.", "VALIDATION_ERROR", { type: "INVALID_PRIVATE_KEY" });
4572
+ const key = normalizePrivateKey(provided);
4573
+ try {
4574
+ if (expectedLower !== void 0 && ethAddressOfPrivateKey(key) !== expectedLower) throw new GSwapSDKError("The signing key does not belong to the expected account.", "SIGNER_ADDRESS_MISMATCH");
4575
+ return {
4576
+ ...dto,
4577
+ signature: signNative(key, dto)
4578
+ };
4579
+ } finally {
4580
+ key.fill(0);
4581
+ }
4582
+ } };
4583
+ }
4584
+ /**
4585
+ * A Gala Wallet signer using native GalaChain signing, with an opt-in personal-sign mode.
4586
+ *
4587
+ * The selected Ethereum account is passed to Gala Wallet. There is NO automatic fallback: a
4588
+ * wallet that rejects `native` surfaces its error to the caller, because the only alternative,
4589
+ * `personal-sign`, silently recovers a phantom signer on every released Gala Wallet.
2735
4590
  *
2736
- * The selected Ethereum account is passed to Gala Wallet. Personal-sign resolves the bare
2737
- * `eth|` identity; the legacy EIP-712 `eth_signTypedData` path is not used. The fallback calls
2738
- * `gala_signChainDto([serialize({ ...dto, prefix }), walletAddress, methodName])`.
4591
+ * `scheme: 'personal-sign'` calls
4592
+ * `gala_signChainDto([serialize({ ...dto, prefix }), walletAddress, methodName])` and REQUIRES
4593
+ * a wallet build that contains wallet_extension commit 3ad7ae6 (strips the embedded `prefix`
4594
+ * field and hashes `prefix` + the prefix-less canonical body, the chain's scheme). Wallets
4595
+ * released before that fix hash the string with the `prefix` field still inside it and the
4596
+ * signature recovers to an address nobody holds, so only opt in once the fixed wallet is
4597
+ * released. See SIGNING-PARITY.md.
2739
4598
  */
2740
4599
  var GalaWalletSigner = class {
2741
4600
  /** The wallet address supplied to Gala Wallet for each signing request. */
2742
4601
  walletAddress;
2743
- /** The scheme currently used, including any automatic native-to-personal fallback. */
2744
- effectiveScheme;
4602
+ /** The signing scheme used for every request; it never changes after construction. */
4603
+ scheme;
2745
4604
  /**
2746
4605
  * Creates a Gala Wallet signer.
2747
4606
  *
@@ -2751,14 +4610,23 @@ var GalaWalletSigner = class {
2751
4610
  * const signed = await signer.signObject('RequestTrade', { token0: 'GALA', token1: 'GUSDC' });
2752
4611
  * ```
2753
4612
  *
2754
- * @param walletAddress - The selected Ethereum account (`0x` plus 40 hex digits); Gala Wallet's
2755
- * `eth|0x...` form is also accepted and normalized.
2756
- * @param options - Select native signing or personal-sign explicitly.
4613
+ * @param walletAddress - The account passed to Gala Wallet as the signer address. An Ethereum
4614
+ * account is `0x` plus 40 hex digits (Gala Wallet's `eth|...` form is normalized to it and held
4615
+ * to the same check); a GalaChain id is `client|` plus 24 hex digits, or the bare 24 hex digits
4616
+ * frontend_v2 keeps for a direct `client|` login, and is passed through unchanged.
4617
+ * @param options - Select native signing (default) or personal-sign explicitly.
4618
+ * `'personal-sign'` requires a Gala Wallet build with wallet_extension commit 3ad7ae6.
4619
+ * `selectAddress` is the identity (`client|...` / `eth|...`) Gala Wallet is switched to with
4620
+ * `window.gala.setAddress` before every signature, when the wallet offers it.
2757
4621
  */
2758
4622
  constructor(walletAddress, options = {}) {
2759
- this.walletAddress = normalizeEthereumAccount(walletAddress, true);
2760
- this.effectiveScheme = options.scheme ?? "native";
4623
+ this.walletAddress = normalizeGalaWalletAccount(walletAddress);
4624
+ this.scheme = options.scheme ?? "native";
4625
+ if (options.selectAddress !== void 0) assertWalletIdentity(options.selectAddress);
4626
+ this.selectAddress = options.selectAddress;
2761
4627
  }
4628
+ /** The identity Gala Wallet is switched to before each signature, if one was given. */
4629
+ selectAddress;
2762
4630
  /**
2763
4631
  * Signs a DTO through `gala_signChainDto`.
2764
4632
  *
@@ -2774,12 +4642,12 @@ var GalaWalletSigner = class {
2774
4642
  */
2775
4643
  async signObject(methodName, dto) {
2776
4644
  const wallet = getWalletProvider();
2777
- if (this.effectiveScheme === "personal-sign") return personalSign(wallet, dto, this.walletAddress, methodName);
2778
- try {
4645
+ return withSelectedIdentity(wallet, this.selectAddress, async () => {
4646
+ if (this.scheme === "personal-sign") return personalSign(wallet, dto, this.walletAddress, methodName);
2779
4647
  const signature = await wallet.request({
2780
4648
  method: "gala_signChainDto",
2781
4649
  params: [
2782
- (0, _gala_chain_api.serialize)(dto),
4650
+ serialize(dto),
2783
4651
  this.walletAddress,
2784
4652
  methodName,
2785
4653
  "native"
@@ -2789,11 +4657,7 @@ var GalaWalletSigner = class {
2789
4657
  ...dto,
2790
4658
  signature: getNativeSignature(signature)
2791
4659
  };
2792
- } catch (error) {
2793
- if (!isUnsupportedNativeSchemeError(error)) throw error;
2794
- this.effectiveScheme = "personal-sign";
2795
- return personalSign(wallet, dto, this.walletAddress, methodName);
2796
- }
4660
+ });
2797
4661
  }
2798
4662
  };
2799
4663
  /**
@@ -2854,11 +4718,490 @@ var BrowserWalletSigner = class {
2854
4718
  }
2855
4719
  };
2856
4720
 
4721
+ //#endregion
4722
+ //#region src/utils/chain_dto_signing.ts
4723
+ /**
4724
+ * String-key signing entry points for apps that hold a private key only briefly (the Gala Wallet
4725
+ * extension reads its session key at sign time and must not keep it in a long-lived object).
4726
+ *
4727
+ * These are thin, byte-exact ports of the functions the wallet shipped in
4728
+ * `wallet_extension/src/helpers/wallet/signing.ts` (including the fail-closed personal-sign
4729
+ * payload resolver from its commit 3ad7ae6), re-expressed over the SDK's own primitives. Every
4730
+ * function takes the key as a string (`0x`-optional hex, or base64) or as bytes, normalizes it per
4731
+ * call, and zeroes the normalized copy before returning, so nothing in this module retains a key.
4732
+ * A JavaScript string cannot be wiped; the caller owns the lifetime of the string it passes in.
4733
+ */
4734
+ /**
4735
+ * Runs `action` with a private normalized copy of the key, zeroed afterwards (also when `action`
4736
+ * throws). Bytes given by the caller are copied, never modified: the caller owns them.
4737
+ */
4738
+ function withPrivateKey(privateKey, action) {
4739
+ const key = normalizePrivateKey(privateKey);
4740
+ try {
4741
+ return action(key);
4742
+ } finally {
4743
+ key.fill(0);
4744
+ }
4745
+ }
4746
+ /**
4747
+ * GalaChain NATIVE signature over an already-serialized DTO: keccak256 of the exact bytes with no
4748
+ * prefix and no envelope (wallet `signChainDtoNative`). The caller owns serialization, so the
4749
+ * bytes a user reviewed and the bytes signed cannot diverge.
4750
+ *
4751
+ * @returns 130 lowercase hex characters, `r || s || v` with `v` = `1b`/`1c`, no `0x`.
4752
+ */
4753
+ function signChainDtoNative(privateKey, serializedDto) {
4754
+ return withPrivateKey(privateKey, (key) => signSerializedNative(key, serializedDto));
4755
+ }
4756
+ /**
4757
+ * The personal-sign hash primitive (wallet `signChainDtoHash`): keccak256(`prefix` + `message`),
4758
+ * both verbatim, with no further EIP-191 wrap. `message` must be the DTO's serialization WITHOUT
4759
+ * its `prefix` field; callers holding a dApp-supplied string go through `signChainDtoPersonalSign`,
4760
+ * which derives both inputs from it.
4761
+ */
4762
+ function signChainDtoHash(privateKey, prefix, message) {
4763
+ return withPrivateKey(privateKey, (key) => signPersonalMessage(key, prefix, message));
4764
+ }
4765
+ const PERSONAL_SIGN_HEADER = String.fromCharCode(25) + "Ethereum Signed Message:\n";
4766
+ const PERSONAL_SIGN_LENGTH_PATTERN = /^(?:0|[1-9][0-9]*)$/u;
4767
+ function isPersonalSignHeader(prefix) {
4768
+ return prefix.startsWith(PERSONAL_SIGN_HEADER) && PERSONAL_SIGN_LENGTH_PATTERN.test(prefix.slice(26));
4769
+ }
4770
+ /**
4771
+ * Top-level fields that would make the chain (`getPayloadToSign`) or defi-backend's personal-sign
4772
+ * verifier hash something other than `prefix + serialize(dto minus prefix)`. An unsigned DTO from
4773
+ * a dApp never legitimately carries any of them, so they fail closed. Order is part of the error
4774
+ * message the wallet shows its user.
4775
+ */
4776
+ const PERSONAL_SIGN_FORBIDDEN_FIELDS = [
4777
+ "signature",
4778
+ "multisig",
4779
+ "trace",
4780
+ "types",
4781
+ "domain",
4782
+ "stringsInstructions"
4783
+ ];
4784
+ function refusePersonalSign(message, type) {
4785
+ throw new GSwapSDKError(message, "INVALID_PERSONAL_SIGN_DTO", { type });
4786
+ }
4787
+ /**
4788
+ * Derives what to hash for a `gala_signChainDto` personal-sign request from the exact string the
4789
+ * dApp sent (and the approval card displayed): wallet `resolvePersonalSignChainDtoPayload`.
4790
+ *
4791
+ * The dApp submits `{ ...dto, prefix, signature }`, which the chain recovers from
4792
+ * `keccak256(prefix + serialize(dto WITHOUT prefix))`. So when the string embeds a `prefix`
4793
+ * field, that field is removed and the remainder re-serialized; the prefix used is the dApp's own
4794
+ * value byte for byte. A string with no `prefix` field is hashed verbatim with an empty prefix
4795
+ * (chain-equivalent to a native signature of that exact string).
4796
+ *
4797
+ * Fails closed (throws `GSwapSDKError` `INVALID_PERSONAL_SIGN_DTO`, message text identical to the
4798
+ * wallet's) on: malformed JSON; JSON that is not a plain object; a `prefix` that is not an EIP-191
4799
+ * "Ethereum Signed Message" header; any signing-envelope field (`signature`, `multisig`, `trace`,
4800
+ * `types`, `domain`, `stringsInstructions`); and a string that is not already the canonical
4801
+ * serialization of what it parses to (duplicate keys, lossy numbers, key-order tricks the card
4802
+ * would render differently). Stripping `prefix` is therefore the ONLY byte change between the
4803
+ * reviewed string and the signed message.
4804
+ */
4805
+ function resolvePersonalSignChainDtoPayload(data) {
4806
+ let parsed;
4807
+ try {
4808
+ parsed = JSON.parse(data);
4809
+ } catch {
4810
+ return refusePersonalSign("Personal-sign DTO is not valid JSON", "NOT_JSON");
4811
+ }
4812
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return refusePersonalSign("Personal-sign DTO must be a JSON object", "NOT_OBJECT");
4813
+ const dto = parsed;
4814
+ if (!Object.prototype.hasOwnProperty.call(dto, "prefix")) return {
4815
+ prefix: "",
4816
+ message: data
4817
+ };
4818
+ const { prefix, ...withoutPrefix } = dto;
4819
+ if (typeof prefix !== "string" || !isPersonalSignHeader(prefix)) return refusePersonalSign("Personal-sign DTO prefix is not an EIP-191 \"Ethereum Signed Message\" header", "BAD_PREFIX");
4820
+ const forbidden = PERSONAL_SIGN_FORBIDDEN_FIELDS.filter((field) => Object.prototype.hasOwnProperty.call(withoutPrefix, field));
4821
+ if (forbidden.length > 0) return refusePersonalSign(`Personal-sign DTO carries signing-envelope field(s): ${forbidden.join(", ")}`, "ENVELOPE_FIELDS");
4822
+ if (serialize(dto) !== data) return refusePersonalSign("Personal-sign DTO is not in canonical serialized form", "NOT_CANONICAL");
4823
+ return {
4824
+ prefix,
4825
+ message: serialize(withoutPrefix)
4826
+ };
4827
+ }
4828
+ /**
4829
+ * Signs a GalaChain personal-sign scheme DTO from the exact string a dApp sent through
4830
+ * `gala_signChainDto` (wallet `signChainDtoPersonalSign`). See
4831
+ * `resolvePersonalSignChainDtoPayload` for what is signed and what is refused; the signature
4832
+ * recovers, under the chain's `getPayloadToSign`, to this key for the DTO the dApp submits with
4833
+ * its `prefix`, and equals a `personal_sign` of the bare body when that prefix is the EIP-191
4834
+ * header of the bare body's byte length.
4835
+ *
4836
+ * @throws GSwapSDKError (`INVALID_PERSONAL_SIGN_DTO`) before any signing when the input is refused.
4837
+ */
4838
+ function signChainDtoPersonalSign(privateKey, data) {
4839
+ const { prefix, message } = resolvePersonalSignChainDtoPayload(data);
4840
+ return signChainDtoHash(privateKey, prefix, message);
4841
+ }
4842
+ /**
4843
+ * Raw object signing (wallet `signObject`), used for transfers, bridge and fee-dry-run DTOs the
4844
+ * wallet signs itself. Removes ONLY `signature`, signs `keccak256(serialize(rest))` natively
4845
+ * (no `prefix` handling, and `trace` / `multisig` stay in the signed bytes, unlike
4846
+ * `signNative`), and returns `{ signature, ...rest }` with the signature as the FIRST key, exactly
4847
+ * like the wallet. Callers that must not sign extra fields strip them first.
4848
+ */
4849
+ function signRawObject(privateKey, obj) {
4850
+ const rest = { ...obj };
4851
+ delete rest["signature"];
4852
+ return {
4853
+ signature: signChainDtoNative(privateKey, serialize(rest)),
4854
+ ...rest
4855
+ };
4856
+ }
4857
+ /**
4858
+ * Drops the `EIP712Domain` entry from a dApp-supplied types map (a typed-data encoder derives the
4859
+ * domain type itself and rejects an explicit one). The order of the remaining types is preserved.
4860
+ */
4861
+ function stripEip712DomainType(types) {
4862
+ const stripped = {};
4863
+ for (const typeName of Object.keys(types)) if (typeName !== "EIP712Domain") stripped[typeName] = types[typeName];
4864
+ return stripped;
4865
+ }
4866
+ /**
4867
+ * `collection$category$type$additionalKey` for a token-class object (wallet `decodeTokenData`).
4868
+ * Lenient on purpose: any missing or non-string part becomes `''`, so a display layer never
4869
+ * throws on odd input.
4870
+ */
4871
+ function tokenClassDollarKey(token) {
4872
+ const part = (field) => {
4873
+ const value = token === null || token === void 0 ? void 0 : token[field];
4874
+ return typeof value === "string" ? value : "";
4875
+ };
4876
+ return `${part("collection")}$${part("category")}$${part("type")}$${part("additionalKey")}`;
4877
+ }
4878
+ /**
4879
+ * A quantity in the form GalaChain's `IsBigNumber` validator accepts: FIXED notation, no leading
4880
+ * zeros, no trailing decimal zeros (`BigNumber#toFixed()`; never `toString()`, which turns
4881
+ * `0.00000001` into `1e-8`, and never a JS number in between).
4882
+ *
4883
+ * @throws GSwapSDKError (`VALIDATION_ERROR`) for a value that is not a finite number. The wallet's
4884
+ * `cleanNumber` returned the string `'NaN'` there; signing that text can only fail on chain.
4885
+ */
4886
+ function canonicalDecimalString(value) {
4887
+ let parsed = new bignumber_js.default(NaN);
4888
+ try {
4889
+ parsed = new bignumber_js.default(value);
4890
+ } catch {}
4891
+ if (!parsed.isFinite()) throw new GSwapSDKError(`Invalid decimal quantity: ${String(value)}`, "VALIDATION_ERROR", { type: "INVALID_DECIMAL" });
4892
+ return parsed.toFixed();
4893
+ }
4894
+
4895
+ //#endregion
4896
+ //#region src/utils/typed_data.ts
4897
+ /**
4898
+ * EIP-712 (`eth_signTypedData_v4`) hashing and signing for ARBITRARY dApp typed data, built on the
4899
+ * SDK's own primitives (`@noble/curves`, `@noble/hashes`) so a wallet can sign a dApp's typed-data
4900
+ * approval without ethers. It replaces the wallet's `new ethers.Wallet(key).signTypedData(...)`
4901
+ * and is pinned to it: the golden suite reproduces every fixture signature exactly and a seeded
4902
+ * differential fuzz compares the digest and signature with ethers for every supported type.
4903
+ *
4904
+ * Compared with ethers this encoder is deliberately STRICTER (it fails closed rather than coerce):
4905
+ * `bool` accepts only a boolean, numbers accept only safe integers / decimal or `0x` strings,
4906
+ * strings must be well-formed UTF-16, struct values must be objects whose fields are their own
4907
+ * properties, and every struct, field and type name must be a plain identifier. Anything ethers
4908
+ * accepts that is NOT in that set is refused here instead of being signed in a different shape
4909
+ * from what a user reviewed.
4910
+ */
4911
+ const textEncoder = new TextEncoder();
4912
+ const IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/u;
4913
+ const TYPE_SYNTAX = /^([A-Za-z_$][A-Za-z0-9_$]*)((?:\[(?:0|[1-9][0-9]*)?\])*)$/u;
4914
+ const BIGINT_DECIMAL = /^-?[0-9]+$/u;
4915
+ const BIGINT_HEX = /^-?0x[0-9a-fA-F]+$/u;
4916
+ const MAX_UINT256 = (1n << 256n) - 1n;
4917
+ /** The EIP-712 domain members and their types, in the order ethers (and the spec) hash them. */
4918
+ const DOMAIN_FIELDS = [
4919
+ {
4920
+ name: "name",
4921
+ type: "string"
4922
+ },
4923
+ {
4924
+ name: "version",
4925
+ type: "string"
4926
+ },
4927
+ {
4928
+ name: "chainId",
4929
+ type: "uint256"
4930
+ },
4931
+ {
4932
+ name: "verifyingContract",
4933
+ type: "address"
4934
+ },
4935
+ {
4936
+ name: "salt",
4937
+ type: "bytes32"
4938
+ }
4939
+ ];
4940
+ function refuse(message, details) {
4941
+ throw new GSwapSDKError(`Invalid typed data: ${message}`, "INVALID_TYPED_DATA", details);
4942
+ }
4943
+ function concat(parts) {
4944
+ const out = new Uint8Array(parts.reduce((total, part) => total + part.length, 0));
4945
+ let offset = 0;
4946
+ for (const part of parts) {
4947
+ out.set(part, offset);
4948
+ offset += part.length;
4949
+ }
4950
+ return out;
4951
+ }
4952
+ function bigintToWord(value) {
4953
+ const word = /* @__PURE__ */ new Uint8Array(32);
4954
+ let rest = value < 0n ? value + (1n << 256n) : value;
4955
+ if (rest < 0n || rest > MAX_UINT256) return refuse("integer does not fit in 256 bits");
4956
+ for (let index = 31; index >= 0; index -= 1) {
4957
+ word[index] = Number(rest & 255n);
4958
+ rest >>= 8n;
4959
+ }
4960
+ return word;
4961
+ }
4962
+ function hexToBytes(hex) {
4963
+ const body = hex.slice(2);
4964
+ const out = new Uint8Array(body.length / 2);
4965
+ for (let index = 0; index < out.length; index += 1) out[index] = parseInt(body.slice(index * 2, index * 2 + 2), 16);
4966
+ return out;
4967
+ }
4968
+ function toBytes(value, what) {
4969
+ if (value instanceof Uint8Array) return value;
4970
+ if (typeof value === "string" && /^0x(?:[0-9a-fA-F]{2})*$/u.test(value)) return hexToBytes(value);
4971
+ return refuse(`${what} must be an even-length 0x hex string`);
4972
+ }
4973
+ function toBigInt(value, what) {
4974
+ if (typeof value === "bigint") return value;
4975
+ if (typeof value === "number") {
4976
+ if (!Number.isSafeInteger(value)) return refuse(`${what} must be a safe integer`);
4977
+ return BigInt(value);
4978
+ }
4979
+ if (typeof value === "string") {
4980
+ if (BIGINT_DECIMAL.test(value)) return BigInt(value);
4981
+ if (BIGINT_HEX.test(value)) return value.startsWith("-") ? -BigInt(value.slice(1)) : BigInt(value);
4982
+ }
4983
+ return refuse(`${what} must be an integer`);
4984
+ }
4985
+ function utf8(value, what) {
4986
+ if (typeof value !== "string") return refuse(`${what} must be a string`);
4987
+ for (let index = 0; index < value.length; index += 1) {
4988
+ const unit = value.charCodeAt(index);
4989
+ if (unit >= 55296 && unit <= 56319) {
4990
+ const next = value.charCodeAt(index + 1);
4991
+ if (!(next >= 56320 && next <= 57343)) return refuse(`${what} has an unpaired surrogate`);
4992
+ index += 1;
4993
+ } else if (unit >= 56320 && unit <= 57343) return refuse(`${what} has an unpaired surrogate`);
4994
+ }
4995
+ return textEncoder.encode(value);
4996
+ }
4997
+ /** EIP-55: a mixed-case address must carry the right checksum; all-lower / all-upper is accepted. */
4998
+ function addressBytes(value) {
4999
+ if (typeof value !== "string" || !/^0x[0-9a-fA-F]{40}$/u.test(value)) return refuse("address must be 0x followed by 40 hex digits");
5000
+ const digits = value.slice(2);
5001
+ if (/[A-F]/u.test(digits) && /[a-f]/u.test(digits)) {
5002
+ const hash = (0, _noble_hashes_sha3.keccak_256)(textEncoder.encode(digits.toLowerCase()));
5003
+ for (let index = 0; index < 40; index += 1) {
5004
+ const nibble = hash[index >> 1] >> (index % 2 === 0 ? 4 : 0) & 15;
5005
+ const char = digits.charAt(index);
5006
+ if (char !== (nibble >= 8 ? char.toUpperCase() : char.toLowerCase())) return refuse("address has a bad checksum");
5007
+ }
5008
+ }
5009
+ return hexToBytes(value);
5010
+ }
5011
+ /** The encoder for a non-struct, non-array type, or undefined when `base` is not one. */
5012
+ function leafEncoder(base) {
5013
+ if (base === "string") return (value) => (0, _noble_hashes_sha3.keccak_256)(utf8(value, "string"));
5014
+ if (base === "bytes") return (value) => (0, _noble_hashes_sha3.keccak_256)(toBytes(value, "bytes"));
5015
+ if (base === "bool") return (value) => {
5016
+ if (typeof value !== "boolean") return refuse("bool must be a boolean");
5017
+ return bigintToWord(value ? 1n : 0n);
5018
+ };
5019
+ if (base === "address") return (value) => concat([/* @__PURE__ */ new Uint8Array(12), addressBytes(value)]);
5020
+ const fixed = /^bytes([1-9][0-9]?)$/u.exec(base);
5021
+ if (fixed !== null) {
5022
+ const width = Number(fixed[1]);
5023
+ if (width > 32 || String(width) !== fixed[1]) return void 0;
5024
+ return (value) => {
5025
+ const bytes = toBytes(value, base);
5026
+ if (bytes.length !== width) return refuse(`${base} must be exactly ${width} bytes`);
5027
+ return concat([bytes, new Uint8Array(32 - width)]);
5028
+ };
5029
+ }
5030
+ const integer = /^(u?)int([1-9][0-9]*)$/u.exec(base);
5031
+ if (integer !== null) {
5032
+ const width = Number(integer[2]);
5033
+ if (width % 8 !== 0 || width > 256 || String(width) !== integer[2]) return void 0;
5034
+ const signed = integer[1] === "";
5035
+ const lower = signed ? -(1n << BigInt(width - 1)) : 0n;
5036
+ const upper = signed ? (1n << BigInt(width - 1)) - 1n : (1n << BigInt(width)) - 1n;
5037
+ return (value) => {
5038
+ const number = toBigInt(value, base);
5039
+ if (number < lower || number > upper) return refuse(`value out of bounds for ${base}`);
5040
+ return bigintToWord(number);
5041
+ };
5042
+ }
5043
+ }
5044
+ function parseType(type) {
5045
+ const match = TYPE_SYNTAX.exec(type);
5046
+ if (match === null) return refuse(`unsupported type "${type}"`);
5047
+ const dimensions = [...(match[2] ?? "").matchAll(/\[([0-9]*)\]/gu)].map((dimension) => dimension[1] === "" ? -1 : Number(dimension[1]));
5048
+ return {
5049
+ base: match[1],
5050
+ dimensions
5051
+ };
5052
+ }
5053
+ function parseFields(typeName, rawFields) {
5054
+ if (!Array.isArray(rawFields)) return refuse(`type ${typeName} must be a list of fields`);
5055
+ const seen = /* @__PURE__ */ new Set();
5056
+ return rawFields.map((raw) => {
5057
+ const field = raw;
5058
+ if (typeof (field === null || field === void 0 ? void 0 : field.name) !== "string" || typeof field.type !== "string") return refuse(`type ${typeName} has a field without a string name and type`);
5059
+ if (!IDENTIFIER.test(field.name)) return refuse(`invalid field name "${field.name}"`);
5060
+ if (seen.has(field.name)) return refuse(`duplicate field "${field.name}" in ${typeName}`);
5061
+ seen.add(field.name);
5062
+ const parsed = parseType(field.type);
5063
+ const base = parsed.base === "uint" || parsed.base === "int" ? `${parsed.base}256` : parsed.base;
5064
+ return {
5065
+ name: field.name,
5066
+ type: base + field.type.slice(parsed.base.length)
5067
+ };
5068
+ });
5069
+ }
5070
+ /**
5071
+ * Builds the struct encoder for a set of type definitions: validates the type graph (unknown or
5072
+ * circular references, duplicate fields) and infers the primary type as the single type no other
5073
+ * type references, exactly like ethers' `TypedDataEncoder`.
5074
+ */
5075
+ function createStructEncoder(rawTypes) {
5076
+ const names = Object.keys(rawTypes);
5077
+ const fields = /* @__PURE__ */ new Map();
5078
+ for (const name of names) {
5079
+ if (!IDENTIFIER.test(name)) return refuse(`invalid type name "${name}"`);
5080
+ fields.set(name, parseFields(name, rawTypes[name]));
5081
+ }
5082
+ const children = /* @__PURE__ */ new Map();
5083
+ const parents = /* @__PURE__ */ new Map();
5084
+ for (const name of names) {
5085
+ children.set(name, /* @__PURE__ */ new Set());
5086
+ parents.set(name, /* @__PURE__ */ new Set());
5087
+ }
5088
+ for (const [name, list] of fields) for (const field of list) {
5089
+ var _children$get, _parents$get;
5090
+ const { base } = parseType(field.type);
5091
+ if (base === name) return refuse(`circular type reference in ${name}`);
5092
+ if (leafEncoder(base) !== void 0) continue;
5093
+ if (!fields.has(base)) return refuse(`unknown type "${base}" in ${name}`);
5094
+ (_children$get = children.get(name)) === null || _children$get === void 0 || _children$get.add(base);
5095
+ (_parents$get = parents.get(base)) === null || _parents$get === void 0 || _parents$get.add(name);
5096
+ }
5097
+ const roots = names.filter((name) => {
5098
+ var _parents$get2;
5099
+ return ((_parents$get2 = parents.get(name)) === null || _parents$get2 === void 0 ? void 0 : _parents$get2.size) === 0;
5100
+ });
5101
+ if (roots.length === 0) return refuse("missing primary type");
5102
+ if (roots.length > 1) return refuse("ambiguous primary types or unused types", { roots });
5103
+ const primaryType = roots[0];
5104
+ const visiting = /* @__PURE__ */ new Set();
5105
+ const checked = /* @__PURE__ */ new Set();
5106
+ const checkCircular = (name) => {
5107
+ if (visiting.has(name)) return refuse(`circular type reference at ${name}`);
5108
+ if (checked.has(name)) return;
5109
+ visiting.add(name);
5110
+ for (const child of children.get(name) ?? []) checkCircular(child);
5111
+ visiting.delete(name);
5112
+ checked.add(name);
5113
+ };
5114
+ checkCircular(primaryType);
5115
+ const dependencies = (name, found) => {
5116
+ for (const child of children.get(name) ?? []) if (!found.has(child)) {
5117
+ found.add(child);
5118
+ dependencies(child, found);
5119
+ }
5120
+ return found;
5121
+ };
5122
+ const encodeOne = (name) => `${name}(${(fields.get(name) ?? []).map((field) => `${field.type} ${field.name}`).join(",")})`;
5123
+ const typeHashes = /* @__PURE__ */ new Map();
5124
+ for (const name of names) {
5125
+ const sorted = [...dependencies(name, /* @__PURE__ */ new Set())].filter((dep) => dep !== name).sort();
5126
+ typeHashes.set(name, (0, _noble_hashes_sha3.keccak_256)(textEncoder.encode([name, ...sorted].map(encodeOne).join(""))));
5127
+ }
5128
+ const encodeValue = (parsed, value, depth) => {
5129
+ const dimension = parsed.dimensions[parsed.dimensions.length - 1 - depth];
5130
+ if (dimension !== void 0) {
5131
+ if (!Array.isArray(value)) return refuse(`${parsed.base} array value must be an array`);
5132
+ if (dimension !== -1 && dimension !== value.length) return refuse("array length mismatch");
5133
+ return (0, _noble_hashes_sha3.keccak_256)(concat(value.map((item) => encodeValue(parsed, item, depth + 1))));
5134
+ }
5135
+ const leaf = leafEncoder(parsed.base);
5136
+ if (leaf !== void 0) return leaf(value);
5137
+ return hashStruct(parsed.base, value);
5138
+ };
5139
+ function hashStruct(typeName, value) {
5140
+ const list = fields.get(typeName);
5141
+ const typeHash = typeHashes.get(typeName);
5142
+ if (list === void 0 || typeHash === void 0) return refuse(`unknown type "${typeName}"`);
5143
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return refuse(`${typeName} value must be an object`);
5144
+ const record = value;
5145
+ const words = list.map((field) => {
5146
+ if (!Object.prototype.hasOwnProperty.call(record, field.name)) return refuse(`${typeName} value is missing field "${field.name}"`);
5147
+ return encodeValue(parseType(field.type), record[field.name], 0);
5148
+ });
5149
+ return (0, _noble_hashes_sha3.keccak_256)(concat([typeHash, ...words]));
5150
+ }
5151
+ return {
5152
+ primaryType,
5153
+ hashStruct
5154
+ };
5155
+ }
5156
+ function hashDomain(domain) {
5157
+ if (domain === null || typeof domain !== "object" || Array.isArray(domain)) return refuse("domain must be an object");
5158
+ for (const key of Object.keys(domain)) if (!DOMAIN_FIELDS.some((field) => field.name === key)) return refuse(`invalid typed-data domain key ${JSON.stringify(key)}`);
5159
+ return createStructEncoder({ EIP712Domain: DOMAIN_FIELDS.filter((field) => Object.prototype.hasOwnProperty.call(domain, field.name)) }).hashStruct("EIP712Domain", domain);
5160
+ }
5161
+ /**
5162
+ * The EIP-712 digest `keccak256(0x1901 || domainSeparator || hashStruct(primaryType, message))`
5163
+ * that a wallet signs for `eth_signTypedData_v4`. The primary type is inferred, as ethers does.
5164
+ *
5165
+ * @throws GSwapSDKError (`INVALID_TYPED_DATA`) for any type graph or value it cannot encode
5166
+ * exactly (see the module note on strictness).
5167
+ */
5168
+ function hashTypedData(payload) {
5169
+ const domainHash = hashDomain(payload.domain);
5170
+ const encoder = createStructEncoder(payload.types);
5171
+ const structHash = encoder.hashStruct(encoder.primaryType, payload.message);
5172
+ return (0, _noble_hashes_sha3.keccak_256)(concat([
5173
+ Uint8Array.of(25, 1),
5174
+ domainHash,
5175
+ structHash
5176
+ ]));
5177
+ }
5178
+ /**
5179
+ * Signs a dApp's EIP-712 typed-data approval (the wallet's `signTypedMessage` path), replacing
5180
+ * `new ethers.Wallet(key).signTypedData(domain, types, message)`. `types` must already have
5181
+ * `EIP712Domain` removed (`stripEip712DomainType`).
5182
+ *
5183
+ * @returns `0x` + r + s + v with `v` = `0x1b`/`0x1c` (27/28): ethers' serialized signature form,
5184
+ * recoverable with `verifyTypedData` / `eth_signTypedData_v4` verifiers. Rejects (never throws
5185
+ * synchronously) on invalid input.
5186
+ */
5187
+ async function signTypedDataPayload(privateKey, payload) {
5188
+ const digest = hashTypedData(payload);
5189
+ return withPrivateKey(privateKey, (key) => `0x${signHash(key, digest)}`);
5190
+ }
5191
+
2857
5192
  //#endregion
2858
5193
  exports.ALL_FEE_TIERS = ALL_FEE_TIERS;
5194
+ exports.AppliedTradeError = AppliedTradeError;
2859
5195
  exports.Assets = Assets;
2860
5196
  exports.BrowserWalletSigner = BrowserWalletSigner;
5197
+ exports.CanonicalSymbols = CanonicalSymbols;
2861
5198
  exports.ChainGateway = ChainGateway;
5199
+ exports.DEFAULT_UNIQUE_KEY_PREFIX = DEFAULT_UNIQUE_KEY_PREFIX;
5200
+ exports.DEX_FEE_METHODS = DEX_FEE_METHODS;
5201
+ exports.DRY_RUN_METHOD = DRY_RUN_METHOD;
5202
+ exports.DRY_RUN_UNIQUE_KEY_PREFIX = DRY_RUN_UNIQUE_KEY_PREFIX;
5203
+ exports.DexFees = DexFees;
5204
+ exports.EIP712_CAPABLE_METHODS = EIP712_CAPABLE_METHODS;
2862
5205
  exports.FEE_TIER = FEE_TIER;
2863
5206
  exports.GC_MAX_TICK = GC_MAX_TICK;
2864
5207
  exports.GC_MIN_TICK = GC_MIN_TICK;
@@ -2868,6 +5211,9 @@ exports.GSwapSDKError = GSwapSDKError;
2868
5211
  exports.GalaWalletSigner = GalaWalletSigner;
2869
5212
  exports.GdexStreamError = GdexStreamError;
2870
5213
  exports.GdexStreamService = GdexStreamService;
5214
+ exports.PENDING_SWAPS_STORAGE_KEY = PENDING_SWAPS_STORAGE_KEY;
5215
+ exports.PENDING_SWAP_EXPIRY_MS = PENDING_SWAP_EXPIRY_MS;
5216
+ exports.PendingSwapStore = PendingSwapStore;
2871
5217
  exports.Pools = Pools;
2872
5218
  exports.Positions = Positions;
2873
5219
  exports.PrivateKeySigner = PrivateKeySigner;
@@ -2878,18 +5224,66 @@ exports.Symbols = Symbols;
2878
5224
  exports.TICK_SPACING_BY_FEE = TICK_SPACING_BY_FEE;
2879
5225
  exports.alignTickDown = alignTickDown;
2880
5226
  exports.alignTickUp = alignTickUp;
5227
+ exports.assertCanonicalPair = assertCanonicalPair;
5228
+ exports.assertCanonicalV2Dto = assertCanonicalV2Dto;
2881
5229
  exports.assertTickRange = assertTickRange;
5230
+ exports.assertUniqueKey = assertUniqueKey;
5231
+ exports.buildAddLiquidityDto = buildAddLiquidityDto;
5232
+ exports.buildCollectPositionFeesDto = buildCollectPositionFeesDto;
5233
+ exports.buildCreatePoolDto = buildCreatePoolDto;
5234
+ exports.buildDryRunWrapper = buildDryRunWrapper;
5235
+ exports.buildEIP712Payload = buildEIP712Payload;
5236
+ exports.buildRemoveLiquidityDto = buildRemoveLiquidityDto;
5237
+ exports.buildRequestTradeDto = buildRequestTradeDto;
5238
+ exports.calculatePersonalSignPrefix = calculatePersonalSignPrefix;
5239
+ exports.canonicalDecimalString = canonicalDecimalString;
5240
+ exports.classifyShadowSubmitError = classifyShadowSubmitError;
5241
+ exports.classifyShadowSubmitStatus = classifyShadowSubmitStatus;
2882
5242
  exports.compositeKeyOf = compositeKeyOf;
5243
+ exports.confirmAppliedTrade = confirmAppliedTrade;
5244
+ exports.createLazyPrivateKeySigner = createLazyPrivateKeySigner;
5245
+ exports.createUniqueKey = createUniqueKey;
5246
+ exports.eip712DomainFor = eip712DomainFor;
5247
+ exports.ethAddressOfPrivateKey = ethAddressOfPrivateKey;
5248
+ exports.fixpointPrefixFor = fixpointPrefixFor;
5249
+ exports.frameSlot0 = frameSlot0;
5250
+ exports.generateEIP712Types = generateEIP712Types;
2883
5251
  exports.getObjectProperty = getObjectProperty;
5252
+ exports.getPayloadToSign = getPayloadToSign;
2884
5253
  exports.getStringProperty = getStringProperty;
5254
+ exports.hashTypedData = hashTypedData;
5255
+ exports.isCurrentContractDexMessage = isCurrentContractDexMessage;
5256
+ exports.isDexFeeMethod = isDexFeeMethod;
2885
5257
  exports.isGdexLiquidityEvent = isGdexLiquidityEvent;
5258
+ exports.isProposableTradingSymbol = isProposableTradingSymbol;
5259
+ exports.normalizePrivateKey = normalizePrivateKey;
2886
5260
  exports.orderSymbols = orderSymbols;
2887
5261
  exports.parseJson = parseJson;
2888
5262
  exports.parseTokenClassKey = parseTokenClassKey;
5263
+ exports.personalSignPrefixFor = personalSignPrefixFor;
5264
+ exports.queryTokenOf = queryTokenOf;
5265
+ exports.rawTokenOf = rawTokenOf;
2889
5266
  exports.readOrderedSymbols = readOrderedSymbols;
5267
+ exports.readShadowSubmitOutcome = readShadowSubmitOutcome;
5268
+ exports.resolvePersonalSignChainDtoPayload = resolvePersonalSignChainDtoPayload;
5269
+ exports.resolveUniqueKeyPrefix = resolveUniqueKeyPrefix;
5270
+ exports.serialize = serialize;
5271
+ exports.signChainDtoHash = signChainDtoHash;
5272
+ exports.signChainDtoNative = signChainDtoNative;
5273
+ exports.signChainDtoPersonalSign = signChainDtoPersonalSign;
5274
+ exports.signNative = signNative;
5275
+ exports.signPersonalMessage = signPersonalMessage;
5276
+ exports.signRawObject = signRawObject;
5277
+ exports.signSerializedNative = signSerializedNative;
5278
+ exports.signTypedDataPayload = signTypedDataPayload;
2890
5279
  exports.sqrtPriceToTick = sqrtPriceToTick;
5280
+ exports.stripEip712DomainType = stripEip712DomainType;
5281
+ exports.swapRouteFromQuote = swapRouteFromQuote;
2891
5282
  exports.tickFromPrice = tickFromPrice;
2892
5283
  exports.tickToSqrtPrice = tickToSqrtPrice;
5284
+ exports.toChainDto = toChainDto;
5285
+ exports.tokenClassDollarKey = tokenClassDollarKey;
5286
+ exports.tradingSymbolOf = tradingSymbolOf;
2893
5287
  exports.validateFee = validateFee;
2894
5288
  exports.validateNumericAmount = validateNumericAmount;
2895
5289
  exports.validatePriceValues = validatePriceValues;
@@ -2897,4 +5291,5 @@ exports.validateTickRange = validateTickRange;
2897
5291
  exports.validateTickSpacing = validateTickSpacing;
2898
5292
  exports.validateTokenDecimals = validateTokenDecimals;
2899
5293
  exports.validateWalletAddress = validateWalletAddress;
5294
+ exports.withPrivateKey = withPrivateKey;
2900
5295
  //# sourceMappingURL=index.cjs.map