@gala-chain/gswap-sdk 1.2.0 → 2.0.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/README.md +42 -8
  2. package/dist/browser/classes/apply_results.js +1 -1
  3. package/dist/browser/classes/apply_results.js.map +1 -1
  4. package/dist/browser/classes/assets.js +1 -1
  5. package/dist/browser/classes/assets.js.map +1 -1
  6. package/dist/browser/classes/dex_fees.js +1 -1
  7. package/dist/browser/classes/dex_fees.js.map +1 -1
  8. package/dist/browser/classes/gateway.js +1 -1
  9. package/dist/browser/classes/gateway.js.map +1 -1
  10. package/dist/browser/classes/gdex_stream.js +1 -1
  11. package/dist/browser/classes/gdex_stream.js.map +1 -1
  12. package/dist/browser/classes/gswap.js +1 -1
  13. package/dist/browser/classes/gswap.js.map +1 -1
  14. package/dist/browser/classes/gswap_error_catalog.js +2 -0
  15. package/dist/browser/classes/gswap_error_catalog.js.map +1 -0
  16. package/dist/browser/classes/gswap_error_phase_table.js +2 -0
  17. package/dist/browser/classes/gswap_error_phase_table.js.map +1 -0
  18. package/dist/browser/classes/gswap_sdk_error.js +1 -1
  19. package/dist/browser/classes/gswap_sdk_error.js.map +1 -1
  20. package/dist/browser/classes/http_client.js +1 -1
  21. package/dist/browser/classes/http_client.js.map +1 -1
  22. package/dist/browser/classes/leaderboard.js +2 -0
  23. package/dist/browser/classes/leaderboard.js.map +1 -0
  24. package/dist/browser/classes/market.js +2 -0
  25. package/dist/browser/classes/market.js.map +1 -0
  26. package/dist/browser/classes/pending_swap_fate.js.map +1 -1
  27. package/dist/browser/classes/pending_swaps.js +1 -1
  28. package/dist/browser/classes/pending_swaps.js.map +1 -1
  29. package/dist/browser/classes/pools.js +1 -1
  30. package/dist/browser/classes/pools.js.map +1 -1
  31. package/dist/browser/classes/positions.js +1 -1
  32. package/dist/browser/classes/positions.js.map +1 -1
  33. package/dist/browser/classes/quoting.js +1 -1
  34. package/dist/browser/classes/quoting.js.map +1 -1
  35. package/dist/browser/classes/shadow_submit.js.map +1 -1
  36. package/dist/browser/classes/storage_adapters.js.map +1 -1
  37. package/dist/browser/classes/submitted_transaction.js +1 -1
  38. package/dist/browser/classes/submitted_transaction.js.map +1 -1
  39. package/dist/browser/classes/swaps.js +1 -1
  40. package/dist/browser/classes/swaps.js.map +1 -1
  41. package/dist/browser/classes/symbols.js +1 -1
  42. package/dist/browser/classes/symbols.js.map +1 -1
  43. package/dist/browser/index.js +1 -1
  44. package/dist/browser/types/env.js.map +1 -1
  45. package/dist/browser/types/fees.js.map +1 -1
  46. package/dist/browser/types/market.js +2 -0
  47. package/dist/browser/types/market.js.map +1 -0
  48. package/dist/browser/utils/classification.js.map +1 -1
  49. package/dist/browser/utils/confirmation_schedule.js.map +1 -1
  50. package/dist/browser/utils/confirmation_state.js +1 -1
  51. package/dist/browser/utils/confirmation_state.js.map +1 -1
  52. package/dist/browser/utils/debug.js.map +1 -1
  53. package/dist/browser/utils/dto_builders.js +1 -1
  54. package/dist/browser/utils/dto_builders.js.map +1 -1
  55. package/dist/browser/utils/failure_classification.js +1 -1
  56. package/dist/browser/utils/failure_classification.js.map +1 -1
  57. package/dist/browser/utils/failure_guards.js +1 -1
  58. package/dist/browser/utils/failure_guards.js.map +1 -1
  59. package/dist/browser/utils/lenient.js +1 -1
  60. package/dist/browser/utils/lenient.js.map +1 -1
  61. package/dist/browser/utils/ordering.js +1 -1
  62. package/dist/browser/utils/ordering.js.map +1 -1
  63. package/dist/browser/utils/reconnection.js +1 -1
  64. package/dist/browser/utils/reconnection.js.map +1 -1
  65. package/dist/browser/utils/resolve_pre_send.js +2 -0
  66. package/dist/browser/utils/resolve_pre_send.js.map +1 -0
  67. package/dist/browser/utils/server_message.js.map +1 -1
  68. package/dist/browser/utils/shadow_errors.js +1 -1
  69. package/dist/browser/utils/shadow_errors.js.map +1 -1
  70. package/dist/browser/utils/submit_state.js.map +1 -1
  71. package/dist/browser/utils/tick_align.js.map +1 -1
  72. package/dist/browser/utils/tick_math.js.map +1 -1
  73. package/dist/browser/utils/tick_range.js.map +1 -1
  74. package/dist/browser/utils/ticks.js +1 -1
  75. package/dist/browser/utils/ticks.js.map +1 -1
  76. package/dist/browser/utils/validation.js +1 -1
  77. package/dist/browser/utils/validation.js.map +1 -1
  78. package/dist/index.cjs +1033 -2925
  79. package/dist/index.cjs.map +1 -1
  80. package/dist/index.d.cts +439 -1604
  81. package/dist/index.d.ts +439 -1604
  82. package/dist/index.mjs +964 -2810
  83. package/dist/index.mjs.map +1 -1
  84. package/package.json +36 -60
  85. package/dist/browser/classes/error_catalog.js +0 -2
  86. package/dist/browser/classes/error_catalog.js.map +0 -1
  87. package/dist/browser/classes/error_phase_table.js +0 -2
  88. package/dist/browser/classes/error_phase_table.js.map +0 -1
  89. package/dist/browser/classes/error_phases.js +0 -2
  90. package/dist/browser/classes/error_phases.js.map +0 -1
  91. package/dist/browser/classes/signers.js +0 -2
  92. package/dist/browser/classes/signers.js.map +0 -1
  93. package/dist/browser/utils/axios_requestor.js +0 -2
  94. package/dist/browser/utils/axios_requestor.js.map +0 -1
  95. package/dist/browser/utils/chain_dto_signing.js +0 -3
  96. package/dist/browser/utils/chain_dto_signing.js.map +0 -1
  97. package/dist/browser/utils/class_key.js +0 -2
  98. package/dist/browser/utils/class_key.js.map +0 -1
  99. package/dist/browser/utils/eip712.js +0 -4
  100. package/dist/browser/utils/eip712.js.map +0 -1
  101. package/dist/browser/utils/error_brand.js +0 -2
  102. package/dist/browser/utils/error_brand.js.map +0 -1
  103. package/dist/browser/utils/ethers_provider.js +0 -2
  104. package/dist/browser/utils/ethers_provider.js.map +0 -1
  105. package/dist/browser/utils/failure_outcomes.js +0 -2
  106. package/dist/browser/utils/failure_outcomes.js.map +0 -1
  107. package/dist/browser/utils/failure_view.js +0 -2
  108. package/dist/browser/utils/failure_view.js.map +0 -1
  109. package/dist/browser/utils/fetch_failure.js +0 -2
  110. package/dist/browser/utils/fetch_failure.js.map +0 -1
  111. package/dist/browser/utils/pre_send.js +0 -2
  112. package/dist/browser/utils/pre_send.js.map +0 -1
  113. package/dist/browser/utils/signing.js +0 -3
  114. package/dist/browser/utils/signing.js.map +0 -1
  115. package/dist/browser/utils/transport.js +0 -2
  116. package/dist/browser/utils/transport.js.map +0 -1
  117. package/dist/browser/utils/typed_data.js +0 -2
  118. package/dist/browser/utils/typed_data.js.map +0 -1
  119. package/dist/browser/utils/unique_key.js +0 -2
  120. package/dist/browser/utils/unique_key.js.map +0 -1
  121. package/dist/browser/utils/unique_key_syntax.js +0 -2
  122. package/dist/browser/utils/unique_key_syntax.js.map +0 -1
  123. package/dist/browser/utils/wallet_rejection.js +0 -2
  124. package/dist/browser/utils/wallet_rejection.js.map +0 -1
  125. package/dist/browser/version.js +0 -2
  126. package/dist/browser/version.js.map +0 -1
  127. package/dist/browser/wallet.js +0 -2
  128. package/dist/browser/wallet.js.map +0 -1
  129. package/dist/wallet.cjs +0 -159
  130. package/dist/wallet.cjs.map +0 -1
  131. package/dist/wallet.d.cts +0 -74
  132. package/dist/wallet.d.ts +0 -74
  133. package/dist/wallet.mjs +0 -156
  134. package/dist/wallet.mjs.map +0 -1
  135. /package/dist/browser/_virtual/{_@oxc-project_runtime@0.151.0 → _@oxc-project_runtime@0.153.0}/helpers/esm/defineProperty.js +0 -0
  136. /package/dist/browser/_virtual/{_@oxc-project_runtime@0.151.0 → _@oxc-project_runtime@0.153.0}/helpers/esm/toPrimitive.js +0 -0
  137. /package/dist/browser/_virtual/{_@oxc-project_runtime@0.151.0 → _@oxc-project_runtime@0.153.0}/helpers/esm/toPropertyKey.js +0 -0
  138. /package/dist/browser/_virtual/{_@oxc-project_runtime@0.151.0 → _@oxc-project_runtime@0.153.0}/helpers/esm/typeof.js +0 -0
package/dist/index.mjs CHANGED
@@ -1,9 +1,7 @@
1
+ import { GALA_ERROR_CATALOG, GALA_ERROR_PHASE_BY_CODE, GalaSDKError, armTimeout, assertUniqueKey, buildQueryString, buildRequestHeaders, compositeKeyOf, createUniqueKey, fetchRequestor, isGalaCatalogCode, isGalaError, isTokenClassKey, isValidUniqueKey, isWriteView, numberParamOf, parseJson, readBlockNumberHeader, readHeader, readResponseBody, readRetryAfter, requestWithTimeout, resolveUniqueKeyPrefix, signPreSend, splitClassKey, stringParamOf, toFailureView, validateTimeoutMs } from "@gala-chain/gala-core-sdk";
1
2
  import BigNumber from "bignumber.js";
2
3
  import { Decimal } from "decimal.js";
3
4
  import debug from "debug";
4
- import { secp256k1 } from "@noble/curves/secp256k1";
5
- import { keccak_256 } from "@noble/hashes/sha3";
6
- import { bytesToHex } from "@noble/hashes/utils";
7
5
 
8
6
  //#region src/utils/confirmation_schedule.ts
9
7
  const DEFAULT_TIMEOUT_MS = 6e4;
@@ -95,352 +93,30 @@ function readServerMessage(error) {
95
93
  }
96
94
 
97
95
  //#endregion
98
- //#region src/utils/unique_key_syntax.ts
99
- /**
100
- * The shape of a write `uniqueKey`, with NO imports: the pending-swap store and the failure guards
101
- * need only this check, and importing it from `unique_key.ts` would drag in `GSwapSDKError` and the
102
- * whole error catalog for it.
103
- */
104
- /** The swap backend's apply-results key constraint (`apply-results.dto.ts`). */
105
- const UNIQUE_KEY_PATTERN = /^[A-Za-z0-9._:-]{1,128}$/u;
106
- /**
107
- * True when `uniqueKey` is a usable write key: 1 to 128 characters of `A-Za-z0-9._:-`, the shape
108
- * the apply-results route accepts. The one definition behind `assertUniqueKey`, `confirm()` and
109
- * the pending-swap store, so they can never disagree about what a valid key is.
110
- */
111
- function isValidUniqueKey(uniqueKey) {
112
- return typeof uniqueKey === "string" && UNIQUE_KEY_PATTERN.test(uniqueKey);
113
- }
114
-
115
- //#endregion
116
- //#region src/utils/error_brand.ts
117
- /**
118
- * The brand every `GSwapSDKError` carries, and the duck-type check for the "failure shape". This
119
- * module has NO imports on purpose: it is part of the light `@gala-chain/gswap-sdk/wallet` entry,
120
- * which must never pull the error catalog or the main bundle in.
121
- */
122
- /**
123
- * The brand of an SDK error: `error[GSWAP_ERROR_BRAND] === true`. It is a registered symbol
124
- * (`Symbol.for`), so two copies of the SDK in one page (a wallet bundle and an app bundle) still
125
- * recognise each other's errors, which `instanceof` cannot do.
126
- */
127
- const GSWAP_ERROR_BRAND = Symbol.for("@gala-chain/gswap-sdk/error");
128
- function isRecord$4(value) {
129
- return typeof value === "object" && value !== null;
130
- }
131
- /** True when `value` carries the SDK error brand (a real `GSwapSDKError`, from any copy of the SDK). */
132
- function hasGSwapErrorBrand(value) {
133
- return isRecord$4(value) && value[GSWAP_ERROR_BRAND] === true;
134
- }
135
- /**
136
- * True when `value` is an SDK error (it carries the brand) or a plain-object VIEW of one: a string
137
- * `code` and an object `params`, which is the shape of `GSwapSDKError` and of the wrapper objects
138
- * an app builds from one (`{ code, params, status?, phase?, ... }`). A wallet's own error (an
139
- * EIP-1193 numeric `code`, an ethers error with a string `code` and no `params`) is neither. It
140
- * says the value has the SDK's shape, not that `code` is a code the SDK knows.
141
- *
142
- * @example
143
- * ```ts
144
- * if (isGSwapErrorLike(error)) report(error.code, error.params); // no `instanceof`, no SDK import
145
- * ```
146
- */
147
- function isGSwapErrorLike(value) {
148
- if (hasGSwapErrorBrand(value)) return true;
149
- return isRecord$4(value) && typeof value["code"] === "string" && isRecord$4(value["params"]) && !Array.isArray(value["params"]);
150
- }
151
-
152
- //#endregion
153
- //#region src/classes/error_phases.ts
154
- /**
155
- * How far an operation had got when it failed, measured against the request it exists to send:
156
- * for a write, the POST that carries the signed DTO to the gateway; for a read, the GET.
157
- *
158
- * - `pre_send`: NOTHING was transmitted. Everything up to the request (validation, symbol
159
- * resolution, the signer, the wallet, a key provider) ran and failed, so no write can have been
160
- * queued. A write that failed `pre_send` is never "maybe applied".
161
- * - `in_flight`: the request left (or may have left) and no answer was read: a timeout, a broken
162
- * connection, a stream that dropped. For a write the outcome is unknown.
163
- * - `post_send`: an answer was read, or the write was already accepted: a refusal, an
164
- * acknowledgement that proves nothing, an unusable body, and every failure of a confirmation.
165
- *
166
- * Each catalog entry declares the phase its code is thrown in; a throw site where the same code
167
- * can occur in another phase overrides it (`GSwapErrorContext.phase`). It is a typed property of
168
- * every `GSwapSDKError` (`error.phase`) and `isPreSendError(error)` reads it.
169
- *
170
- * This module has no imports, on purpose: it is the closed set of phases, shared by the catalog,
171
- * the light phase table (`error_phase_table.ts`) and the failure view.
172
- */
173
- const GSWAP_ERROR_PHASES = [
174
- "pre_send",
175
- "in_flight",
176
- "post_send"
177
- ];
178
-
179
- //#endregion
180
- //#region src/utils/failure_outcomes.ts
181
- /**
182
- * What a failure means for the operation that raised it, as one vocabulary for every app.
183
- *
184
- * - `rejected`: the CHAIN refused the request (a definite no). Nothing was applied, so a NEW
185
- * request is safe.
186
- * - `unknown`: the request MAY have been applied (a timeout, a broken connection, an
187
- * acknowledgement that proves nothing, a failed confirmation). Never submit again under a fresh
188
- * key: keep `error.uniqueKey` and confirm it.
189
- * - `not_queued`: the gateway refused the write before queueing it. Nothing is pending.
190
- * - `not_sent`: the request never left (`error.phase === 'pre_send'`) for a reason outside the
191
- * call itself: the signer or the wallet failed (locked, unreachable, the person declined), the
192
- * key provider or the token resolver failed, or no key was available yet. Nothing is pending and
193
- * nothing was transmitted, so repeating the call is safe and may succeed once the cause (an
194
- * unlocked wallet) is gone.
195
- * - `invalid_input`: the call itself cannot work (a bad argument, a missing signer or wallet, an
196
- * unknown token or pool, a corrupt stored key). Repeating the same call fails the same way.
197
- * Such an error is also pre-send (`isPreSendError`), but the input is what has to change.
198
- * - `transient`: a read or connection failed with no state change. Repeating the same call is safe
199
- * and may succeed.
96
+ //#region src/classes/gswap_error_catalog.ts
97
+ /**
98
+ * The catalog of every error code gswap-sdk ITSELF throws (the DEX codes): the SINGLE source of
99
+ * truth for its `code`, the named parameters it carries and the English template its `message` is
100
+ * generated from. The codes of the signing, transport and wallet layer (`INVALID_PRIVATE_KEY`,
101
+ * `REQUEST_TIMEOUT`, `SIGNING_FAILED`, `TYPED_DATA_*`, ...) live in `@gala-chain/gala-core-sdk`'s
102
+ * `GALA_ERROR_CATALOG`; no code is defined in both. A `GSwapSDKError` can carry a code of either
103
+ * catalog (`GSwapErrorCode` is the union), and reads its template from both.
200
104
  *
201
- * This module has no imports: the failure view (`FailureView.outcome`) and the classifier both
202
- * read the closed set from here.
203
- */
204
- const FAILURE_OUTCOMES = [
205
- "rejected",
206
- "unknown",
207
- "not_queued",
208
- "not_sent",
209
- "invalid_input",
210
- "transient"
211
- ];
212
-
213
- //#endregion
214
- //#region src/utils/failure_view.ts
215
- function isRecord$3(value) {
216
- return typeof value === "object" && value !== null;
217
- }
218
- /**
219
- * Reads the failure view of any value: a `GSwapSDKError`, or a plain object with a string `code`
220
- * (and, optionally, `params`, `status`, `phase`, `uniqueKey`, `shadow`, `notFound`). Anything else
221
- * (a string, a wallet error with a numeric `code`, `null`) has no view and is `undefined`.
222
- *
223
- * Having a view does not mean the SDK knows the code: an ethers error is `{ code:
224
- * 'ACTION_REJECTED' }` and has a view too. The SDK's guards compare the code with the catalog.
225
- *
226
- * @example
227
- * ```ts
228
- * const view = toFailureView(error); // an SDK error, or an app's `{ code, params, status }` wrapper
229
- * if (view?.code === 'CHAIN_REJECTED') showChainRejection(view.params);
230
- * ```
231
- */
232
- function toFailureView(value) {
233
- if (!isRecord$3(value)) return void 0;
234
- const { code, params, status, phase, uniqueKey, write, outcome, retryable, shadow, notFound } = value;
235
- if (typeof code !== "string" || code === "") return void 0;
236
- return {
237
- code,
238
- params: isRecord$3(params) && !Array.isArray(params) ? params : void 0,
239
- status: typeof status === "number" && Number.isFinite(status) ? status : void 0,
240
- phase: GSWAP_ERROR_PHASES.find((known) => known === phase),
241
- uniqueKey: typeof uniqueKey === "string" && uniqueKey !== "" ? uniqueKey : void 0,
242
- write: typeof write === "boolean" ? write : void 0,
243
- outcome: FAILURE_OUTCOMES.find((known) => known === outcome),
244
- retryable: retryable === false ? false : void 0,
245
- shadow: typeof shadow === "boolean" ? shadow : void 0,
246
- notFound: typeof notFound === "boolean" ? notFound : void 0
247
- };
248
- }
249
- /**
250
- * True when a failure is a WRITE's: it names a `uniqueKey`, or it does not say `write: false`.
251
- * Fail-closed (see {@link FailureView}): only an explicit read is a read.
252
- */
253
- function isWriteView(view) {
254
- return view.uniqueKey !== void 0 || view.write !== false;
255
- }
256
- /** A param of a view, when it is a string. */
257
- function stringParamOf(view, name) {
258
- var _view$params;
259
- const value = (_view$params = view.params) === null || _view$params === void 0 ? void 0 : _view$params[name];
260
- return typeof value === "string" ? value : void 0;
261
- }
262
- /** A param of a view, when it is a finite number. */
263
- function numberParamOf(view, name) {
264
- var _view$params2;
265
- const value = (_view$params2 = view.params) === null || _view$params2 === void 0 ? void 0 : _view$params2[name];
266
- return typeof value === "number" && Number.isFinite(value) ? value : void 0;
267
- }
268
-
269
- //#endregion
270
- //#region src/classes/error_phase_table.ts
271
- /**
272
- * The phase of every catalog code, WITHOUT the catalog. The failure guards (`classifyFailure`,
273
- * `mayHaveBeenQueued`, ...) need only a code's phase, and the catalog's English templates are
274
- * ~20 kB: reading the phase from here keeps an app shell that imports a guard from loading them.
275
- * The catalog entry is still where a code is declared; this table is checked against it AT
276
- * COMPILE TIME, in both directions (a missing code, an extra code or a different phase is a type
277
- * error), so the two cannot drift.
278
- */
279
- const GSWAP_ERROR_PHASE_BY_CODE = {
280
- HTTP_ERROR: "post_send",
281
- UPDATE_REQUIRED: "post_send",
282
- REQUEST_TIMEOUT: "in_flight",
283
- TRANSPORT_ERROR: "in_flight",
284
- REQUEST_FAILED: "in_flight",
285
- INVALID_TIMEOUT: "pre_send",
286
- METHOD_NOT_SUPPORTED: "pre_send",
287
- SUBMISSION_OUTCOME_UNKNOWN: "in_flight",
288
- GATEWAY_REPORTED_ERROR: "post_send",
289
- GATEWAY_DATA_MISSING: "post_send",
290
- GATEWAY_MODE_INVALID: "post_send",
291
- GATEWAY_SYNC_REQUEST_TRADE: "post_send",
292
- MISSING_UNIQUE_KEY: "post_send",
293
- UNIQUE_KEY_MISMATCH: "post_send",
294
- SHADOW_NOT_QUEUED: "post_send",
295
- SHADOW_SUBMIT_NEEDS_WALLET_ADDRESS: "pre_send",
296
- METHOD_NOT_ALLOWED: "post_send",
297
- SIGNATURE_INVALID: "post_send",
298
- SIGNER_MISMATCH: "post_send",
299
- DTO_INVALID: "post_send",
300
- BOUNDS_VIOLATION: "post_send",
301
- SYMBOL_CONFLICT: "post_send",
302
- RATE_LIMITED: "post_send",
303
- CHAIN_DISPATCH_FAILED: "post_send",
304
- GATEWAY_REJECTED: "post_send",
305
- CHAIN_REJECTED: "post_send",
306
- INVALID_CHAIN_RESPONSE: "post_send",
307
- INVALID_POSITION_RESPONSE: "post_send",
308
- CONFIRMATION_TIMEOUT: "post_send",
309
- CONFIRMATION_ABORTED: "post_send",
310
- CONFIRMATION_FAILED: "post_send",
311
- CONFIRMATION_MALFORMED: "post_send",
312
- CONFIRMATION_KEY_MISMATCH: "post_send",
313
- NO_SIGNER: "pre_send",
314
- NO_WALLET_ADDRESS: "post_send",
315
- NO_POOL_AVAILABLE: "post_send",
316
- INSUFFICIENT_LIQUIDITY: "post_send",
317
- UNKNOWN_TOKEN: "pre_send",
318
- SYMBOL_NOT_FOUND: "pre_send",
319
- STREAM_API_KEY_UNUSABLE: "pre_send",
320
- STREAM_WEBSOCKET_UNAVAILABLE: "pre_send",
321
- STREAM_NOT_CONNECTED: "pre_send",
322
- STREAM_DISCONNECTED: "in_flight",
323
- STREAM_REQUEST_TIMEOUT: "in_flight",
324
- STREAM_CONNECT_FAILED: "in_flight",
325
- STREAM_CLOSED_BEFORE_CONNECT: "in_flight",
326
- STREAM_CONNECTION_CLOSED: "in_flight",
327
- GALA_WALLET_NOT_AVAILABLE: "pre_send",
328
- WALLET_UNAVAILABLE: "pre_send",
329
- MALFORMED_WALLET_REQUEST: "pre_send",
330
- UNSUPPORTED_WALLET_METHOD: "pre_send",
331
- INVALID_SIGNATURE: "pre_send",
332
- INVALID_CHAIN_ID: "pre_send",
333
- SIGNER_ADDRESS_MISMATCH: "pre_send",
334
- INVALID_PRIVATE_KEY: "pre_send",
335
- SIGNING_FAILED: "pre_send",
336
- KEY_PROVIDER_FAILED: "pre_send",
337
- TOKEN_RESOLUTION_FAILED: "pre_send",
338
- INVALID_SIGNING_HASH_LENGTH: "pre_send",
339
- MISSING_WALLET_ADDRESS: "pre_send",
340
- INVALID_WALLET_ADDRESS: "pre_send",
341
- INVALID_DECIMAL: "pre_send",
342
- PERSONAL_SIGN_NOT_JSON: "pre_send",
343
- PERSONAL_SIGN_NOT_OBJECT: "pre_send",
344
- PERSONAL_SIGN_BAD_PREFIX: "pre_send",
345
- PERSONAL_SIGN_ENVELOPE_FIELDS: "pre_send",
346
- PERSONAL_SIGN_NOT_CANONICAL: "pre_send",
347
- EIP712_NAME_COLLISION: "pre_send",
348
- EIP712_UNSUPPORTED_VALUE: "pre_send",
349
- TYPED_DATA_NOT_STRING: "pre_send",
350
- TYPED_DATA_UNPAIRED_SURROGATE: "pre_send",
351
- TYPED_DATA_NOT_HEX_BYTES: "pre_send",
352
- TYPED_DATA_NOT_SAFE_INTEGER: "pre_send",
353
- TYPED_DATA_NOT_INTEGER: "pre_send",
354
- TYPED_DATA_NOT_BOOLEAN: "pre_send",
355
- TYPED_DATA_BAD_ADDRESS: "pre_send",
356
- TYPED_DATA_BAD_CHECKSUM: "pre_send",
357
- TYPED_DATA_BYTES_LENGTH: "pre_send",
358
- TYPED_DATA_OUT_OF_BOUNDS: "pre_send",
359
- TYPED_DATA_UNSUPPORTED_TYPE: "pre_send",
360
- TYPED_DATA_FIELDS_NOT_LIST: "pre_send",
361
- TYPED_DATA_FIELD_MALFORMED: "pre_send",
362
- TYPED_DATA_INVALID_FIELD_NAME: "pre_send",
363
- TYPED_DATA_DUPLICATE_FIELD: "pre_send",
364
- TYPED_DATA_INVALID_TYPE_NAME: "pre_send",
365
- TYPED_DATA_CIRCULAR_TYPE: "pre_send",
366
- TYPED_DATA_UNKNOWN_FIELD_TYPE: "pre_send",
367
- TYPED_DATA_NO_PRIMARY_TYPE: "pre_send",
368
- TYPED_DATA_AMBIGUOUS_PRIMARY_TYPE: "pre_send",
369
- TYPED_DATA_ARRAY_EXPECTED: "pre_send",
370
- TYPED_DATA_ARRAY_LENGTH_MISMATCH: "pre_send",
371
- TYPED_DATA_STRUCT_NOT_OBJECT: "pre_send",
372
- TYPED_DATA_MISSING_FIELD: "pre_send",
373
- TYPED_DATA_DOMAIN_NOT_OBJECT: "pre_send",
374
- TYPED_DATA_INVALID_DOMAIN_KEY: "pre_send",
375
- NON_CANONICAL_PAIR: "pre_send",
376
- UNORDERABLE_SYMBOLS: "pre_send",
377
- INVALID_TOKEN_CLASS_KEY: "pre_send",
378
- INCOMPLETE_TOKEN_CLASS_KEY: "pre_send",
379
- INVALID_TRADING_SYMBOL: "pre_send",
380
- NO_VALID_TRADING_SYMBOL: "pre_send",
381
- TOKEN_SYMBOL_NOT_REGISTERED: "pre_send",
382
- SYMBOL_DISAGREES_WITH_REGISTRY: "pre_send",
383
- CREATE_POOL_NEEDS_CLASS_KEY: "pre_send",
384
- INVALID_UNIQUE_KEY: "pre_send",
385
- INVALID_UNIQUE_KEY_PREFIX: "pre_send",
386
- AMOUNT_IS_JS_NUMBER: "pre_send",
387
- AMOUNT_NOT_FINITE: "pre_send",
388
- AMOUNT_NOT_POSITIVE: "pre_send",
389
- AMOUNT_NEGATIVE: "pre_send",
390
- PRICE_VALUES_IS_JS_NUMBER: "pre_send",
391
- INVALID_PRICE_VALUES: "pre_send",
392
- INVALID_PRICE_RANGE: "pre_send",
393
- INVALID_PRICE_BOUNDS: "pre_send",
394
- PRICE_BOUNDS_ORDER: "pre_send",
395
- INVALID_STARTING_PRICE: "pre_send",
396
- INVALID_SQRT_PRICE: "pre_send",
397
- INVALID_TOKEN_DECIMALS: "pre_send",
398
- TICKS_NOT_INTEGERS: "pre_send",
399
- INVALID_TICK_RANGE: "pre_send",
400
- TICKS_OUT_OF_BOUNDS: "pre_send",
401
- TICKS_NOT_ALIGNED: "pre_send",
402
- INVALID_TICK_SPACING: "pre_send",
403
- INVALID_FEE: "pre_send",
404
- EXACT_AMOUNT_REQUIRED: "pre_send",
405
- EXACTLY_ONE_OF_REQUIRED: "pre_send",
406
- AT_MOST_ONE_OF_ALLOWED: "pre_send",
407
- POOL_MISMATCH: "pre_send",
408
- INVALID_DTO: "pre_send",
409
- DTO_NOT_CANONICAL: "pre_send",
410
- SEARCH_TOO_LONG: "pre_send",
411
- INVALID_SORT_BY: "pre_send",
412
- INVALID_SORT_ORDER: "pre_send",
413
- INVALID_PAGE: "pre_send",
414
- INVALID_LIMIT: "pre_send"
415
- };
416
- /** True for a code the SDK catalog defines. */
417
- function isCatalogCode(code) {
418
- return Object.hasOwn(GSWAP_ERROR_PHASE_BY_CODE, code);
419
- }
420
- /** The phase of a view: the one it states, else the catalog's phase for its code, else `undefined`. */
421
- function phaseOfView(view) {
422
- if (view.phase !== void 0) return view.phase;
423
- return isCatalogCode(view.code) ? GSWAP_ERROR_PHASE_BY_CODE[view.code] : void 0;
424
- }
425
-
426
- //#endregion
427
- //#region src/classes/error_catalog.ts
428
- /**
429
- * Why a private key was refused (`INVALID_PRIVATE_KEY.params.reason`). A token for the app to map
430
- * to its own text, never English and never any part of the key.
105
+ * - `code` is the only discriminator. It is stable: apps branch on it, never on message text.
106
+ * - `params` are the named placeholders of the error: a flat object of primitives (string,
107
+ * number, boolean) that an app feeds to its own localization (i18next, ICU, FormatJS).
108
+ * Raw server payloads are never params (they are `error.body` / `error.bodyText`), and no
109
+ * secret (private key, signature, raw DTO) ever is.
110
+ * - `template` is English with `{placeholder}` markers. `error.message` is generated from it
111
+ * and the params, so the two cannot drift. It is for logs and developers: it is not stable and
112
+ * must not be shown to users or matched on.
431
113
  *
432
- * - `wrong_length`: raw bytes that are not 32 bytes long.
433
- * - `bad_encoding`: a string that is neither 64 hex characters (optionally `0x`-prefixed, 63 is
434
- * left-padded) nor a 44-character base64 string.
435
- * - `not_on_curve`: 32 bytes that are not a valid secp256k1 secret scalar.
436
- * - `no_key_provided`: a lazy key provider returned something that is not a string.
114
+ * Every placeholder in a template is a declared param and every declared param is a placeholder,
115
+ * except the few `unrendered` params that carry server text for the app and are kept out of
116
+ * `message` on purpose (pinned by tests/error_catalog.test.ts). Facts that are not placeholders (HTTP status, URL,
117
+ * `uniqueKey`, transaction ids, the raw body, the cause) are typed properties of the error, see
118
+ * `GalaErrorContext`.
437
119
  */
438
- const INVALID_PRIVATE_KEY_REASONS = [
439
- "wrong_length",
440
- "bad_encoding",
441
- "not_on_curve",
442
- "no_key_provided"
443
- ];
444
120
  /**
445
121
  * Why the outcome of a write submission is unknown (`SUBMISSION_OUTCOME_UNKNOWN.params.reason`).
446
122
  * Both say the same thing about the swap (it MAY have been queued: never resubmit under a fresh
@@ -452,20 +128,9 @@ const INVALID_PRIVATE_KEY_REASONS = [
452
128
  * does NOT prove the swap was not sent.
453
129
  */
454
130
  const SUBMISSION_OUTCOME_UNKNOWN_REASONS = ["timeout", "transport"];
455
- /**
456
- * Why a signer could not produce a signature (`SIGNING_FAILED.params.reason`). A token for the
457
- * app to map to its own text.
458
- *
459
- * - `user_rejected`: the person declined in their wallet (an EIP-1193 `4001`, an ethers
460
- * `ACTION_REJECTED`, or a wallet that words it "User rejected/denied ..."). Nothing is wrong:
461
- * they may simply try again.
462
- * - `failed`: anything else the signer threw (a locked wallet, an unreachable extension, a wallet
463
- * that refused). The original error is `cause`.
464
- */
465
- const SIGNING_FAILURE_REASONS = ["user_rejected", "failed"];
466
131
  /** The fee tiers as they appear in `INVALID_FEE.allowed`. */
467
132
  const ALLOWED_FEE_TIERS_TEXT = "0,500,3000,10000";
468
- /** The whole catalog. Add a code here before throwing it anywhere. */
133
+ /** The whole DEX catalog. Add a code here before throwing it anywhere in gswap-sdk. */
469
134
  const GSWAP_ERROR_CATALOG = {
470
135
  HTTP_ERROR: {
471
136
  phase: "post_send",
@@ -482,32 +147,11 @@ const GSWAP_ERROR_CATALOG = {
482
147
  template: "The backend refused this app version and requires an update (HTTP {status}); nothing was queued.",
483
148
  params: { status: "number" }
484
149
  },
485
- REQUEST_TIMEOUT: {
486
- phase: "in_flight",
487
- template: "Request to {url} timed out after {timeoutMs} ms.",
488
- params: {
489
- url: "string",
490
- timeoutMs: "number"
491
- }
492
- },
493
- TRANSPORT_ERROR: {
494
- phase: "in_flight",
495
- template: "The request to {url} failed before the server answered.",
496
- params: { url: "string" }
497
- },
498
150
  REQUEST_FAILED: {
499
151
  phase: "in_flight",
500
152
  template: "The {operation} request failed.",
501
153
  params: { operation: "string" }
502
154
  },
503
- INVALID_TIMEOUT: {
504
- phase: "pre_send",
505
- template: "{field} must be a positive number of milliseconds, or Infinity for no timeout (got {value}).",
506
- params: {
507
- field: "string",
508
- value: "number"
509
- }
510
- },
511
155
  METHOD_NOT_SUPPORTED: {
512
156
  phase: "pre_send",
513
157
  template: "The method {method} is not supported; swaps are queued through RequestTrade.",
@@ -737,370 +381,109 @@ const GSWAP_ERROR_CATALOG = {
737
381
  template: "GDEX stream connection closed (code {closeCode}).",
738
382
  params: { closeCode: "number" }
739
383
  },
740
- GALA_WALLET_NOT_AVAILABLE: {
384
+ TOKEN_RESOLUTION_FAILED: {
741
385
  phase: "pre_send",
742
- template: "Gala wallet is not available. Please ensure the Gala wallet is connected.",
743
- params: {}
386
+ template: "Token {token} could not be looked up; nothing was sent.",
387
+ params: { token: "string" }
744
388
  },
745
- WALLET_UNAVAILABLE: {
389
+ MISSING_WALLET_ADDRESS: {
746
390
  phase: "pre_send",
747
- template: "No wallet is connected.",
391
+ template: "Invalid wallet address: No wallet address provided. Either provide a wallet address to the function you are calling, or set one when instantiating GSwapSDK.",
748
392
  params: {}
749
393
  },
750
- MALFORMED_WALLET_REQUEST: {
751
- phase: "pre_send",
752
- template: "Wallet {method} request is malformed.",
753
- params: { method: "string" }
754
- },
755
- UNSUPPORTED_WALLET_METHOD: {
394
+ NON_CANONICAL_PAIR: {
756
395
  phase: "pre_send",
757
- template: "Unsupported wallet method {method}.",
758
- params: { method: "string" }
396
+ template: "The pair {token0}/{token1} is not in canonical order: token0 must sort before token1.",
397
+ params: {
398
+ token0: "string",
399
+ token1: "string"
400
+ }
759
401
  },
760
- INVALID_SIGNATURE: {
402
+ UNORDERABLE_SYMBOLS: {
761
403
  phase: "pre_send",
762
- template: "Wallet returned an invalid {scheme} signature.",
763
- params: { scheme: "string" }
404
+ template: "Unable to order trading symbols: expected a pair of symbols, got {received}.",
405
+ params: { received: "string" }
764
406
  },
765
- INVALID_CHAIN_ID: {
407
+ INVALID_TOKEN_CLASS_KEY: {
766
408
  phase: "pre_send",
767
- template: "Wallet returned an invalid chain id ({chainId}).",
768
- params: { chainId: "string" }
409
+ template: "Invalid token class key \"{tokenClassKey}\": expected a collection followed by category, type and additionalKey",
410
+ params: { tokenClassKey: "string" }
769
411
  },
770
- SIGNER_ADDRESS_MISMATCH: {
412
+ INCOMPLETE_TOKEN_CLASS_KEY: {
771
413
  phase: "pre_send",
772
- template: "The signing key does not belong to the expected account.",
773
- params: {}
414
+ template: "Token class key \"{compositeKey}\" is missing {part}.",
415
+ params: {
416
+ compositeKey: "string",
417
+ part: "string"
418
+ }
774
419
  },
775
- INVALID_PRIVATE_KEY: {
420
+ INVALID_TRADING_SYMBOL: {
776
421
  phase: "pre_send",
777
- template: "Invalid private key: {reason}.",
778
- params: { reason: INVALID_PRIVATE_KEY_REASONS }
422
+ template: "Invalid trading symbol \"{symbol}\": must be 1-32 ASCII letters and digits and not a reserved word (Token, Unit, none).",
423
+ params: { symbol: "string" }
779
424
  },
780
- SIGNING_FAILED: {
425
+ NO_VALID_TRADING_SYMBOL: {
781
426
  phase: "pre_send",
782
- template: "Signing the {method} request failed ({reason}); nothing was sent.",
427
+ template: "No valid trading symbol for token class {classKey}: \"{displaySymbol}\" is not an acceptable symbol and none is registered",
783
428
  params: {
784
- method: "string",
785
- reason: SIGNING_FAILURE_REASONS
429
+ classKey: "string",
430
+ displaySymbol: "string"
786
431
  }
787
432
  },
788
- KEY_PROVIDER_FAILED: {
433
+ TOKEN_SYMBOL_NOT_REGISTERED: {
789
434
  phase: "pre_send",
790
- template: "The signing key could not be fetched; nothing was sent.",
791
- params: {}
435
+ template: "Token {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.",
436
+ params: { token: "string" }
792
437
  },
793
- TOKEN_RESOLUTION_FAILED: {
438
+ SYMBOL_DISAGREES_WITH_REGISTRY: {
794
439
  phase: "pre_send",
795
- template: "Token {token} could not be looked up; nothing was sent.",
796
- params: { token: "string" }
440
+ template: "Supplied symbol {suppliedSymbol} for {token} disagrees with the registered trading symbol {registeredSymbol}.",
441
+ params: {
442
+ token: "string",
443
+ suppliedSymbol: "string",
444
+ registeredSymbol: "string"
445
+ }
797
446
  },
798
- INVALID_SIGNING_HASH_LENGTH: {
447
+ CREATE_POOL_NEEDS_CLASS_KEY: {
799
448
  phase: "pre_send",
800
- template: "secp256k1 can only sign a 32-byte hash (got {length}).",
801
- params: { length: "number" }
449
+ template: "createPool needs each token as a class key; {token} is a bare trading symbol, which does not carry one.",
450
+ params: { token: "string" }
802
451
  },
803
- MISSING_WALLET_ADDRESS: {
452
+ AMOUNT_IS_JS_NUMBER: {
804
453
  phase: "pre_send",
805
- template: "Invalid wallet address: No wallet address provided. Either provide a wallet address to the function you are calling, or set one when instantiating GSwapSDK.",
806
- params: {}
454
+ template: "Invalid {field}: use a decimal string or BigNumber, not a JavaScript number",
455
+ params: { field: "string" }
807
456
  },
808
- INVALID_WALLET_ADDRESS: {
457
+ AMOUNT_NOT_FINITE: {
809
458
  phase: "pre_send",
810
- template: "Invalid {field}: \"{value}\" is not an accepted wallet address form.",
459
+ template: "Invalid {field}: must be a finite number (got {value})",
811
460
  params: {
812
461
  field: "string",
813
462
  value: "string"
814
463
  }
815
464
  },
816
- INVALID_DECIMAL: {
817
- phase: "pre_send",
818
- template: "Invalid decimal quantity: {value}",
819
- params: { value: "string" }
820
- },
821
- PERSONAL_SIGN_NOT_JSON: {
465
+ AMOUNT_NOT_POSITIVE: {
822
466
  phase: "pre_send",
823
- template: "Personal-sign DTO is not valid JSON",
824
- params: {}
467
+ template: "Invalid {field}: must be positive (got {value})",
468
+ params: {
469
+ field: "string",
470
+ value: "string"
471
+ }
825
472
  },
826
- PERSONAL_SIGN_NOT_OBJECT: {
473
+ AMOUNT_NEGATIVE: {
827
474
  phase: "pre_send",
828
- template: "Personal-sign DTO must be a JSON object",
829
- params: {}
475
+ template: "Invalid {field}: must not be negative (got {value})",
476
+ params: {
477
+ field: "string",
478
+ value: "string"
479
+ }
830
480
  },
831
- PERSONAL_SIGN_BAD_PREFIX: {
481
+ PRICE_VALUES_IS_JS_NUMBER: {
832
482
  phase: "pre_send",
833
- template: "Personal-sign DTO prefix is not an EIP-191 \"Ethereum Signed Message\" header",
483
+ template: "Invalid price values: use decimal strings or BigNumber values, not JavaScript numbers",
834
484
  params: {}
835
485
  },
836
- PERSONAL_SIGN_ENVELOPE_FIELDS: {
837
- phase: "pre_send",
838
- template: "Personal-sign DTO carries signing-envelope field(s): {fields}",
839
- params: { fields: "string" }
840
- },
841
- PERSONAL_SIGN_NOT_CANONICAL: {
842
- phase: "pre_send",
843
- template: "Personal-sign DTO is not in canonical serialized form",
844
- params: {}
845
- },
846
- EIP712_NAME_COLLISION: {
847
- phase: "pre_send",
848
- template: "Name collisions are not supported when deriving EIP-712 types ({name}).",
849
- params: { name: "string" }
850
- },
851
- EIP712_UNSUPPORTED_VALUE: {
852
- phase: "pre_send",
853
- template: "Unsupported EIP-712 value of type {valueType}.",
854
- params: { valueType: "string" }
855
- },
856
- TYPED_DATA_NOT_STRING: {
857
- phase: "pre_send",
858
- template: "Invalid typed data: {what} must be a string",
859
- params: { what: "string" }
860
- },
861
- TYPED_DATA_UNPAIRED_SURROGATE: {
862
- phase: "pre_send",
863
- template: "Invalid typed data: {what} has an unpaired surrogate",
864
- params: { what: "string" }
865
- },
866
- TYPED_DATA_NOT_HEX_BYTES: {
867
- phase: "pre_send",
868
- template: "Invalid typed data: {what} must be an even-length 0x hex string",
869
- params: { what: "string" }
870
- },
871
- TYPED_DATA_NOT_SAFE_INTEGER: {
872
- phase: "pre_send",
873
- template: "Invalid typed data: {what} must be a safe integer",
874
- params: { what: "string" }
875
- },
876
- TYPED_DATA_NOT_INTEGER: {
877
- phase: "pre_send",
878
- template: "Invalid typed data: {what} must be an integer",
879
- params: { what: "string" }
880
- },
881
- TYPED_DATA_NOT_BOOLEAN: {
882
- phase: "pre_send",
883
- template: "Invalid typed data: bool must be a boolean",
884
- params: {}
885
- },
886
- TYPED_DATA_BAD_ADDRESS: {
887
- phase: "pre_send",
888
- template: "Invalid typed data: address must be 0x followed by 40 hex digits",
889
- params: {}
890
- },
891
- TYPED_DATA_BAD_CHECKSUM: {
892
- phase: "pre_send",
893
- template: "Invalid typed data: address has a bad checksum",
894
- params: {}
895
- },
896
- TYPED_DATA_BYTES_LENGTH: {
897
- phase: "pre_send",
898
- template: "Invalid typed data: {type} must be exactly {width} bytes",
899
- params: {
900
- type: "string",
901
- width: "number"
902
- }
903
- },
904
- TYPED_DATA_OUT_OF_BOUNDS: {
905
- phase: "pre_send",
906
- template: "Invalid typed data: value out of bounds for {type}",
907
- params: { type: "string" }
908
- },
909
- TYPED_DATA_UNSUPPORTED_TYPE: {
910
- phase: "pre_send",
911
- template: "Invalid typed data: unsupported type \"{type}\"",
912
- params: { type: "string" }
913
- },
914
- TYPED_DATA_FIELDS_NOT_LIST: {
915
- phase: "pre_send",
916
- template: "Invalid typed data: type {typeName} must be a list of fields",
917
- params: { typeName: "string" }
918
- },
919
- TYPED_DATA_FIELD_MALFORMED: {
920
- phase: "pre_send",
921
- template: "Invalid typed data: type {typeName} has a field without a string name and type",
922
- params: { typeName: "string" }
923
- },
924
- TYPED_DATA_INVALID_FIELD_NAME: {
925
- phase: "pre_send",
926
- template: "Invalid typed data: invalid field name \"{name}\"",
927
- params: { name: "string" }
928
- },
929
- TYPED_DATA_DUPLICATE_FIELD: {
930
- phase: "pre_send",
931
- template: "Invalid typed data: duplicate field \"{name}\" in {typeName}",
932
- params: {
933
- name: "string",
934
- typeName: "string"
935
- }
936
- },
937
- TYPED_DATA_INVALID_TYPE_NAME: {
938
- phase: "pre_send",
939
- template: "Invalid typed data: invalid type name \"{name}\"",
940
- params: { name: "string" }
941
- },
942
- TYPED_DATA_CIRCULAR_TYPE: {
943
- phase: "pre_send",
944
- template: "Invalid typed data: circular type reference in {typeName}",
945
- params: { typeName: "string" }
946
- },
947
- TYPED_DATA_UNKNOWN_FIELD_TYPE: {
948
- phase: "pre_send",
949
- template: "Invalid typed data: unknown type \"{type}\" in {typeName}",
950
- params: {
951
- type: "string",
952
- typeName: "string"
953
- }
954
- },
955
- TYPED_DATA_NO_PRIMARY_TYPE: {
956
- phase: "pre_send",
957
- template: "Invalid typed data: missing primary type",
958
- params: {}
959
- },
960
- TYPED_DATA_AMBIGUOUS_PRIMARY_TYPE: {
961
- phase: "pre_send",
962
- template: "Invalid typed data: ambiguous primary types or unused types ({roots})",
963
- params: { roots: "string" }
964
- },
965
- TYPED_DATA_ARRAY_EXPECTED: {
966
- phase: "pre_send",
967
- template: "Invalid typed data: {type} array value must be an array",
968
- params: { type: "string" }
969
- },
970
- TYPED_DATA_ARRAY_LENGTH_MISMATCH: {
971
- phase: "pre_send",
972
- template: "Invalid typed data: array length mismatch",
973
- params: {}
974
- },
975
- TYPED_DATA_STRUCT_NOT_OBJECT: {
976
- phase: "pre_send",
977
- template: "Invalid typed data: {typeName} value must be an object",
978
- params: { typeName: "string" }
979
- },
980
- TYPED_DATA_MISSING_FIELD: {
981
- phase: "pre_send",
982
- template: "Invalid typed data: {typeName} value is missing field \"{field}\"",
983
- params: {
984
- typeName: "string",
985
- field: "string"
986
- }
987
- },
988
- TYPED_DATA_DOMAIN_NOT_OBJECT: {
989
- phase: "pre_send",
990
- template: "Invalid typed data: domain must be an object",
991
- params: {}
992
- },
993
- TYPED_DATA_INVALID_DOMAIN_KEY: {
994
- phase: "pre_send",
995
- template: "Invalid typed data: invalid typed-data domain key {key}",
996
- params: { key: "string" }
997
- },
998
- NON_CANONICAL_PAIR: {
999
- phase: "pre_send",
1000
- template: "The pair {token0}/{token1} is not in canonical order: token0 must sort before token1.",
1001
- params: {
1002
- token0: "string",
1003
- token1: "string"
1004
- }
1005
- },
1006
- UNORDERABLE_SYMBOLS: {
1007
- phase: "pre_send",
1008
- template: "Unable to order trading symbols: expected a pair of symbols, got {received}.",
1009
- params: { received: "string" }
1010
- },
1011
- INVALID_TOKEN_CLASS_KEY: {
1012
- phase: "pre_send",
1013
- template: "Invalid token class key \"{tokenClassKey}\": expected a collection followed by category, type and additionalKey",
1014
- params: { tokenClassKey: "string" }
1015
- },
1016
- INCOMPLETE_TOKEN_CLASS_KEY: {
1017
- phase: "pre_send",
1018
- template: "Token class key \"{compositeKey}\" is missing {part}.",
1019
- params: {
1020
- compositeKey: "string",
1021
- part: "string"
1022
- }
1023
- },
1024
- INVALID_TRADING_SYMBOL: {
1025
- phase: "pre_send",
1026
- template: "Invalid trading symbol \"{symbol}\": must be 1-32 ASCII letters and digits and not a reserved word (Token, Unit, none).",
1027
- params: { symbol: "string" }
1028
- },
1029
- NO_VALID_TRADING_SYMBOL: {
1030
- phase: "pre_send",
1031
- template: "No valid trading symbol for token class {classKey}: \"{displaySymbol}\" is not an acceptable symbol and none is registered",
1032
- params: {
1033
- classKey: "string",
1034
- displaySymbol: "string"
1035
- }
1036
- },
1037
- TOKEN_SYMBOL_NOT_REGISTERED: {
1038
- phase: "pre_send",
1039
- template: "Token {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.",
1040
- params: { token: "string" }
1041
- },
1042
- SYMBOL_DISAGREES_WITH_REGISTRY: {
1043
- phase: "pre_send",
1044
- template: "Supplied symbol {suppliedSymbol} for {token} disagrees with the registered trading symbol {registeredSymbol}.",
1045
- params: {
1046
- token: "string",
1047
- suppliedSymbol: "string",
1048
- registeredSymbol: "string"
1049
- }
1050
- },
1051
- CREATE_POOL_NEEDS_CLASS_KEY: {
1052
- phase: "pre_send",
1053
- template: "createPool needs each token as a class key; {token} is a bare trading symbol, which does not carry one.",
1054
- params: { token: "string" }
1055
- },
1056
- INVALID_UNIQUE_KEY: {
1057
- phase: "pre_send",
1058
- template: "The uniqueKey \"{uniqueKey}\" must be 1-128 characters of A-Z, a-z, 0-9, \".\", \"_\", \":\" or \"-\".",
1059
- params: { uniqueKey: "string" }
1060
- },
1061
- INVALID_UNIQUE_KEY_PREFIX: {
1062
- phase: "pre_send",
1063
- template: "uniqueKeyPrefix \"{uniqueKeyPrefix}\" must be at most {maxLength} characters of A-Z, a-z, 0-9, \".\", \"_\", \":\" or \"-\".",
1064
- params: {
1065
- uniqueKeyPrefix: "string",
1066
- maxLength: "number"
1067
- }
1068
- },
1069
- AMOUNT_IS_JS_NUMBER: {
1070
- phase: "pre_send",
1071
- template: "Invalid {field}: use a decimal string or BigNumber, not a JavaScript number",
1072
- params: { field: "string" }
1073
- },
1074
- AMOUNT_NOT_FINITE: {
1075
- phase: "pre_send",
1076
- template: "Invalid {field}: must be a finite number (got {value})",
1077
- params: {
1078
- field: "string",
1079
- value: "string"
1080
- }
1081
- },
1082
- AMOUNT_NOT_POSITIVE: {
1083
- phase: "pre_send",
1084
- template: "Invalid {field}: must be positive (got {value})",
1085
- params: {
1086
- field: "string",
1087
- value: "string"
1088
- }
1089
- },
1090
- AMOUNT_NEGATIVE: {
1091
- phase: "pre_send",
1092
- template: "Invalid {field}: must not be negative (got {value})",
1093
- params: {
1094
- field: "string",
1095
- value: "string"
1096
- }
1097
- },
1098
- PRICE_VALUES_IS_JS_NUMBER: {
1099
- phase: "pre_send",
1100
- template: "Invalid price values: use decimal strings or BigNumber values, not JavaScript numbers",
1101
- params: {}
1102
- },
1103
- INVALID_PRICE_VALUES: {
486
+ INVALID_PRICE_VALUES: {
1104
487
  phase: "pre_send",
1105
488
  template: "Invalid price values: all prices must be finite and positive (spot {spotPrice}, lower {lowerPrice}, upper {upperPrice})",
1106
489
  params: {
@@ -1279,156 +662,217 @@ const GSWAP_ERROR_CATALOG = {
1279
662
  min: "number",
1280
663
  max: "number"
1281
664
  }
665
+ },
666
+ INVALID_MARKET_TOKEN: {
667
+ phase: "pre_send",
668
+ template: "A market read names a token by a symbol or class key of 1 to 120 letters, digits and . _ $ + / : | - characters (got \"{token}\").",
669
+ params: { token: "string" }
670
+ },
671
+ INVALID_TIMEFRAME: {
672
+ phase: "pre_send",
673
+ template: "timeframe \"{value}\" is not allowed; it must be one of {allowed}.",
674
+ params: {
675
+ value: "string",
676
+ allowed: "string"
677
+ }
678
+ },
679
+ INVALID_SEASON_ID: {
680
+ phase: "pre_send",
681
+ template: "seasonId must be an integer of at least 1 (got {value}).",
682
+ params: { value: "string" }
683
+ },
684
+ INVALID_POLL_INTERVAL: {
685
+ phase: "pre_send",
686
+ template: "intervalMs must be a finite number of milliseconds of at least 1 (got {value}).",
687
+ params: { value: "string" }
1282
688
  }
1283
689
  };
1284
690
  /**
1285
- * The English template of every error code, `{placeholder}` markers included. An optional seed
1286
- * or fallback for an app's own translations; the SDK's `message` is generated from it.
691
+ * The catalog `GSwapSDKError` reads templates and phases from: core's entries and the DEX entries,
692
+ * merged (never redefined: the two key sets are disjoint, pinned by tests/error_catalog.test.ts).
693
+ */
694
+ const GSWAP_SDK_ERROR_ENTRIES = {
695
+ ...GALA_ERROR_CATALOG,
696
+ ...GSWAP_ERROR_CATALOG
697
+ };
698
+ /**
699
+ * The English template of every DEX error code, `{placeholder}` markers included. An optional seed
700
+ * or fallback for an app's own translations; the SDK's `message` is generated from it. The core
701
+ * codes' templates are `galaErrorTemplates` from `@gala-chain/gala-core-sdk`: an app that seeds
702
+ * every code a `GSwapSDKError` can carry merges the two.
1287
703
  */
1288
704
  const gswapErrorTemplates = Object.fromEntries(Object.entries(GSWAP_ERROR_CATALOG).map(([code, entry]) => [code, entry.template]));
1289
- const PLACEHOLDER = /\{([A-Za-z0-9_]+)\}/gu;
1290
- /** Fills a template's `{placeholders}` from `params`; an unknown placeholder is left as written. */
1291
- function renderGSwapErrorTemplate(template, params) {
1292
- return template.replace(PLACEHOLDER, (whole, name) => {
1293
- const value = params[name];
1294
- return typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? String(value) : whole;
1295
- });
1296
- }
1297
705
 
1298
706
  //#endregion
1299
- //#region src/classes/gswap_sdk_error.ts
1300
- /**
1301
- * The HTTP status of a version wall: the backend refuses an app version that is too old (HTTP 426
1302
- * Upgrade Required). It is `UPDATE_REQUIRED` on every request the SDK sends, whatever else the body
1303
- * says; a confirmation read keeps `CONFIRMATION_FAILED` because the write it confirms may be applied.
1304
- */
1305
- const UPDATE_REQUIRED_STATUS = 426;
707
+ //#region src/classes/gswap_error_phase_table.ts
1306
708
  /**
1307
- * Error raised by the SDK for validation, gateway, chain, or transaction failures.
1308
- *
1309
- * Branch on {@link code} (stable) and render {@link params} through your own localization.
1310
- * {@link message} is English generated from the catalog template for logs and developers: it
1311
- * is not stable and must never be shown to a user or matched on.
709
+ * The phase of every DEX catalog code, WITHOUT the catalog. The failure guards (`classifyFailure`,
710
+ * `mayHaveBeenQueued`, ...) need only a code's phase, and the catalog's English templates are
711
+ * ~15 kB: reading the phase from here keeps an app shell that imports a guard from loading them.
712
+ * The catalog entry is still where a code is declared; this table is checked against it AT
713
+ * COMPILE TIME, in both directions (a missing code, an extra code or a different phase is a type
714
+ * error), so the two cannot drift. The core codes' phases are core's `GALA_ERROR_PHASE_BY_CODE`.
1312
715
  */
1313
- var GSwapSDKError = class GSwapSDKError extends Error {
1314
- /** The stable, machine-readable code. */
1315
- code;
1316
- /** The named placeholders of the error: a flat object of primitives, safe to localize with. */
1317
- params;
1318
- /** The server's `Retry-After` in milliseconds (a `RATE_LIMITED` answer), when it sent one. */
1319
- retryAfterMs;
1320
- /** The HTTP status of the response that caused this error, when there was one. */
1321
- status;
1322
- /**
1323
- * The response body parsed as JSON, when the failing response had one; `undefined` for a body
1324
- * that was not JSON (see {@link bodyText}). Never part of {@link message}.
1325
- */
1326
- body;
1327
- /**
1328
- * The raw text of a failing response whose body was NOT JSON (a proxy's HTML 502, a WAF page,
1329
- * an empty body). It is kept here for diagnostics and is never copied into {@link message},
1330
- * so it cannot reach a user interface by accident.
1331
- */
1332
- bodyText;
1333
- /** The URL the failing request went to, when there was one. */
1334
- url;
1335
- /** The `uniqueKey` of the write this error is about, when there is one. */
1336
- uniqueKey;
1337
- /** The transaction id of the queued request, when known. */
1338
- requestTransactionId;
1339
- /** The transaction id of the apply, when known. */
1340
- applyTransactionId;
1341
- /** The block the apply landed in, when known. */
1342
- blockNumber;
1343
- /**
1344
- * How far the operation had got when this was thrown, against the request it exists to send
1345
- * (see {@link GSwapErrorPhase}): `pre_send` means NOTHING was transmitted, so a write that
1346
- * failed this way cannot have been queued; `in_flight` means the request left and no answer was
1347
- * read; `post_send` means an answer was read or the write was already accepted. Every catalog
1348
- * code declares its phase, and a throw site can refine it. Read it with
1349
- * {@link isPreSendError}; `classifyFailure` already folds it into `outcome` and `retryable`.
1350
- */
1351
- phase;
1352
- /**
1353
- * True when this error came from the shadow (dark) path: `swapShadow`, `submitSwapShadow` or
1354
- * `ChainGateway.submitShadow`, whatever failed there (a validation error, the signer, the
1355
- * transport, `SHADOW_NOT_QUEUED`). The classification is the same as on the public path; this
1356
- * is how an app applies its own dark-swap policy (the wallet and frontend_v2 never offer a
1357
- * public fallback or Retry after one). A shadow gateway refusal that RESOLVES (`{ ok: false }`)
1358
- * is not an error and has no flag: the call itself says it was shadow.
1359
- */
1360
- shadow;
1361
- /**
1362
- * Whether this failure is a WRITE's, as the guards read it: `false` only for the failure of a
1363
- * READ (a quote, a pool list, an apply-results read), which has no `uniqueKey` to confirm and
1364
- * nothing pending, so a 5xx there is `transient` and retryable. It is `true` for everything else,
1365
- * and always when {@link uniqueKey} is set. That is fail-closed: a failure that does not say it
1366
- * is a read is judged a write, where a 5xx is `unknown` and never retried. Apps that carry an
1367
- * error through their own wrapper copy this field next to `uniqueKey` (`FailureView.write`).
1368
- */
1369
- write;
1370
- /**
1371
- * On `SIGNING_FAILED`: the code the wallet put on the error its signer threw, when it set one: an
1372
- * EIP-1193 / JSON-RPC number such as `4001` (the person declined) or `-32603`, or an ethers /
1373
- * viem string such as `'ACTION_REJECTED'`. It is read from the `cause`'s own `code` and is
1374
- * always a short primitive; `undefined` when the wallet sent none, and on every other code. It is
1375
- * a typed field, not a param: it is data for an app's analytics or its own wallet-specific
1376
- * wording, never part of `message`, and `params.reason` stays the stable thing to branch on.
1377
- */
1378
- walletCode;
1379
- constructor(code, params, context = {}) {
1380
- super(renderGSwapErrorTemplate(GSWAP_ERROR_CATALOG[code].template, params), context.cause === void 0 ? void 0 : { cause: context.cause });
1381
- this.name = "GSwapSDKError";
1382
- this.code = code;
1383
- this.params = params;
1384
- this.retryAfterMs = context.retryAfterMs;
1385
- this.status = context.status;
1386
- this.body = typeof context.body === "string" ? void 0 : context.body;
1387
- this.bodyText = typeof context.body === "string" ? context.body : void 0;
1388
- this.url = context.url;
1389
- this.uniqueKey = context.uniqueKey;
1390
- this.requestTransactionId = context.requestTransactionId;
1391
- this.applyTransactionId = context.applyTransactionId;
1392
- this.blockNumber = context.blockNumber;
1393
- this.phase = context.phase ?? GSWAP_ERROR_CATALOG[code].phase;
1394
- this.shadow = context.shadow ?? false;
1395
- this.write = context.uniqueKey !== void 0 && context.uniqueKey !== "" ? true : context.write !== false;
1396
- this.walletCode = context.walletCode;
1397
- }
1398
- /**
1399
- * The SDK error brand (`Symbol.for('@gala-chain/gswap-sdk/error')`), always `true`. The
1400
- * dependency-free helpers (`isWalletUserRejection`, `isGSwapErrorLike`) recognise an SDK error
1401
- * by it instead of `instanceof`, so they work across copies of the SDK and without importing
1402
- * this class. It lives on the prototype: it is not an own property and is never copied or logged.
1403
- */
1404
- get [GSWAP_ERROR_BRAND]() {
1405
- return true;
1406
- }
1407
- /**
1408
- * True when the failure means "that market, pool or position does not exist" rather than
1409
- * "something went wrong": an HTTP 404, a pair with no pool at any fee tier
1410
- * (`NO_POOL_AVAILABLE`), or a token the DEX contract has no trading symbol for
1411
- * (`UNKNOWN_TOKEN`). A pool that exists but is too thin (`INSUFFICIENT_LIQUIDITY`) is NOT
1412
- * not-found.
1413
- */
1414
- get notFound() {
1415
- return this.status === 404 || this.code === "NO_POOL_AVAILABLE" || this.code === "UNKNOWN_TOKEN";
1416
- }
1417
- /** Create the standard missing-signer error. */
1418
- static noSignerError() {
1419
- return new GSwapSDKError("NO_SIGNER", {});
1420
- }
1421
- /** Create the error used when confirming a position write without a configured wallet address. */
1422
- static noWalletAddressError() {
1423
- return new GSwapSDKError("NO_WALLET_ADDRESS", {});
1424
- }
1425
- /**
1426
- * Create the error used when no pool can quote the requested pair. `source` is the backend
1427
- * answer that said so; its status and body are kept.
1428
- */
1429
- static noPoolAvailableError(tokenIn, tokenOut, fee, source) {
1430
- return new GSwapSDKError("NO_POOL_AVAILABLE", {
1431
- tokenIn: tokenText(tokenIn),
716
+ const GSWAP_ERROR_PHASE_BY_CODE = {
717
+ HTTP_ERROR: "post_send",
718
+ UPDATE_REQUIRED: "post_send",
719
+ REQUEST_FAILED: "in_flight",
720
+ METHOD_NOT_SUPPORTED: "pre_send",
721
+ SUBMISSION_OUTCOME_UNKNOWN: "in_flight",
722
+ GATEWAY_REPORTED_ERROR: "post_send",
723
+ GATEWAY_DATA_MISSING: "post_send",
724
+ GATEWAY_MODE_INVALID: "post_send",
725
+ GATEWAY_SYNC_REQUEST_TRADE: "post_send",
726
+ MISSING_UNIQUE_KEY: "post_send",
727
+ UNIQUE_KEY_MISMATCH: "post_send",
728
+ SHADOW_NOT_QUEUED: "post_send",
729
+ SHADOW_SUBMIT_NEEDS_WALLET_ADDRESS: "pre_send",
730
+ METHOD_NOT_ALLOWED: "post_send",
731
+ SIGNATURE_INVALID: "post_send",
732
+ SIGNER_MISMATCH: "post_send",
733
+ DTO_INVALID: "post_send",
734
+ BOUNDS_VIOLATION: "post_send",
735
+ SYMBOL_CONFLICT: "post_send",
736
+ RATE_LIMITED: "post_send",
737
+ CHAIN_DISPATCH_FAILED: "post_send",
738
+ GATEWAY_REJECTED: "post_send",
739
+ CHAIN_REJECTED: "post_send",
740
+ INVALID_CHAIN_RESPONSE: "post_send",
741
+ INVALID_POSITION_RESPONSE: "post_send",
742
+ CONFIRMATION_TIMEOUT: "post_send",
743
+ CONFIRMATION_ABORTED: "post_send",
744
+ CONFIRMATION_FAILED: "post_send",
745
+ CONFIRMATION_MALFORMED: "post_send",
746
+ CONFIRMATION_KEY_MISMATCH: "post_send",
747
+ NO_SIGNER: "pre_send",
748
+ NO_WALLET_ADDRESS: "post_send",
749
+ NO_POOL_AVAILABLE: "post_send",
750
+ INSUFFICIENT_LIQUIDITY: "post_send",
751
+ UNKNOWN_TOKEN: "pre_send",
752
+ SYMBOL_NOT_FOUND: "pre_send",
753
+ STREAM_API_KEY_UNUSABLE: "pre_send",
754
+ STREAM_WEBSOCKET_UNAVAILABLE: "pre_send",
755
+ STREAM_NOT_CONNECTED: "pre_send",
756
+ STREAM_DISCONNECTED: "in_flight",
757
+ STREAM_REQUEST_TIMEOUT: "in_flight",
758
+ STREAM_CONNECT_FAILED: "in_flight",
759
+ STREAM_CLOSED_BEFORE_CONNECT: "in_flight",
760
+ STREAM_CONNECTION_CLOSED: "in_flight",
761
+ TOKEN_RESOLUTION_FAILED: "pre_send",
762
+ MISSING_WALLET_ADDRESS: "pre_send",
763
+ NON_CANONICAL_PAIR: "pre_send",
764
+ UNORDERABLE_SYMBOLS: "pre_send",
765
+ INVALID_TOKEN_CLASS_KEY: "pre_send",
766
+ INCOMPLETE_TOKEN_CLASS_KEY: "pre_send",
767
+ INVALID_TRADING_SYMBOL: "pre_send",
768
+ NO_VALID_TRADING_SYMBOL: "pre_send",
769
+ TOKEN_SYMBOL_NOT_REGISTERED: "pre_send",
770
+ SYMBOL_DISAGREES_WITH_REGISTRY: "pre_send",
771
+ CREATE_POOL_NEEDS_CLASS_KEY: "pre_send",
772
+ AMOUNT_IS_JS_NUMBER: "pre_send",
773
+ AMOUNT_NOT_FINITE: "pre_send",
774
+ AMOUNT_NOT_POSITIVE: "pre_send",
775
+ AMOUNT_NEGATIVE: "pre_send",
776
+ PRICE_VALUES_IS_JS_NUMBER: "pre_send",
777
+ INVALID_PRICE_VALUES: "pre_send",
778
+ INVALID_PRICE_RANGE: "pre_send",
779
+ INVALID_PRICE_BOUNDS: "pre_send",
780
+ PRICE_BOUNDS_ORDER: "pre_send",
781
+ INVALID_STARTING_PRICE: "pre_send",
782
+ INVALID_SQRT_PRICE: "pre_send",
783
+ INVALID_TOKEN_DECIMALS: "pre_send",
784
+ TICKS_NOT_INTEGERS: "pre_send",
785
+ INVALID_TICK_RANGE: "pre_send",
786
+ TICKS_OUT_OF_BOUNDS: "pre_send",
787
+ TICKS_NOT_ALIGNED: "pre_send",
788
+ INVALID_TICK_SPACING: "pre_send",
789
+ INVALID_FEE: "pre_send",
790
+ EXACT_AMOUNT_REQUIRED: "pre_send",
791
+ EXACTLY_ONE_OF_REQUIRED: "pre_send",
792
+ AT_MOST_ONE_OF_ALLOWED: "pre_send",
793
+ POOL_MISMATCH: "pre_send",
794
+ INVALID_DTO: "pre_send",
795
+ DTO_NOT_CANONICAL: "pre_send",
796
+ SEARCH_TOO_LONG: "pre_send",
797
+ INVALID_SORT_BY: "pre_send",
798
+ INVALID_SORT_ORDER: "pre_send",
799
+ INVALID_PAGE: "pre_send",
800
+ INVALID_LIMIT: "pre_send",
801
+ INVALID_MARKET_TOKEN: "pre_send",
802
+ INVALID_TIMEFRAME: "pre_send",
803
+ INVALID_SEASON_ID: "pre_send",
804
+ INVALID_POLL_INTERVAL: "pre_send"
805
+ };
806
+ /** True for a DEX code (gswap-sdk's own catalog). */
807
+ function isDexCatalogCode(code) {
808
+ return Object.hasOwn(GSWAP_ERROR_PHASE_BY_CODE, code);
809
+ }
810
+ /** True for a code a `GSwapSDKError` can carry: a core code or a DEX code. */
811
+ function isCatalogCode(code) {
812
+ return isGalaCatalogCode(code) || isDexCatalogCode(code);
813
+ }
814
+ /**
815
+ * The phase of a view: the one it states, else the catalog's phase for its code (core's or the
816
+ * DEX catalog's), else `undefined`.
817
+ */
818
+ function phaseOfView(view) {
819
+ if (view.phase !== void 0) return view.phase;
820
+ if (isGalaCatalogCode(view.code)) return GALA_ERROR_PHASE_BY_CODE[view.code];
821
+ return isDexCatalogCode(view.code) ? GSWAP_ERROR_PHASE_BY_CODE[view.code] : void 0;
822
+ }
823
+
824
+ //#endregion
825
+ //#region src/classes/gswap_sdk_error.ts
826
+ /**
827
+ * The HTTP status of a version wall: the backend refuses an app version that is too old (HTTP 426
828
+ * Upgrade Required). It is `UPDATE_REQUIRED` on every request the SDK sends, whatever else the body
829
+ * says; a confirmation read keeps `CONFIRMATION_FAILED` because the write it confirms may be applied.
830
+ */
831
+ const UPDATE_REQUIRED_STATUS = 426;
832
+ /**
833
+ * Error raised by gswap-sdk for validation, gateway, chain, or transaction failures. It is a
834
+ * `GalaSDKError` (`@gala-chain/gala-core-sdk`), and can carry any code of the core catalog or of
835
+ * gswap-sdk's own DEX catalog: the signing, transport and wallet failures of a gswap flow are core
836
+ * codes, thrown either as a `GSwapSDKError` by gswap-sdk or as a plain `GalaSDKError` by core
837
+ * itself (a signer, the fetch requestor). Catch both with `isGSwapError` (or core's `isGalaError`);
838
+ * `instanceof GSwapSDKError` alone misses the second kind.
839
+ *
840
+ * Branch on {@link code} (stable) and render {@link params} through your own localization.
841
+ * {@link message} is English generated from the catalog template for logs and developers: it
842
+ * is not stable and must never be shown to a user or matched on.
843
+ */
844
+ var GSwapSDKError = class GSwapSDKError extends GalaSDKError {
845
+ /** Core's catalog entries and the DEX entries: a `GSwapSDKError` can carry a code of either. */
846
+ static catalog = GSWAP_SDK_ERROR_ENTRIES;
847
+ constructor(code, params, context = {}) {
848
+ super(code, params, context);
849
+ this.name = "GSwapSDKError";
850
+ }
851
+ /**
852
+ * True when the failure means "that market, pool or position does not exist" rather than
853
+ * "something went wrong": an HTTP 404, a pair with no pool at any fee tier
854
+ * (`NO_POOL_AVAILABLE`), or a token the DEX contract has no trading symbol for
855
+ * (`UNKNOWN_TOKEN`). A pool that exists but is too thin (`INSUFFICIENT_LIQUIDITY`) is NOT
856
+ * not-found.
857
+ */
858
+ get notFound() {
859
+ return super.notFound || this.code === "NO_POOL_AVAILABLE" || this.code === "UNKNOWN_TOKEN";
860
+ }
861
+ /** Create the standard missing-signer error. */
862
+ static noSignerError() {
863
+ return new GSwapSDKError("NO_SIGNER", {});
864
+ }
865
+ /** Create the error used when confirming a position write without a configured wallet address. */
866
+ static noWalletAddressError() {
867
+ return new GSwapSDKError("NO_WALLET_ADDRESS", {});
868
+ }
869
+ /**
870
+ * Create the error used when no pool can quote the requested pair. `source` is the backend
871
+ * answer that said so; its status and body are kept.
872
+ */
873
+ static noPoolAvailableError(tokenIn, tokenOut, fee, source) {
874
+ return new GSwapSDKError("NO_POOL_AVAILABLE", {
875
+ tokenIn: tokenText(tokenIn),
1432
876
  tokenOut: tokenText(tokenOut),
1433
877
  fee: feeText(fee)
1434
878
  }, sourceContext(source));
@@ -1590,7 +1034,7 @@ function sourceContext(source) {
1590
1034
  };
1591
1035
  }
1592
1036
  function isGSwapError(value, code) {
1593
- return value instanceof GSwapSDKError && (code === void 0 || value.code === code);
1037
+ return value instanceof GalaSDKError && (code === void 0 || value.code === code);
1594
1038
  }
1595
1039
  /**
1596
1040
  * True when `value` is an SDK error that was thrown BEFORE anything was transmitted
@@ -1605,8 +1049,9 @@ function isGSwapError(value, code) {
1605
1049
  * error is pre-send too, and repeating the same call fails the same way.
1606
1050
  *
1607
1051
  * It also answers for a plain-object view of an SDK error (`{ code, phase? }`, see
1608
- * `FailureView`): the view's own `phase`, else the catalog's phase for its `code`. It is a plain
1609
- * `boolean`, not a type guard (a view is not a `GSwapSDKError`); use `isGSwapError` to narrow.
1052
+ * `FailureView`): the view's own `phase`, else the catalog's phase for its `code` (a core code or
1053
+ * a DEX code). It is a plain `boolean`, not a type guard (a view is not a `GSwapSDKError`); use
1054
+ * `isGSwapError` to narrow.
1610
1055
  *
1611
1056
  * @example
1612
1057
  * ```ts
@@ -1629,165 +1074,6 @@ function getStringProperty(value, property) {
1629
1074
  const propertyValue = value[property];
1630
1075
  return typeof propertyValue === "string" ? propertyValue : void 0;
1631
1076
  }
1632
- /** Parse a response body, preserving non-JSON text for error reporting. */
1633
- function parseJson(text) {
1634
- try {
1635
- return JSON.parse(text);
1636
- } catch {
1637
- return;
1638
- }
1639
- }
1640
-
1641
- //#endregion
1642
- //#region src/version.ts
1643
- /** Package version embedded by the bundler, with a source-test fallback. */
1644
- const SDK_VERSION = "1.2.0";
1645
-
1646
- //#endregion
1647
- //#region src/utils/transport.ts
1648
- /**
1649
- * Arms the abort timer for a request. `Infinity` means "never time out": `setTimeout` would turn
1650
- * it into 1 ms, so no timer is armed at all. Returns a function that disarms it.
1651
- */
1652
- function armTimeout(controller, timeoutMs) {
1653
- if (timeoutMs === Infinity) return () => void 0;
1654
- const timer = setTimeout(() => controller.abort(), timeoutMs);
1655
- return () => clearTimeout(timer);
1656
- }
1657
- /**
1658
- * Validates a request timeout in milliseconds: a positive finite number, or `Infinity` for none.
1659
- *
1660
- * @throws GSwapSDKError `INVALID_TIMEOUT` (`params.field` = `name`) otherwise.
1661
- */
1662
- function validateTimeoutMs(timeoutMs, name) {
1663
- if (timeoutMs === Infinity || Number.isFinite(timeoutMs) && timeoutMs > 0) return timeoutMs;
1664
- throw new GSwapSDKError("INVALID_TIMEOUT", {
1665
- field: name,
1666
- value: timeoutMs
1667
- });
1668
- }
1669
- const READABLE_QUERY_SAFE = /[A-Za-z0-9\-._~!'()*$|:,]/u;
1670
- function encodeReadable(text) {
1671
- let out = "";
1672
- for (const char of text) if (char === " ") out += "+";
1673
- else if (READABLE_QUERY_SAFE.test(char)) out += char;
1674
- else out += encodeScalar(char);
1675
- return out;
1676
- }
1677
- /** Percent-encodes one code point; a lone surrogate becomes U+FFFD, as `URLSearchParams` does. */
1678
- function encodeScalar(char) {
1679
- try {
1680
- return encodeURIComponent(char);
1681
- } catch {
1682
- return "%EF%BF%BD";
1683
- }
1684
- }
1685
- /** Serializes GET parameters, in insertion order, in the encoding the wire options select. */
1686
- function buildQueryString(params, encoding = "standard") {
1687
- if (encoding === "readable") return Object.entries(params).map(([key, value]) => `${encodeReadable(key)}=${encodeReadable(value)}`).join("&");
1688
- return new URLSearchParams(params).toString();
1689
- }
1690
- /**
1691
- * The request headers for ANY SDK request, GET or POST, from one place so the wire options reach
1692
- * every request alike. Order, which is the order the requestor sees them in: `User-Agent`
1693
- * (`wire.userAgent`, default `GalaChain-SDK/<version>`, `false` omits it), `parts.defaults`,
1694
- * `wire.headers`, `Content-Type` (JSON bodies only), `parts.fixed`. A later entry replaces an
1695
- * earlier one of the same name regardless of case, so a header is never sent twice.
1696
- */
1697
- function buildRequestHeaders(wire, parts = {}) {
1698
- const entries = /* @__PURE__ */ new Map();
1699
- const put = (headers, last = false) => {
1700
- for (const [name, value] of Object.entries(headers ?? {})) {
1701
- if (last) entries.delete(name.toLowerCase());
1702
- entries.set(name.toLowerCase(), [name, value]);
1703
- }
1704
- };
1705
- if ((wire === null || wire === void 0 ? void 0 : wire.userAgent) !== false) put({ "User-Agent": (wire === null || wire === void 0 ? void 0 : wire.userAgent) ?? `GalaChain-SDK/${"1.2.0"}` });
1706
- put(parts.defaults);
1707
- put(wire === null || wire === void 0 ? void 0 : wire.headers);
1708
- if (parts.json === true && !entries.has("content-type")) put({ "Content-Type": "application/json" });
1709
- put(parts.fixed, true);
1710
- return Object.fromEntries(entries.values());
1711
- }
1712
- /**
1713
- * The default requestor: the global `fetch`, with a failure before any answer (offline, DNS,
1714
- * CORS) coded as `TRANSPORT_ERROR` and the original error kept as `cause`. An aborted request is
1715
- * left to {@link requestWithTimeout}, which knows whether the timeout or the caller aborted it.
1716
- * A requestor the app supplies is not wrapped: its rejection reaches the caller unchanged.
1717
- */
1718
- const fetchRequestor = async (url, init) => {
1719
- try {
1720
- return await fetch(url, init);
1721
- } catch (error) {
1722
- var _init$signal;
1723
- if ((init === null || init === void 0 || (_init$signal = init.signal) === null || _init$signal === void 0 ? void 0 : _init$signal.aborted) === true) throw error;
1724
- throw new GSwapSDKError("TRANSPORT_ERROR", { url }, {
1725
- url,
1726
- cause: error
1727
- });
1728
- }
1729
- };
1730
- async function requestWithTimeout(requestor, url, options, timeoutMs) {
1731
- const controller = new AbortController();
1732
- const disarm = armTimeout(controller, timeoutMs);
1733
- const externalSignal = options.signal;
1734
- const abortFromCaller = () => controller.abort();
1735
- externalSignal === null || externalSignal === void 0 || externalSignal.addEventListener("abort", abortFromCaller, { once: true });
1736
- try {
1737
- return await requestor(url, {
1738
- ...options,
1739
- signal: controller.signal
1740
- });
1741
- } catch (error) {
1742
- if ((externalSignal === null || externalSignal === void 0 ? void 0 : externalSignal.aborted) === true) throw error;
1743
- if (controller.signal.aborted) throw new GSwapSDKError("REQUEST_TIMEOUT", {
1744
- url,
1745
- timeoutMs
1746
- }, {
1747
- url,
1748
- cause: error
1749
- });
1750
- throw error;
1751
- } finally {
1752
- disarm();
1753
- externalSignal === null || externalSignal === void 0 || externalSignal.removeEventListener("abort", abortFromCaller);
1754
- }
1755
- }
1756
- async function readResponseBody(response) {
1757
- const text = await response.text();
1758
- return parseJson(text) ?? text;
1759
- }
1760
- /** Read a case-insensitive response header from either supported header shape. */
1761
- function readHeader(response, name) {
1762
- var _Object$entries$find;
1763
- const headers = response.headers;
1764
- if (headers === void 0) return void 0;
1765
- if (hasHeaderGetter(headers)) {
1766
- const value = headers.get(name);
1767
- return value === null || value === "" ? void 0 : value;
1768
- }
1769
- 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];
1770
- return value === void 0 || value === "" ? void 0 : value;
1771
- }
1772
- /** Read a Retry-After header (delta-seconds or an HTTP date) as non-negative milliseconds. */
1773
- function readRetryAfter(response) {
1774
- const raw = readHeader(response, "retry-after");
1775
- if (raw === void 0) return void 0;
1776
- const seconds = Number(raw);
1777
- if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1e3;
1778
- const timestamp = Date.parse(raw);
1779
- return Number.isNaN(timestamp) ? void 0 : Math.max(0, timestamp - Date.now());
1780
- }
1781
- /** Read a non-negative integer block number header. */
1782
- function readBlockNumberHeader(response, headerName = "x-block-number") {
1783
- const raw = readHeader(response, headerName);
1784
- if (raw === void 0) return void 0;
1785
- const blockNumber = Number(raw);
1786
- return Number.isInteger(blockNumber) && blockNumber >= 0 ? blockNumber : void 0;
1787
- }
1788
- function hasHeaderGetter(headers) {
1789
- return "get" in headers && typeof headers.get === "function";
1790
- }
1791
1077
 
1792
1078
  //#endregion
1793
1079
  //#region src/classes/apply_results.ts
@@ -1928,9 +1214,9 @@ function readAppliedResult(input, response, body, url) {
1928
1214
  url
1929
1215
  });
1930
1216
  }
1931
- if (status !== 1) throw malformed$1(input, response, body, url);
1217
+ if (status !== 1) throw malformed(input, response, body, url);
1932
1218
  const result = parseTradeExecution(record === null || record === void 0 ? void 0 : record["Data"]);
1933
- if (result === void 0) throw malformed$1(input, response, body, url);
1219
+ if (result === void 0) throw malformed(input, response, body, url);
1934
1220
  return {
1935
1221
  uniqueKey,
1936
1222
  transactionId: applyTransactionId,
@@ -1939,7 +1225,7 @@ function readAppliedResult(input, response, body, url) {
1939
1225
  result
1940
1226
  };
1941
1227
  }
1942
- function malformed$1(input, response, body, url) {
1228
+ function malformed(input, response, body, url) {
1943
1229
  return new AppliedTradeError("CONFIRMATION_MALFORMED", {}, {
1944
1230
  uniqueKey: input.uniqueKey,
1945
1231
  status: response.status,
@@ -2339,7 +1625,10 @@ var Assets = class {
2339
1625
  function parseUserAssets(value) {
2340
1626
  const data = getObjectProperty(value, "data");
2341
1627
  const tokens = data === null || data === void 0 ? void 0 : data["token"];
2342
- if (!Array.isArray(tokens) || !tokens.every(isRawUserAsset)) throw new GSwapSDKError("INVALID_CHAIN_RESPONSE", { resource: "user assets response" }, { body: value });
1628
+ if (!Array.isArray(tokens) || !tokens.every(isRawUserAsset)) throw new GSwapSDKError("INVALID_CHAIN_RESPONSE", { resource: "user assets response" }, {
1629
+ body: value,
1630
+ write: false
1631
+ });
2343
1632
  const count = data === null || data === void 0 ? void 0 : data["count"];
2344
1633
  return {
2345
1634
  tokens: tokens.map((token) => ({
@@ -2355,178 +1644,6 @@ function isRawUserAsset(value) {
2355
1644
  return typeof entry["image"] === "string" && typeof entry["name"] === "string" && typeof entry["decimals"] === "string" && /^\d+$/u.test(entry["decimals"]) && typeof entry["verify"] === "boolean" && typeof entry["symbol"] === "string" && typeof entry["quantity"] === "string";
2356
1645
  }
2357
1646
 
2358
- //#endregion
2359
- //#region src/utils/wallet_rejection.ts
2360
- /**
2361
- * What the SDK knows about a failure thrown by a WALLET (the signer an app supplies, or wallet
2362
- * code of the app's own): the code the wallet put on it, and whether it means the person declined.
2363
- * Both read only the wallet's own error value; neither shows or forwards its text.
2364
- */
2365
- /** Codes the EIP-1193 / ethers / viem families use for "the person declined in the wallet". */
2366
- const REJECTION_CODES = [
2367
- 4001,
2368
- "ACTION_REJECTED",
2369
- "USER_REJECTED"
2370
- ];
2371
- /** The `name` viem / wagmi give the error of a declined request. */
2372
- const REJECTION_NAME = "UserRejectedRequestError";
2373
- /**
2374
- * Wordings of a decline, matched case-insensitively as substrings. This is a LAST RESORT for
2375
- * wallets that set neither a `code` nor a `name`: it reads a third party's text, so it is broad on
2376
- * purpose (a superset of the matchers the Gala wallet and frontend_v2 used) and can in principle
2377
- * match an unrelated message that uses these words.
2378
- */
2379
- const REJECTION_PHRASES = [
2380
- "user rejected",
2381
- "user denied",
2382
- "user cancel",
2383
- "rejected the request",
2384
- "request rejected",
2385
- "rejected by user",
2386
- "action_rejected",
2387
- "cancelled",
2388
- "canceled"
2389
- ];
2390
- /** Properties wallets nest their text under (EIP-1193, ethers, viem, Gala wallet). */
2391
- const TEXT_KEYS = [
2392
- "message",
2393
- "Message",
2394
- "shortMessage",
2395
- "details",
2396
- "errorDetails",
2397
- "reason"
2398
- ];
2399
- /** Wallet errors nest their text at most a level or two deep; the bound also stops a cyclic object. */
2400
- const MAX_TEXT_DEPTH = 3;
2401
- function isRecord$2(value) {
2402
- return typeof value === "object" && value !== null;
2403
- }
2404
- function textsOf(value, depth = 0) {
2405
- if (typeof value === "string") return [value];
2406
- if (!isRecord$2(value) || depth >= MAX_TEXT_DEPTH) return [];
2407
- return TEXT_KEYS.flatMap((key) => textsOf(value[key], depth + 1));
2408
- }
2409
- function wordsADecline(text) {
2410
- const lower = text.toLowerCase();
2411
- return REJECTION_PHRASES.some((phrase) => lower.includes(phrase)) || lower.includes("signature") && lower.includes("cancel");
2412
- }
2413
- /**
2414
- * True when a wallet's error (or just its message text) says the person DECLINED the request in
2415
- * their wallet. This is exactly the test behind `SIGNING_FAILED` with `params.reason ===
2416
- * 'user_rejected'`, exported for wallet code outside the SDK (connect and registration flows, a
2417
- * v1 write path) so an app keeps no matcher of its own.
2418
- *
2419
- * It is decided from the wallet's own fields first: a `code` of `4001` (EIP-1193), `'ACTION_REJECTED'`
2420
- * (ethers) or `'USER_REJECTED'`, or `name === 'UserRejectedRequestError'` (viem). Only when those are
2421
- * absent is the wallet's TEXT read (`message`, `Message`, `shortMessage`, `details`, `errorDetails`,
2422
- * `reason`, up to three levels deep, or a bare string): "user rejected", "user denied", "user
2423
- * cancelled", "request rejected", "rejected by user", "rejected the request", "cancelled" /
2424
- * "canceled", or a "signature ... cancel" sentence. The wording fallback is a last resort for
2425
- * wallets that do not set codes; prefer `code` and `name` wherever you control the wallet.
2426
- *
2427
- * An SDK error is never read by its text: a `SIGNING_FAILED` is a decline exactly when its
2428
- * `params.reason` is `user_rejected`, and every other SDK error is not one. The same holds for a
2429
- * plain-object VIEW of an SDK error (`{ code: 'SIGNING_FAILED', params: { reason } }`, the shape
2430
- * of an app's wrapper): a string `code` plus an object `params` is decided by the code alone, with
2431
- * the wallet code list above still honoured for a view that is not branded, and its English is
2432
- * never read. This works without `instanceof`, so it is safe across two copies of the SDK.
2433
- *
2434
- * @example
2435
- * ```ts
2436
- * try {
2437
- * await wallet.connect();
2438
- * } catch (error) {
2439
- * if (isWalletUserRejection(error)) return; // the person said no: not an error to report
2440
- * report(error);
2441
- * }
2442
- * ```
2443
- */
2444
- function isWalletUserRejection(value) {
2445
- if (isGSwapErrorLike(value)) {
2446
- if (value.code === "SIGNING_FAILED") return value.params["reason"] === "user_rejected";
2447
- return !hasGSwapErrorBrand(value) && REJECTION_CODES.includes(value.code);
2448
- }
2449
- if (isRecord$2(value)) {
2450
- if (REJECTION_CODES.includes(value["code"]) || value["name"] === REJECTION_NAME) return true;
2451
- }
2452
- return textsOf(value).some(wordsADecline);
2453
- }
2454
- /** The longest wallet code kept: real ones (`4001`, `ACTION_REJECTED`) are short. */
2455
- const MAX_WALLET_CODE_LENGTH = 64;
2456
- /**
2457
- * The code a wallet put on its error: an EIP-1193 / JSON-RPC number (`4001`, `-32603`) or an
2458
- * ethers / viem string (`'ACTION_REJECTED'`), read from the error's own top-level `code`. Anything
2459
- * else (no code, a non-finite number, an empty or very long string, an object) is `undefined`, so
2460
- * the value is always a safe primitive. Used for `GSwapSDKError.walletCode`.
2461
- *
2462
- * @example
2463
- * ```ts
2464
- * walletDeclineCode({ code: 4001, message: 'User rejected the request.' }); // 4001
2465
- * walletDeclineCode(new Error('locked')); // undefined
2466
- * ```
2467
- */
2468
- function walletDeclineCode(value) {
2469
- if (typeof value !== "object" || value === null) return void 0;
2470
- const { code } = value;
2471
- if (typeof code === "number") return Number.isFinite(code) ? code : void 0;
2472
- if (typeof code === "string" && code.length > 0 && code.length <= MAX_WALLET_CODE_LENGTH) return code;
2473
- }
2474
-
2475
- //#endregion
2476
- //#region src/utils/pre_send.ts
2477
- /**
2478
- * The boundary of "nothing was sent". Every place where the SDK hands control to code the APP
2479
- * supplied BEFORE the request leaves (the signer, the token resolver) goes through here, so a
2480
- * failure of that code reaches the caller as a catalogued, pre-send SDK error with the original as
2481
- * `cause`, never as a bare value the SDK cannot classify. An SDK error thrown by that code is
2482
- * already catalogued and passes through untouched. Nothing here is used after the request has
2483
- * been sent: an app's own rejection AFTER send stays what it is, because the SDK cannot know what
2484
- * the server did with the write.
2485
- */
2486
- /**
2487
- * Signs a DTO with the app's signer. Whatever the signer throws that is not already an SDK error
2488
- * (a locked wallet, an unreachable extension, a wallet that declined) is `SIGNING_FAILED`
2489
- * (`reason` `user_rejected` or `failed`, the original as `cause`), and a signer that returns
2490
- * something other than an object is `SIGNING_FAILED` too: both are pre-send, so a retry is safe.
2491
- *
2492
- * @throws GSwapSDKError SIGNING_FAILED, or the SDK error the signer itself threw.
2493
- */
2494
- async function signPreSend(signer, method, dto) {
2495
- let signed;
2496
- try {
2497
- signed = await signer.signObject(method, dto);
2498
- } catch (cause) {
2499
- if (isGSwapError(cause)) throw cause;
2500
- const reason = isWalletUserRejection(cause) ? "user_rejected" : "failed";
2501
- throw new GSwapSDKError("SIGNING_FAILED", {
2502
- method,
2503
- reason
2504
- }, {
2505
- cause,
2506
- walletCode: walletDeclineCode(cause)
2507
- });
2508
- }
2509
- if (typeof signed !== "object" || signed === null) throw new GSwapSDKError("SIGNING_FAILED", {
2510
- method,
2511
- reason: "failed"
2512
- }, { cause: signed });
2513
- return signed;
2514
- }
2515
- /**
2516
- * Runs a token-resolution step (a resolver's `resolve` / `orderPair`, which may read the registry
2517
- * through the app's HTTP stack). A failure that is not an SDK error is `TOKEN_RESOLUTION_FAILED`
2518
- * (`token` names what was being resolved, the original is `cause`); an SDK error passes through.
2519
- *
2520
- * @throws GSwapSDKError TOKEN_RESOLUTION_FAILED, or the SDK error the step itself threw.
2521
- */
2522
- async function resolvePreSend(token, run) {
2523
- try {
2524
- return await run();
2525
- } catch (cause) {
2526
- throw isGSwapError(cause) ? cause : new GSwapSDKError("TOKEN_RESOLUTION_FAILED", { token }, { cause });
2527
- }
2528
- }
2529
-
2530
1647
  //#endregion
2531
1648
  //#region src/classes/dex_fees.ts
2532
1649
  /** DEX methods the backend accepts a fee quote for (its allow-list enumerates the same six). */
@@ -2577,7 +1694,7 @@ function buildDryRunWrapper(signer, callerPublicKey, method, dto, uniqueKeyPrefi
2577
1694
  callerPublicKey,
2578
1695
  method,
2579
1696
  dto,
2580
- uniqueKey: `${uniqueKeyPrefix}${globalThis.crypto.randomUUID()}`
1697
+ uniqueKey: createUniqueKey(uniqueKeyPrefix)
2581
1698
  });
2582
1699
  }
2583
1700
  /**
@@ -2700,8 +1817,10 @@ function isShadowError(value) {
2700
1817
  }
2701
1818
  /**
2702
1819
  * The same failure, marked as shadow. An SDK error is COPIED (same code, params, status, body,
2703
- * url, ids, cause, phase and stack), never mutated: one error object can be shared by several
2704
- * callers (a registry read many swaps wait on), and only the shadow caller's copy is shadow.
1820
+ * url, ids, cause, phase, read/write, wallet code and stack), never mutated: one error object can
1821
+ * be shared by several callers (a registry read many swaps wait on), and only the shadow caller's
1822
+ * copy is shadow. Everything the deciders read is carried over, so the copy classifies exactly as
1823
+ * the original (a failed READ stays a read, never a "maybe queued" write).
2705
1824
  */
2706
1825
  function asShadowError(error) {
2707
1826
  if (isGSwapError(error)) {
@@ -2717,6 +1836,8 @@ function asShadowError(error) {
2717
1836
  retryAfterMs: error.retryAfterMs,
2718
1837
  cause: error.cause,
2719
1838
  phase: error.phase,
1839
+ write: error.write,
1840
+ walletCode: error.walletCode,
2720
1841
  shadow: true
2721
1842
  });
2722
1843
  if (error.stack !== void 0) copy.stack = error.stack;
@@ -2799,6 +1920,12 @@ var SubmittedTransaction = class {
2799
1920
  * For RequestTrade, calling this method is required to learn the outcome: the queued
2800
1921
  * acknowledgement proves acceptance, not execution.
2801
1922
  *
1923
+ * Every failure is the write's (it carries `uniqueKey`, so `mayHaveBeenQueued` is true): a
1924
+ * re-read that cannot succeed is `CONFIRMATION_FAILED` (a terminal HTTP status) or
1925
+ * `CONFIRMATION_MALFORMED` (an unusable answer); one that may (no answer, a 5xx, 404, 408,
1926
+ * 429) is read again until the deadline, then `CONFIRMATION_TIMEOUT` with the last read error
1927
+ * as `cause`. Confirm the same transaction again; never resubmit the write.
1928
+ *
2802
1929
  * @example
2803
1930
  * ```ts
2804
1931
  * const applied = await submitted.confirm({ initialDelayMs: 3_000 });
@@ -2836,6 +1963,7 @@ var SubmittedTransaction = class {
2836
1963
  });
2837
1964
  const schedule = createConfirmationSchedule(options, 0, abortError);
2838
1965
  await schedule.waitForRead();
1966
+ let lastError;
2839
1967
  while (true) {
2840
1968
  const controller = new AbortController();
2841
1969
  const disarm = armTimeout(controller, this.requestTimeoutMs);
@@ -2846,27 +1974,60 @@ var SubmittedTransaction = class {
2846
1974
  if (result.confirmed) return result.position;
2847
1975
  } catch (error) {
2848
1976
  throwIfAborted(callerSignal, abortError);
2849
- throw error;
1977
+ const terminal = terminalPositionReadFailure(this.uniqueKey, error);
1978
+ if (terminal !== void 0) throw terminal;
1979
+ lastError = error;
2850
1980
  } finally {
2851
1981
  disarm();
2852
1982
  callerSignal === null || callerSignal === void 0 || callerSignal.removeEventListener("abort", forwardAbort);
2853
1983
  }
2854
1984
  if (!await schedule.waitForRead()) break;
2855
1985
  }
2856
- throw GSwapSDKError.confirmationTimeoutError(this.uniqueKey);
1986
+ throw GSwapSDKError.confirmationTimeoutError(this.uniqueKey, lastError);
2857
1987
  }
2858
1988
  };
2859
-
2860
- //#endregion
2861
- //#region src/classes/gateway.ts
2862
- /** Thin client for backend-routed v2 writes: synchronous liquidity writes and queued RequestTrade. */
2863
- var ChainGateway = class {
2864
- requestor;
2865
- walletAddress;
2866
- chainCallTimeoutMs;
2867
- isTransportFailure;
2868
- answerOfRejectionHook;
2869
- /** The request timeout in milliseconds (`Infinity` when no timer is armed). */
1989
+ /** HTTP answers a confirmation reads again (as the apply-results poll does), not terminal ones. */
1990
+ function isRetriedStatus(status) {
1991
+ return status === 404 || status === 408 || status === 429 || status >= 500;
1992
+ }
1993
+ /**
1994
+ * The confirmation error for a position re-read that failed TERMINALLY (an answer that reading
1995
+ * again cannot change), or `undefined` when it is worth reading again.
1996
+ */
1997
+ function terminalPositionReadFailure(uniqueKey, error) {
1998
+ if (!isGSwapError(error)) return void 0;
1999
+ if (error.code === "INVALID_POSITION_RESPONSE" || error.code === "INVALID_CHAIN_RESPONSE") return new GSwapSDKError("CONFIRMATION_MALFORMED", {}, {
2000
+ uniqueKey,
2001
+ url: error.url,
2002
+ body: error.body ?? error.bodyText,
2003
+ cause: error
2004
+ });
2005
+ const status = error.status;
2006
+ if ((error.code === "HTTP_ERROR" || error.code === "UPDATE_REQUIRED") && status !== void 0 && !isRetriedStatus(status)) {
2007
+ const serverMessage = error.params["serverMessage"];
2008
+ return new GSwapSDKError("CONFIRMATION_FAILED", {
2009
+ status,
2010
+ serverMessage: typeof serverMessage === "string" ? serverMessage : ""
2011
+ }, {
2012
+ uniqueKey,
2013
+ status,
2014
+ url: error.url,
2015
+ body: error.body ?? error.bodyText,
2016
+ cause: error
2017
+ });
2018
+ }
2019
+ }
2020
+
2021
+ //#endregion
2022
+ //#region src/classes/gateway.ts
2023
+ /** Thin client for backend-routed v2 writes: synchronous liquidity writes and queued RequestTrade. */
2024
+ var ChainGateway = class {
2025
+ requestor;
2026
+ walletAddress;
2027
+ chainCallTimeoutMs;
2028
+ isTransportFailure;
2029
+ answerOfRejectionHook;
2030
+ /** The request timeout in milliseconds (`Infinity` when no timer is armed). */
2870
2031
  requestTimeoutMs;
2871
2032
  /** The swap backend base URL every request is sent to, without a trailing slash. */
2872
2033
  dexBackendBaseUrl;
@@ -3607,7 +2768,7 @@ var HttpClient = class {
3607
2768
  headers: buildRequestHeaders(this.wire, { json: hasBody }),
3608
2769
  ...hasBody ? { body: JSON.stringify(body) } : {},
3609
2770
  ...(options === null || options === void 0 ? void 0 : options.signal) === void 0 ? {} : { signal: options.signal }
3610
- }, this.timeoutMs);
2771
+ }, this.timeoutMs, { write: false });
3611
2772
  if (!response.ok) throw await GSwapSDKError.fromErrorResponse(url, response);
3612
2773
  const json = await readResponseBody(response);
3613
2774
  debugLog(`Response from ${baseUrl}${basePath}${endpoint}:`, json);
@@ -3643,10 +2804,13 @@ function entryRejected(reason) {
3643
2804
  /**
3644
2805
  * Reads a list answer row by row: a row that cannot be used is skipped and reported, instead of
3645
2806
  * rejecting the whole list because of one bad row. An answer that is not a list at all is still
3646
- * `INVALID_CHAIN_RESPONSE`: nothing in it can be trusted.
2807
+ * `INVALID_CHAIN_RESPONSE` (a read's: `write: false`): nothing in it can be trusted.
3647
2808
  */
3648
2809
  function readEntries(source, data, parse, onInvalidEntry) {
3649
- if (!Array.isArray(data)) throw new GSwapSDKError("INVALID_CHAIN_RESPONSE", { resource: `${source} list` }, { body: data });
2810
+ if (!Array.isArray(data)) throw new GSwapSDKError("INVALID_CHAIN_RESPONSE", { resource: `${source} list` }, {
2811
+ body: data,
2812
+ write: false
2813
+ });
3650
2814
  const rows = [];
3651
2815
  data.forEach((entry, index) => {
3652
2816
  const parsed = parse(entry);
@@ -3679,97 +2843,462 @@ function finiteNumberOf(value) {
3679
2843
  }
3680
2844
 
3681
2845
  //#endregion
3682
- //#region src/utils/class_key.ts
2846
+ //#region src/classes/leaderboard.ts
3683
2847
  /**
3684
- * Token class keys that never throw: the `$`-joined composite key and the non-throwing parsers.
3685
- * This module imports nothing at run time (the key type is erased), so an app shell that reads a
3686
- * stored key pays for a few hundred bytes, never for the error class or the catalog. The parser
3687
- * that THROWS an SDK error on a bad key (`parseTokenClassKey`) lives in `ordering.ts`.
2848
+ * The swap backend's trading-competition reads: seasons (`GET /leaderboard/seasons`) and a
2849
+ * season's rankings (`GET /leaderboard?seasonId=`). Public reads: no signer, no wallet. A row that
2850
+ * is not usable is skipped and reported to `onInvalidEntry`; an answer that is not the expected
2851
+ * list is `INVALID_CHAIN_RESPONSE`.
3688
2852
  */
3689
- /** Return the `$`-joined composite key used by GalaChain object identities. */
3690
- function compositeKeyOf(token) {
3691
- return [
3692
- token.collection,
3693
- token.category,
3694
- token.type,
3695
- token.additionalKey
3696
- ].join("$");
2853
+ var Leaderboard = class {
2854
+ dexBackendBaseUrl;
2855
+ httpClient;
2856
+ options;
2857
+ /**
2858
+ * Creates a leaderboard service. `options.onInvalidEntry` is told about every season or entry
2859
+ * row that was skipped as unusable.
2860
+ *
2861
+ * @example
2862
+ * ```ts
2863
+ * const leaderboard = new Leaderboard('https://dex-backend-prod1.defi.gala.com', httpClient);
2864
+ * ```
2865
+ */
2866
+ constructor(dexBackendBaseUrl, httpClient, options = {}) {
2867
+ this.dexBackendBaseUrl = dexBackendBaseUrl;
2868
+ this.httpClient = httpClient;
2869
+ this.options = options;
2870
+ }
2871
+ /**
2872
+ * Every active season, in the backend's order (newest first).
2873
+ *
2874
+ * @example
2875
+ * ```ts
2876
+ * const seasons = await gSwap.leaderboard.getSeasons();
2877
+ * console.log(seasons.map((season) => season.name));
2878
+ * ```
2879
+ */
2880
+ async getSeasons(options) {
2881
+ const response = await this.httpClient.sendGetRequest(this.dexBackendBaseUrl, "/leaderboard", "/seasons", void 0, signalOption$1(options));
2882
+ return readEntries("seasons", response, parseSeason, (options === null || options === void 0 ? void 0 : options.onInvalidEntry) ?? this.options.onInvalidEntry);
2883
+ }
2884
+ /**
2885
+ * The season running at `options.now` (default: now; `start` and `end` both count as inside),
2886
+ * or `null` between seasons. When seasons overlap, the first in the backend's order.
2887
+ *
2888
+ * @example
2889
+ * ```ts
2890
+ * const season = await gSwap.leaderboard.getCurrentSeason();
2891
+ * console.log(season === null ? 'no season running' : season.name);
2892
+ * ```
2893
+ */
2894
+ async getCurrentSeason(options) {
2895
+ const now = ((options === null || options === void 0 ? void 0 : options.now) ?? /* @__PURE__ */ new Date()).getTime();
2896
+ return (await this.getSeasons(options)).find((season) => season.start.getTime() <= now && now <= season.end.getTime()) ?? null;
2897
+ }
2898
+ /**
2899
+ * A season's rankings (the backend's top 100 wallets). `seasonId` must be an integer of at
2900
+ * least 1 (`INVALID_SEASON_ID` before anything is sent); a season with no rankings, or none at
2901
+ * all, is an empty list.
2902
+ *
2903
+ * @example
2904
+ * ```ts
2905
+ * const { entries } = await gSwap.leaderboard.getLeaderboard(1);
2906
+ * console.log(entries[0]?.wallet, entries[0]?.totalXp);
2907
+ * ```
2908
+ */
2909
+ async getLeaderboard(seasonId, options) {
2910
+ var _asEntryRecord;
2911
+ if (typeof seasonId !== "number" || !Number.isSafeInteger(seasonId) || seasonId < 1) throw new GSwapSDKError("INVALID_SEASON_ID", { value: String(seasonId) });
2912
+ const response = await this.httpClient.sendGetRequest(this.dexBackendBaseUrl, "/leaderboard", "", { seasonId: String(seasonId) }, signalOption$1(options));
2913
+ const rows = (_asEntryRecord = asEntryRecord(response)) === null || _asEntryRecord === void 0 ? void 0 : _asEntryRecord["leaderboard"];
2914
+ if (!Array.isArray(rows)) throw new GSwapSDKError("INVALID_CHAIN_RESPONSE", { resource: "leaderboard" }, {
2915
+ body: response,
2916
+ write: false
2917
+ });
2918
+ return {
2919
+ seasonId,
2920
+ entries: readEntries("leaderboard", rows, parseEntry, (options === null || options === void 0 ? void 0 : options.onInvalidEntry) ?? this.options.onInvalidEntry)
2921
+ };
2922
+ }
2923
+ /**
2924
+ * The rankings of the season running at `options.now` (see
2925
+ * {@link Leaderboard.getCurrentSeason}), or `null` between seasons.
2926
+ *
2927
+ * @example
2928
+ * ```ts
2929
+ * const current = await gSwap.leaderboard.getCurrentLeaderboard();
2930
+ * if (current !== null) console.log(current.seasonId, current.entries.length);
2931
+ * ```
2932
+ */
2933
+ async getCurrentLeaderboard(options) {
2934
+ const season = await this.getCurrentSeason(options);
2935
+ return season === null ? null : this.getLeaderboard(season.id, options);
2936
+ }
2937
+ };
2938
+ function signalOption$1(options) {
2939
+ return (options === null || options === void 0 ? void 0 : options.signal) === void 0 ? void 0 : { signal: options.signal };
2940
+ }
2941
+ function positiveInteger(value) {
2942
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 1 ? value : void 0;
2943
+ }
2944
+ function isoDate(value) {
2945
+ if (typeof value !== "string") return void 0;
2946
+ const date = new Date(value);
2947
+ return Number.isNaN(date.getTime()) ? void 0 : date;
2948
+ }
2949
+ function parseSeason(entry) {
2950
+ const record = asEntryRecord(entry);
2951
+ if (record === void 0) return entryRejected("not an object");
2952
+ const id = positiveInteger(record["id"]);
2953
+ if (id === void 0) return entryRejected("id is not an integer of at least 1");
2954
+ const name = record["name"];
2955
+ if (typeof name !== "string") return entryRejected("name is not a string");
2956
+ const start = isoDate(record["start"]);
2957
+ const end = isoDate(record["end"]);
2958
+ if (start === void 0 || end === void 0) return entryRejected("start or end is not a date");
2959
+ if (start.getTime() > end.getTime()) return entryRejected("start is after end");
2960
+ const rulesId = record["rules_id"];
2961
+ if (typeof rulesId !== "number" || !Number.isSafeInteger(rulesId) || rulesId < 0) return entryRejected("rules_id is not an integer");
2962
+ return entryOk({
2963
+ id,
2964
+ name,
2965
+ start,
2966
+ end,
2967
+ rulesId
2968
+ });
3697
2969
  }
3698
- /**
3699
- * Splits `collection<d>category<d>type<d>additionalKey` from the right, or `undefined` when there
3700
- * are fewer than four segments or any of the four is empty. The one place the split is written;
3701
- * the strict `parseTokenClassKey` calls it too.
3702
- *
3703
- * The LAST three segments are always `category`, `type` and `additionalKey`; everything before
3704
- * them is the `collection`. Parsing from the right is required because real collections contain
3705
- * the `$` delimiter themselves (for example `$GMUSIC`), so a plain left-to-right `split('$')`
3706
- * shifts every field.
3707
- */
3708
- function splitClassKey(key, delimiter) {
3709
- const segments = key.split(delimiter);
3710
- const tail = segments.slice(-3);
3711
- const collection = segments.slice(0, -3).join(delimiter);
3712
- if (segments.length < 4 || collection.length === 0 || tail.some((part) => part.length === 0)) return;
3713
- const [category, type, additionalKey] = tail;
2970
+ /** A non-negative decimal, as the backend's DECIMAL columns are serialized (or a finite number). */
2971
+ const DECIMAL = /^\d+(?:\.\d+)?$/u;
2972
+ function decimalOf(value) {
2973
+ const text = typeof value === "number" ? String(value) : value;
2974
+ return typeof text === "string" && DECIMAL.test(text) ? text : void 0;
2975
+ }
2976
+ function parseTitle(value) {
2977
+ const record = asEntryRecord(value);
2978
+ if (record === void 0) return void 0;
2979
+ const { name, type, order } = record;
2980
+ if (typeof name !== "string" || typeof type !== "string") return void 0;
2981
+ if (typeof order !== "number" || !Number.isFinite(order)) return void 0;
3714
2982
  return {
3715
- collection,
3716
- category,
2983
+ name,
3717
2984
  type,
3718
- additionalKey
2985
+ order
3719
2986
  };
3720
2987
  }
2988
+ function parseEntry(entry) {
2989
+ const record = asEntryRecord(entry);
2990
+ if (record === void 0) return entryRejected("not an object");
2991
+ const wallet = record["wallet"];
2992
+ if (typeof wallet !== "string" || wallet === "") return entryRejected("wallet is not a string");
2993
+ const rank = positiveInteger(record["rank"]);
2994
+ if (rank === void 0) return entryRejected("rank is not an integer of at least 1");
2995
+ const rawTotal = record["total_xp"];
2996
+ const totalXp = rawTotal === void 0 || rawTotal === null ? null : decimalOf(rawTotal);
2997
+ if (totalXp === void 0) return entryRejected("total_xp is not a decimal");
2998
+ const distributionPercent = decimalOf(record["distribution_percent"]);
2999
+ const liquidityXp = decimalOf(record["liquidity_xp"]);
3000
+ const tradingXp = decimalOf(record["trading_xp"]);
3001
+ if (distributionPercent === void 0 || liquidityXp === void 0 || tradingXp === void 0) return entryRejected("distribution_percent, liquidity_xp or trading_xp is not a decimal");
3002
+ const rawTitles = record["mastery_titles"];
3003
+ if (!Array.isArray(rawTitles)) return entryRejected("mastery_titles is not a list");
3004
+ const masteryTitles = [];
3005
+ for (const raw of rawTitles) {
3006
+ const title = parseTitle(raw);
3007
+ if (title === void 0) return entryRejected("a mastery title is not { name, type, order }");
3008
+ masteryTitles.push(title);
3009
+ }
3010
+ return entryOk({
3011
+ wallet,
3012
+ rank,
3013
+ totalXp,
3014
+ distributionPercent,
3015
+ liquidityXp,
3016
+ tradingXp,
3017
+ masteryTitles
3018
+ });
3019
+ }
3020
+
3021
+ //#endregion
3022
+ //#region src/types/market.ts
3023
+ /**
3024
+ * The swap backend's market data and trading-competition reads: what `GSwap.market` and
3025
+ * `GSwap.leaderboard` return. Values are the backend's, unconverted: USD prices as numbers, times
3026
+ * as unix seconds or `Date`s, XP as decimal strings.
3027
+ */
3028
+ /** The windows `GET /market/price-series` serves (the backend refuses any other with a 400). */
3029
+ const PRICE_SERIES_TIMEFRAMES = [
3030
+ "1D",
3031
+ "1W",
3032
+ "1M",
3033
+ "1Y",
3034
+ "ALL"
3035
+ ];
3036
+ /** The most tokens one `GET /market/price` request may name (the backend's limit). */
3037
+ const MARKET_PRICE_BATCH_LIMIT = 500;
3038
+
3039
+ //#endregion
3040
+ //#region src/classes/market.ts
3721
3041
  /**
3722
- * The non-throwing `parseTokenClassKey`: the class key, or `undefined` when `value` is not
3723
- * one. A string is split exactly as `parseTokenClassKey` splits it (`|` form when it contains a
3724
- * `|`, else `$`, from the right, all four parts non-empty). Unlike `parseTokenClassKey`, an
3725
- * object is VALIDATED, not just copied: all four fields must be non-empty strings. Anything else
3726
- * (`undefined`, `null`, a number, an array) is `undefined`, so it is safe on untrusted input (a
3727
- * stored token, a URL parameter, a wallet row) without a `try`/`catch`.
3728
- *
3729
- * Use {@link tryParseCompositeKey} instead when only the `$` form is valid input.
3042
+ * The ids `GET /market/price` and `GET /market/price-series` accept (the backend's own allowlist):
3043
+ * a symbol, or a class key in the `$` or `|` form. A comma would split the price request.
3044
+ */
3045
+ const MARKET_TOKEN_PATTERN = /^[A-Za-z0-9._$+/:|-]{1,120}$/u;
3046
+ /**
3047
+ * Market data from the swap backend: USD prices (`GET /market/price`), price series
3048
+ * (`GET /market/price-series`) and the platform volume totals (`GET /v2/trade/volume`). Public
3049
+ * reads: no signer, no wallet.
3730
3050
  *
3731
- * @example
3732
- * ```ts
3733
- * const key = tryParseTokenClassKey(row.compositeKey);
3734
- * if (key === undefined) return failClosed(); // no class is known: never invent one from a symbol
3735
- * ```
3051
+ * A token is named by a symbol (`GALA`) or a class key (`GALA$Unit$none$none`,
3052
+ * `GALA|Unit|none|none` or the object). The backend answers each token under the exact id it was
3053
+ * asked for, so the answer is read back under that id: a token the backend has no entry for is
3054
+ * `undefined`, never another token's price.
3736
3055
  */
3737
- function tryParseTokenClassKey(value) {
3738
- if (typeof value === "string") return splitClassKey(value, value.includes("|") ? "|" : "$");
3739
- if (typeof value !== "object" || value === null) return void 0;
3740
- try {
3741
- const { collection, category, type, additionalKey } = value;
3742
- if (typeof collection !== "string" || typeof category !== "string" || typeof type !== "string" || typeof additionalKey !== "string" || collection === "" || category === "" || type === "" || additionalKey === "") return;
3056
+ var Market = class {
3057
+ dexBackendBaseUrl;
3058
+ httpClient;
3059
+ options;
3060
+ /**
3061
+ * Creates a market service. `options.onInvalidEntry` is told about every price or price point
3062
+ * that was skipped as unusable.
3063
+ *
3064
+ * @example
3065
+ * ```ts
3066
+ * const market = new Market('https://dex-backend-prod1.defi.gala.com', httpClient);
3067
+ * ```
3068
+ */
3069
+ constructor(dexBackendBaseUrl, httpClient, options = {}) {
3070
+ this.dexBackendBaseUrl = dexBackendBaseUrl;
3071
+ this.httpClient = httpClient;
3072
+ this.options = options;
3073
+ }
3074
+ /**
3075
+ * USD prices of 1 to 500 tokens, in the order asked (a token named twice is asked for once).
3076
+ * The ids go in as many requests as their length needs (see {@link MARKET_PRICE_QUERY_BYTES}),
3077
+ * one after another; any request failing fails the call. A token the backend did not answer
3078
+ * for is `undefined`; one it answered for unusably is `undefined` too, and reported to
3079
+ * `onInvalidEntry`. An answer that is not an object is `INVALID_CHAIN_RESPONSE`.
3080
+ *
3081
+ * @example
3082
+ * ```ts
3083
+ * const [gala, gusdc] = await gSwap.market.getPrices(['GALA$Unit$none$none', 'GUSDC$Unit$none$none']);
3084
+ * console.log(gala?.price, gusdc?.price);
3085
+ * ```
3086
+ */
3087
+ async getPrices(tokens, options) {
3088
+ if (!Array.isArray(tokens) || tokens.length < 1 || tokens.length > 500) throw new GSwapSDKError("INVALID_TOKEN_BATCH", {
3089
+ method: "getPrices",
3090
+ count: Array.isArray(tokens) ? tokens.length : 0,
3091
+ max: 500
3092
+ });
3093
+ const ids = tokens.map(marketTokenOf);
3094
+ const answered = /* @__PURE__ */ new Map();
3095
+ for (const batch of priceBatches([...new Set(ids)])) {
3096
+ const response = await this.httpClient.sendGetRequest(this.dexBackendBaseUrl, "/market", "/price", { assetIds: batch.join(",") }, signalOption(options));
3097
+ const answer = asEntryRecord(response);
3098
+ if (answer === void 0) throw new GSwapSDKError("INVALID_CHAIN_RESPONSE", { resource: "market prices" }, {
3099
+ body: response,
3100
+ write: false
3101
+ });
3102
+ for (const id of batch) if (Object.hasOwn(answer, id)) answered.set(id, answer[id]);
3103
+ }
3104
+ const onInvalidEntry = (options === null || options === void 0 ? void 0 : options.onInvalidEntry) ?? this.options.onInvalidEntry;
3105
+ return ids.map((id, index) => {
3106
+ if (!answered.has(id)) return void 0;
3107
+ const entry = answered.get(id);
3108
+ const parsed = parseMarketPrice(id, entry);
3109
+ if (parsed.ok) return parsed.value;
3110
+ try {
3111
+ onInvalidEntry === null || onInvalidEntry === void 0 || onInvalidEntry({
3112
+ source: "prices",
3113
+ index,
3114
+ entry,
3115
+ reason: parsed.reason
3116
+ });
3117
+ } catch {}
3118
+ });
3119
+ }
3120
+ /**
3121
+ * The USD price of one token, or `undefined` when the backend has no entry for it (see
3122
+ * {@link Market.getPrices}).
3123
+ *
3124
+ * @example
3125
+ * ```ts
3126
+ * const gala = await gSwap.market.getPrice('GALA$Unit$none$none');
3127
+ * if (gala?.price != null) console.log(`GALA: $${gala.price}`);
3128
+ * ```
3129
+ */
3130
+ async getPrice(token, options) {
3131
+ const [price] = await this.getPrices([token], options);
3132
+ return price;
3133
+ }
3134
+ /**
3135
+ * A token's USD price line for one window (1D, 1W, 1M, 1Y or ALL; any other is
3136
+ * `INVALID_TIMEFRAME` before anything is sent). The whole series in one answer, oldest first; a
3137
+ * token the backend has no prices for is an empty series. An unusable point is skipped and
3138
+ * reported to `onInvalidEntry`; an answer that is not a series is `INVALID_CHAIN_RESPONSE`.
3139
+ *
3140
+ * @example
3141
+ * ```ts
3142
+ * const series = await gSwap.market.getPriceSeries('GALA$Unit$none$none', '1W');
3143
+ * console.log(series.prices.length, series.currentPrice);
3144
+ * ```
3145
+ */
3146
+ async getPriceSeries(token, timeframe, options) {
3147
+ if (!PRICE_SERIES_TIMEFRAMES.includes(timeframe)) throw new GSwapSDKError("INVALID_TIMEFRAME", {
3148
+ value: String(timeframe),
3149
+ allowed: PRICE_SERIES_TIMEFRAMES.join(", ")
3150
+ });
3151
+ const id = marketTokenOf(token);
3152
+ const response = await this.httpClient.sendGetRequest(this.dexBackendBaseUrl, "/market", "/price-series", {
3153
+ token: id,
3154
+ timeframe
3155
+ }, signalOption(options));
3156
+ const answer = asEntryRecord(response);
3157
+ const currentPrice = answer === void 0 ? void 0 : nullableFinite(answer["currentPrice"]);
3158
+ const change24h = answer === void 0 ? void 0 : nullableFinite(answer["change24h"]);
3159
+ if (answer === void 0 || !Array.isArray(answer["prices"]) || currentPrice === void 0 || change24h === void 0) throw new GSwapSDKError("INVALID_CHAIN_RESPONSE", { resource: "price series" }, {
3160
+ body: response,
3161
+ write: false
3162
+ });
3743
3163
  return {
3744
- collection,
3745
- category,
3746
- type,
3747
- additionalKey
3164
+ token: id,
3165
+ timeframe,
3166
+ prices: readEntries("priceSeries", answer["prices"], parsePricePoint, (options === null || options === void 0 ? void 0 : options.onInvalidEntry) ?? this.options.onInvalidEntry),
3167
+ currentPrice,
3168
+ change24h
3748
3169
  };
3749
- } catch {
3750
- return;
3751
3170
  }
3171
+ /**
3172
+ * TVL, volume and trade totals across every current-contract pool. Every field must be a finite
3173
+ * number, or the answer is `INVALID_CHAIN_RESPONSE`.
3174
+ *
3175
+ * @example
3176
+ * ```ts
3177
+ * const volume = await gSwap.market.getVolumeSummary();
3178
+ * console.log(`24h: $${volume.volume1d.toFixed(0)} (${(volume.volume1dDelta * 100).toFixed(1)} %)`);
3179
+ * ```
3180
+ */
3181
+ async getVolumeSummary(options) {
3182
+ var _asEntryRecord;
3183
+ const response = await this.httpClient.sendGetRequest(this.dexBackendBaseUrl, "/v2/trade", "/volume", void 0, signalOption(options));
3184
+ const summary = parseVolumeSummary(asEntryRecord((_asEntryRecord = asEntryRecord(response)) === null || _asEntryRecord === void 0 ? void 0 : _asEntryRecord["data"]));
3185
+ if (summary === void 0) throw new GSwapSDKError("INVALID_CHAIN_RESPONSE", { resource: "volume summary" }, {
3186
+ body: response,
3187
+ write: false
3188
+ });
3189
+ return summary;
3190
+ }
3191
+ };
3192
+ /**
3193
+ * The most query bytes one `GET /market/price` carries. The backend refuses a request line over
3194
+ * 16 KB (Node's default header limit: 300 launchpad keys, 17.7 KB encoded, answer HTTP 431,
3195
+ * recorded on prod 2026-10-13), so a long list is split well under it (about 135 launchpad keys
3196
+ * per request, 270 short ones).
3197
+ */
3198
+ const MARKET_PRICE_QUERY_BYTES = 8e3;
3199
+ /**
3200
+ * The ids in request-sized batches: at most {@link MARKET_PRICE_BATCH_LIMIT} ids and
3201
+ * {@link MARKET_PRICE_QUERY_BYTES} of encoded `assetIds` each (the standard encoding's size, the
3202
+ * larger of the two, with a `%2C` per id).
3203
+ */
3204
+ function priceBatches(ids) {
3205
+ const batches = [];
3206
+ let batch = [];
3207
+ let bytes = 0;
3208
+ for (const id of ids) {
3209
+ const size = encodeURIComponent(id).length + 3;
3210
+ if (batch.length > 0 && (bytes + size > 8e3 || batch.length === 500)) {
3211
+ batches.push(batch);
3212
+ batch = [];
3213
+ bytes = 0;
3214
+ }
3215
+ batch.push(id);
3216
+ bytes += size;
3217
+ }
3218
+ if (batch.length > 0) batches.push(batch);
3219
+ return batches;
3752
3220
  }
3753
3221
  /**
3754
- * Parses a `$`-joined composite key (`Collection$Category$Type$AdditionalKey`, what
3755
- * {@link compositeKeyOf} writes) and nothing else, or `undefined`. It is {@link tryParseTokenClassKey}
3756
- * for a string that may ONLY be in the `$` form: a key that contains `|` is NOT read as the pipe
3757
- * form here (it stays part of a segment), and only a string is accepted. The split is from the
3758
- * right, so a collection that itself contains `$` (production's `$GMUSIC`, stage's
3759
- * `$GMUSIC_TEST1`) stays whole; `'GALA'`, `'GALA$Unit$none'`, `'ETIME$$$'`, `'GALA$Unit$none$'` and
3760
- * `'$$$$'` are `undefined`.
3761
- *
3762
- * It replaces an app's own `parseCompositeKey(key): Components | null` (`?? null`), which had
3763
- * exactly these semantics.
3764
- *
3765
- * @example
3766
- * ```ts
3767
- * tryParseCompositeKey('$GMUSIC_TEST1$Unit$none$none')?.collection; // '$GMUSIC_TEST1'
3768
- * tryParseCompositeKey('GALA$Unit$none'); // undefined
3769
- * ```
3222
+ * The id a market read sends for a token: a string as given (a symbol or a class key in either
3223
+ * form), an object key in its `$` form. Anything the backend's allowlist refuses is refused here.
3770
3224
  */
3771
- function tryParseCompositeKey(value) {
3772
- return typeof value === "string" ? splitClassKey(value, "$") : void 0;
3225
+ function marketTokenOf(token) {
3226
+ if (typeof token !== "string") {
3227
+ if (!isTokenClassKey(token)) throw new GSwapSDKError("INVALID_TOKEN_CLASS_KEY", { tokenClassKey: describeValue(token) });
3228
+ return checkedMarketToken(compositeKeyOf(token));
3229
+ }
3230
+ return checkedMarketToken(token);
3231
+ }
3232
+ function checkedMarketToken(id) {
3233
+ if (!MARKET_TOKEN_PATTERN.test(id)) throw new GSwapSDKError("INVALID_MARKET_TOKEN", { token: id });
3234
+ return id;
3235
+ }
3236
+ function describeValue(value) {
3237
+ try {
3238
+ return JSON.stringify(value) ?? String(value);
3239
+ } catch {
3240
+ return Object.prototype.toString.call(value);
3241
+ }
3242
+ }
3243
+ function signalOption(options) {
3244
+ return (options === null || options === void 0 ? void 0 : options.signal) === void 0 ? void 0 : { signal: options.signal };
3245
+ }
3246
+ /** A finite number, `null` for `null`, `undefined` for anything else (an unusable value). */
3247
+ function nullableFinite(value) {
3248
+ if (value === null) return null;
3249
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
3250
+ }
3251
+ function finite(value) {
3252
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
3253
+ }
3254
+ function parseMarketPrice(token, entry) {
3255
+ const record = asEntryRecord(entry);
3256
+ if (record === void 0) return entryRejected("not an object");
3257
+ const price = nullableFinite(record["price"]);
3258
+ if (price === void 0 || price !== null && price < 0) return entryRejected("price is not a non-negative number or null");
3259
+ const change24h = nullableFinite(record["change24h"]);
3260
+ if (change24h === void 0) return entryRejected("change24h is not a number or null");
3261
+ const listed = record["listed"];
3262
+ if (typeof listed !== "boolean") return entryRejected("listed is not a boolean");
3263
+ return entryOk({
3264
+ token,
3265
+ price,
3266
+ change24h,
3267
+ listed
3268
+ });
3269
+ }
3270
+ function parsePricePoint(entry) {
3271
+ const record = asEntryRecord(entry);
3272
+ const time = finite(record === null || record === void 0 ? void 0 : record["time"]);
3273
+ const price = finite(record === null || record === void 0 ? void 0 : record["price"]);
3274
+ if (time === void 0 || !Number.isInteger(time) || time < 0) return entryRejected("time is not unix seconds");
3275
+ if (price === void 0 || price < 0) return entryRejected("price is not a non-negative number");
3276
+ return entryOk({
3277
+ time,
3278
+ price
3279
+ });
3280
+ }
3281
+ const VOLUME_FIELDS = [
3282
+ "tvl",
3283
+ "volume1d",
3284
+ "volume7d",
3285
+ "volume30d",
3286
+ "trades1d",
3287
+ "volume1dDelta",
3288
+ "volume7dDelta",
3289
+ "volume30dDelta"
3290
+ ];
3291
+ function parseVolumeSummary(data) {
3292
+ if (data === void 0) return void 0;
3293
+ const summary = {};
3294
+ for (const field of VOLUME_FIELDS) {
3295
+ const value = finite(data[field]);
3296
+ if (value === void 0) return void 0;
3297
+ summary[field] = value;
3298
+ }
3299
+ const trades = summary.trades1d;
3300
+ if (trades === void 0 || !Number.isInteger(trades) || trades < 0) return void 0;
3301
+ return summary;
3773
3302
  }
3774
3303
 
3775
3304
  //#endregion
@@ -3832,6 +3361,25 @@ function parseTokenClassKey(tokenClassKey) {
3832
3361
  return parsed;
3833
3362
  }
3834
3363
 
3364
+ //#endregion
3365
+ //#region src/utils/resolve_pre_send.ts
3366
+ /**
3367
+ * Runs a token-resolution step (a resolver's `resolve` / `orderPair`, which may read the registry
3368
+ * through the app's HTTP stack) on the "nothing was sent" boundary, like core's `signPreSend` does
3369
+ * for the signer. A failure that is not an SDK error is `TOKEN_RESOLUTION_FAILED` (`token` names
3370
+ * what was being resolved, the original is `cause`); an SDK error of any product (every one is a
3371
+ * `GalaSDKError`, core's transport errors included) passes through.
3372
+ *
3373
+ * @throws GSwapSDKError TOKEN_RESOLUTION_FAILED, or the SDK error the step itself threw.
3374
+ */
3375
+ async function resolvePreSend(token, run) {
3376
+ try {
3377
+ return await run();
3378
+ } catch (cause) {
3379
+ throw isGalaError(cause) ? cause : new GSwapSDKError("TOKEN_RESOLUTION_FAILED", { token }, { cause });
3380
+ }
3381
+ }
3382
+
3835
3383
  //#endregion
3836
3384
  //#region src/classes/symbols.ts
3837
3385
  const SYMBOL_CACHE_TTL_MS = 6e4;
@@ -4078,6 +3626,70 @@ var Pools = class {
4078
3626
  return readEntries("pools", response.data, parsePoolListInfo, (options === null || options === void 0 ? void 0 : options.onInvalidEntry) ?? this.options.onInvalidEntry);
4079
3627
  }
4080
3628
  /**
3629
+ * Watches for pools created from now on, by polling {@link Pools.getPools} every `intervalMs`
3630
+ * (the polling alternative to the realtime stream's `GSwap.stream.subscribePoolCreated`, for a
3631
+ * client that cannot hold a WebSocket open). The first answer is the baseline: a pool that
3632
+ * existed then is never reported. Each later pool is judged once, when it first appears, against
3633
+ * `minTvl` and `tokens`. A poll never overlaps the previous one (a tick that finds one still in
3634
+ * flight is skipped), and nothing is reported after the returned function stops the watcher.
3635
+ *
3636
+ * @example
3637
+ * ```ts
3638
+ * const stop = gSwap.pools.watchNewPools((pool) => console.log('new pool', pool.poolRef), {
3639
+ * intervalMs: 60_000,
3640
+ * tokens: ['GALA'],
3641
+ * });
3642
+ * // later
3643
+ * stop();
3644
+ * ```
3645
+ */
3646
+ watchNewPools(onPool, options = {}) {
3647
+ const intervalMs = options.intervalMs ?? 3e4;
3648
+ if (typeof intervalMs !== "number" || !Number.isFinite(intervalMs) || intervalMs < 1) throw new GSwapSDKError("INVALID_POLL_INTERVAL", { value: String(intervalMs) });
3649
+ let known;
3650
+ let stopped = false;
3651
+ let polling = false;
3652
+ const report = (error) => {
3653
+ try {
3654
+ var _options$onError;
3655
+ (_options$onError = options.onError) === null || _options$onError === void 0 || _options$onError.call(options, error);
3656
+ } catch {}
3657
+ };
3658
+ const wanted = (pool) => (options.minTvl === void 0 || (pool.tvlUsd ?? 0) >= options.minTvl) && (options.tokens === void 0 || options.tokens.includes(pool.token0) || options.tokens.includes(pool.token1));
3659
+ const poll = async () => {
3660
+ if (stopped || polling) return;
3661
+ polling = true;
3662
+ try {
3663
+ const pools = await this.getPools();
3664
+ if (known === void 0) {
3665
+ known = new Set(pools.map((pool) => pool.poolRef));
3666
+ return;
3667
+ }
3668
+ for (const pool of pools) {
3669
+ if (stopped) return;
3670
+ if (known.has(pool.poolRef)) continue;
3671
+ known.add(pool.poolRef);
3672
+ if (!wanted(pool)) continue;
3673
+ try {
3674
+ onPool(pool);
3675
+ } catch (error) {
3676
+ report(error);
3677
+ }
3678
+ }
3679
+ } catch (error) {
3680
+ if (!stopped) report(error);
3681
+ } finally {
3682
+ polling = false;
3683
+ }
3684
+ };
3685
+ poll();
3686
+ const timer = setInterval(() => void poll(), intervalMs);
3687
+ return () => {
3688
+ stopped = true;
3689
+ clearInterval(timer);
3690
+ };
3691
+ }
3692
+ /**
4081
3693
  * Fetches one pool from the backend, preserving whether the request order was flipped.
4082
3694
  *
4083
3695
  * @example
@@ -4139,8 +3751,11 @@ var Pools = class {
4139
3751
  try {
4140
3752
  return (await this.http.sendGetRequest(this.gateway.dexBackendBaseUrl, "/v2/trade", endpoint, params)).data;
4141
3753
  } catch (error) {
4142
- if (error instanceof GSwapSDKError) throw error;
4143
- throw new GSwapSDKError("REQUEST_FAILED", { operation: "pool" }, { cause: error });
3754
+ if (isGSwapError(error)) throw error;
3755
+ throw new GSwapSDKError("REQUEST_FAILED", { operation: "pool" }, {
3756
+ cause: error,
3757
+ write: false
3758
+ });
4144
3759
  }
4145
3760
  }
4146
3761
  };
@@ -4363,49 +3978,6 @@ function frameSlot0(slot0) {
4363
3978
  };
4364
3979
  }
4365
3980
 
4366
- //#endregion
4367
- //#region src/utils/unique_key.ts
4368
- /**
4369
- * The `uniqueKey` prefix frontend_v2 and wallet_extension have always sent
4370
- * (`galaswap-operation-<uuid>`). defi-backend does not depend on it: the chain-write verifier
4371
- * only requires a non-empty key of at most 256 characters, and apply-results accepts
4372
- * `[A-Za-z0-9._:-]{1,128}`; the only code that mints a prefixed key itself is the server-side v1
4373
- * trade builder, which nothing reads back by prefix.
4374
- */
4375
- const DEFAULT_UNIQUE_KEY_PREFIX = "galaswap-operation-";
4376
- /** A canonical UUID is 36 characters; the apply-results key limit is 128. */
4377
- const MAX_UNIQUE_KEY_PREFIX_LENGTH = 92;
4378
- const UNIQUE_KEY_PREFIX_PATTERN = /^[A-Za-z0-9._:-]*$/u;
4379
- /**
4380
- * Validates a caller-supplied `uniqueKey` prefix. The whole key (`<prefix><uuid>`) must stay a
4381
- * valid apply-results key, otherwise `confirm()` could never read the outcome of the write.
4382
- *
4383
- * @throws GSwapSDKError `INVALID_UNIQUE_KEY_PREFIX` when the prefix has characters outside
4384
- * `A-Za-z0-9._:-` or is longer than 92 characters.
4385
- */
4386
- function resolveUniqueKeyPrefix(prefix) {
4387
- if (prefix === void 0) return DEFAULT_UNIQUE_KEY_PREFIX;
4388
- if (!UNIQUE_KEY_PREFIX_PATTERN.test(prefix) || prefix.length > MAX_UNIQUE_KEY_PREFIX_LENGTH) throw new GSwapSDKError("INVALID_UNIQUE_KEY_PREFIX", {
4389
- uniqueKeyPrefix: prefix,
4390
- maxLength: MAX_UNIQUE_KEY_PREFIX_LENGTH
4391
- });
4392
- return prefix;
4393
- }
4394
- /**
4395
- * Validates a caller-supplied write `uniqueKey`: 1 to 128 characters of `A-Za-z0-9._:-`, the
4396
- * shape the apply-results route accepts, so the write's outcome can always be read back with
4397
- * `confirm()`. A key is one attempt's identity: reuse it only to retry that same attempt.
4398
- *
4399
- * @throws GSwapSDKError `INVALID_UNIQUE_KEY` otherwise.
4400
- */
4401
- function assertUniqueKey(uniqueKey) {
4402
- if (!isValidUniqueKey(uniqueKey)) throw new GSwapSDKError("INVALID_UNIQUE_KEY", { uniqueKey: String(uniqueKey) });
4403
- }
4404
- /** A fresh per-attempt key: `<prefix><random UUID>`. */
4405
- function createUniqueKey(prefix) {
4406
- return `${prefix}${globalThis.crypto.randomUUID()}`;
4407
- }
4408
-
4409
3981
  //#endregion
4410
3982
  //#region src/utils/dto_builders.ts
4411
3983
  const decimalString = (amount) => new BigNumber(amount).toFixed();
@@ -4819,7 +4391,10 @@ var Positions = class {
4819
4391
  if ((signal === null || signal === void 0 ? void 0 : signal.aborted) === true) throw error;
4820
4392
  return {
4821
4393
  kind: "unknown",
4822
- error: isGSwapError(error) ? error : new GSwapSDKError("REQUEST_FAILED", { operation: "position" }, { cause: error })
4394
+ error: isGSwapError(error) ? error : new GSwapSDKError("REQUEST_FAILED", { operation: "position" }, {
4395
+ cause: error,
4396
+ write: false
4397
+ })
4823
4398
  };
4824
4399
  }
4825
4400
  }
@@ -5286,7 +4861,7 @@ var Positions = class {
5286
4861
  }
5287
4862
  }
5288
4863
  isNotFound(error) {
5289
- return error instanceof GSwapSDKError && error.status === 404;
4864
+ return isGSwapError(error) && error.status === 404;
5290
4865
  }
5291
4866
  canonicalTicks(tickLower, tickUpper, flipped) {
5292
4867
  return flipped ? {
@@ -5343,7 +4918,7 @@ var Positions = class {
5343
4918
  })
5344
4919
  };
5345
4920
  } catch (error) {
5346
- if (!(error instanceof GSwapSDKError) || error.code !== "UNKNOWN_TOKEN") throw error;
4921
+ if (!isGSwapError(error, "UNKNOWN_TOKEN")) throw error;
5347
4922
  let classKey;
5348
4923
  try {
5349
4924
  classKey = parseTokenClassKey(ref);
@@ -5428,7 +5003,10 @@ function parsePosition(value) {
5428
5003
  function readPositionBody(data) {
5429
5004
  const parsed = parsePosition(data);
5430
5005
  if (parsed.ok) return parsed.value;
5431
- throw new GSwapSDKError("INVALID_POSITION_RESPONSE", { reason: parsed.reason }, { body: data });
5006
+ throw new GSwapSDKError("INVALID_POSITION_RESPONSE", { reason: parsed.reason }, {
5007
+ body: data,
5008
+ write: false
5009
+ });
5432
5010
  }
5433
5011
 
5434
5012
  //#endregion
@@ -5496,7 +5074,10 @@ var Quoting = class {
5496
5074
  }
5497
5075
  };
5498
5076
  function mapQuote(value) {
5499
- if (!isQuoteWire(value)) throw new GSwapSDKError("INVALID_CHAIN_RESPONSE", { resource: "quote" }, { body: value });
5077
+ if (!isQuoteWire(value)) throw new GSwapSDKError("INVALID_CHAIN_RESPONSE", { resource: "quote" }, {
5078
+ body: value,
5079
+ write: false
5080
+ });
5500
5081
  const wire = value;
5501
5082
  const currentPrice = wire.currentPrice !== void 0 ? new BigNumber(wire.currentPrice) : orientPrice(wire.currentSqrtPrice, wire.tokenInIsToken0);
5502
5083
  const newPrice = wire.newPrice !== void 0 ? new BigNumber(wire.newPrice) : orientPrice(wire.newSqrtPrice, wire.tokenInIsToken0);
@@ -5581,7 +5162,10 @@ function mapQuoteError(error, tokenIn, tokenOut, fee, queried) {
5581
5162
  if (error.status === 400 && /insufficient liquidity/iu.test(message)) return GSwapSDKError.insufficientLiquidityError(tokenIn, tokenOut, fee, error);
5582
5163
  return error;
5583
5164
  }
5584
- return new GSwapSDKError("REQUEST_FAILED", { operation: "quote" }, { cause: error });
5165
+ return new GSwapSDKError("REQUEST_FAILED", { operation: "quote" }, {
5166
+ cause: error,
5167
+ write: false
5168
+ });
5585
5169
  }
5586
5170
  function validateOptionalFee(fee) {
5587
5171
  if (fee !== void 0 && !ALL_FEE_TIERS.some((tier) => Number(tier) === Number(fee))) throw new GSwapSDKError("INVALID_FEE", {
@@ -5883,7 +5467,8 @@ var Swaps = class {
5883
5467
  /**
5884
5468
  * Main entry point for the current GalaChainDex SDK: one client bundling the services that read
5885
5469
  * and write through the swap backend (`quoting`, `swaps`, `pools`, `positions`, `assets`,
5886
- * `symbols`, `fees`, `stream`). See {@link GSwapOptions} for configuration.
5470
+ * `market`, `leaderboard`, `symbols`, `fees`, `stream`). See {@link GSwapOptions} for
5471
+ * configuration.
5887
5472
  */
5888
5473
  var GSwap = class {
5889
5474
  /** The swap backend base URL every request goes to, without a trailing slash. */
@@ -5908,6 +5493,10 @@ var GSwap = class {
5908
5493
  swaps;
5909
5494
  /** User token balances. */
5910
5495
  assets;
5496
+ /** USD prices, price series and the platform volume totals. */
5497
+ market;
5498
+ /** Trading-competition seasons and rankings. */
5499
+ leaderboard;
5911
5500
  /** Network-fee quotes (DryRun) signed with this client's signer. */
5912
5501
  fees;
5913
5502
  /** The realtime stream endpoint {@link GSwap.stream} connects to. */
@@ -5950,6 +5539,8 @@ var GSwap = class {
5950
5539
  this.positions = new Positions(this.gateway, resolver, this.signer, this.walletAddress, keyOptions);
5951
5540
  this.swaps = new Swaps(this.gateway, resolver, this.signer, this.walletAddress, keyOptions);
5952
5541
  this.assets = new Assets(this.dexBackendBaseUrl, httpClient);
5542
+ this.market = new Market(this.dexBackendBaseUrl, httpClient, lenient);
5543
+ this.leaderboard = new Leaderboard(this.dexBackendBaseUrl, httpClient, lenient);
5953
5544
  this.fees = new DexFees(this.gateway, this.signer);
5954
5545
  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`);
5955
5546
  this.webSocketImpl = options === null || options === void 0 ? void 0 : options.webSocketImpl;
@@ -6066,6 +5657,13 @@ const BY_CODE = {
6066
5657
  GATEWAY_SYNC_REQUEST_TRADE: UNKNOWN,
6067
5658
  MISSING_UNIQUE_KEY: UNKNOWN,
6068
5659
  UNIQUE_KEY_MISMATCH: UNKNOWN,
5660
+ GATEWAY_WRITE_OUTCOME_UNKNOWN: UNKNOWN,
5661
+ GATEWAY_RESPONSE_INVALID: UNKNOWN,
5662
+ BRIDGE_RESPONSE_INVALID: UNKNOWN,
5663
+ BRIDGE_WRITE_OUTCOME_UNKNOWN: UNKNOWN,
5664
+ BRIDGE_OUT_OUTCOME_UNKNOWN: UNKNOWN,
5665
+ BRIDGE_DEPOSIT_OUTCOME_UNKNOWN: UNKNOWN,
5666
+ BRIDGE_STATUS_TIMEOUT: UNKNOWN,
6069
5667
  SHADOW_NOT_QUEUED: NOT_QUEUED_FINAL,
6070
5668
  METHOD_NOT_ALLOWED: NOT_QUEUED,
6071
5669
  SIGNATURE_INVALID: NOT_QUEUED,
@@ -6078,6 +5676,14 @@ const BY_CODE = {
6078
5676
  CHAIN_REJECTED: REJECTED,
6079
5677
  INVALID_CHAIN_RESPONSE: TRANSIENT,
6080
5678
  INVALID_POSITION_RESPONSE: TRANSIENT,
5679
+ GATEWAY_READ_REJECTED: TRANSIENT,
5680
+ GATEWAY_READ_RESPONSE_INVALID: TRANSIENT,
5681
+ BRIDGE_READ_RESPONSE_INVALID: TRANSIENT,
5682
+ BRIDGE_TOKEN_NOT_FOUND: INVALID_INPUT,
5683
+ TOKEN_NOT_WRAPPABLE: INVALID_INPUT,
5684
+ BRIDGE_CHAIN_READ_FAILED: TRANSIENT,
5685
+ TOKEN_CLASS_NOT_FOUND: INVALID_INPUT,
5686
+ PAGINATION_LIMIT_EXCEEDED: INVALID_INPUT,
6081
5687
  CONFIRMATION_TIMEOUT: UNKNOWN,
6082
5688
  CONFIRMATION_ABORTED: UNKNOWN,
6083
5689
  CONFIRMATION_FAILED: UNKNOWN,
@@ -6091,6 +5697,7 @@ const BY_CODE = {
6091
5697
  SIGNING_FAILED: NOT_SENT,
6092
5698
  KEY_PROVIDER_FAILED: NOT_SENT,
6093
5699
  TOKEN_RESOLUTION_FAILED: NOT_SENT,
5700
+ TOKEN_NAME_RESOLUTION_FAILED: NOT_SENT,
6094
5701
  NO_WALLET_ADDRESS: INVALID_INPUT,
6095
5702
  NO_POOL_AVAILABLE: INVALID_INPUT,
6096
5703
  INSUFFICIENT_LIQUIDITY: INVALID_INPUT
@@ -6123,6 +5730,14 @@ function classifyHttpError(view) {
6123
5730
  return status === 408 || status === 429 || status >= 500 ? TRANSIENT : INVALID_INPUT;
6124
5731
  }
6125
5732
  /**
5733
+ * A core `./bridge` GalaConnect HTTP failure: like {@link classifyHttpError}, except that a
5734
+ * WRITE answered 409 is `unknown`: GalaChain's "this uniqueKey was already consumed", after which
5735
+ * an earlier attempt under the key may be applied (a bridge-out that locked the tokens).
5736
+ */
5737
+ function classifyGalaConnectHttpError(view) {
5738
+ return isWriteView(view) && statusOfView(view) === 409 ? UNKNOWN : classifyHttpError(view);
5739
+ }
5740
+ /**
6126
5741
  * The version wall (HTTP 426). On a write the gateway refused it before queueing anything, and a
6127
5742
  * repeat of the same app version is refused the same way, so it is `not_queued` with no Retry. On a
6128
5743
  * read nothing changed either, and only an update helps: `invalid_input`, no Retry. Both say "show
@@ -6143,7 +5758,17 @@ function classifyChainDispatchFailed(view) {
6143
5758
  return status === 422 ? REJECTED : NOT_QUEUED;
6144
5759
  }
6145
5760
  /**
6146
- * Classifies any thrown value from its code (and, for two codes, its HTTP status), never from
5761
+ * GalaChain answered a core `GatewayWrites` token-contract write with a `Status` other than 1: the
5762
+ * chain's verdict, so the write was not applied (`rejected`), except where the verdict does not
5763
+ * prove that: a 409 is "this uniqueKey was already consumed" (an earlier attempt under it may be
5764
+ * applied), and a 5xx, or no status at all, cannot be judged.
5765
+ */
5766
+ function classifyGatewayWriteRejected(view) {
5767
+ const status = view.status;
5768
+ return status === void 0 || status === 409 || status >= 500 ? UNKNOWN : REJECTED;
5769
+ }
5770
+ /**
5771
+ * Classifies any thrown value from its code (and, for a few codes, its HTTP status), never from
6147
5772
  * message text, so an app does not have to re-derive "may I offer Retry?" and "is the outcome
6148
5773
  * unknown?" for every code. The decision for each code is in this module's table; a new catalog
6149
5774
  * code cannot be added without one.
@@ -6201,7 +5826,7 @@ function classifyFailure(error, options = {}) {
6201
5826
  }
6202
5827
  /**
6203
5828
  * The classification of a failure view: the table decision for its code, the status-dependent
6204
- * decision for the four codes whose meaning needs it, and for a code the catalog does not define
5829
+ * decision for the codes whose meaning needs it, and for a code the catalog does not define
6205
5830
  * (a wallet error, an ethers `ACTION_REJECTED`, a typo, an app's own error): `unknown` unless the
6206
5831
  * view DECLARES its `outcome` (an app's error, `{ code: 'POSITION_STALE', outcome: 'not_sent' }`),
6207
5832
  * then the SDK's own classification of that outcome. A value with no view is `unknown`.
@@ -6213,7 +5838,9 @@ function classifyView(view) {
6213
5838
  return view.retryable === false ? classification(view.outcome, { retryable: false }) : classification(view.outcome);
6214
5839
  }
6215
5840
  const { code } = view;
6216
- if (code === "HTTP_ERROR") return { ...classifyHttpError(view) };
5841
+ if (code === "HTTP_ERROR" || code === "GATEWAY_HTTP_ERROR") return { ...classifyHttpError(view) };
5842
+ if (code === "GALACONNECT_HTTP_ERROR") return { ...classifyGalaConnectHttpError(view) };
5843
+ if (code === "GATEWAY_WRITE_REJECTED" || code === "BRIDGE_REQUEST_REJECTED") return { ...classifyGatewayWriteRejected(view) };
6217
5844
  if (code === "UPDATE_REQUIRED") return { ...classifyUpdateRequired(view) };
6218
5845
  if (code === "CHAIN_DISPATCH_FAILED") return { ...classifyChainDispatchFailed(view) };
6219
5846
  if (code === "INVALID_PRIVATE_KEY") return { ...classifyInvalidPrivateKey(view) };
@@ -6531,796 +6158,12 @@ var PendingSwapStore = class {
6531
6158
  };
6532
6159
 
6533
6160
  //#endregion
6534
- //#region src/utils/eip712.ts
6535
- /**
6536
- * Genuine EIP-712 typed-data signing for the four v2 dex-contract operations proven (2026-09-16,
6537
- * live bisection against stage — see defi-backend's `chain-findings-artifact` memory /
6538
- * https://claude.ai/artifact/3N9MevSjoQdU7gqnVB3dfU) to recover the `client|` identity correctly
6539
- * via `eth_signTypedData_v4`, unlike `personal_sign` (which only ever resolves the bare `eth|`
6540
- * form — see `BrowserWalletSigner`'s own doc comment). Mirrors frontend_v2's
6541
- * `src/Services/eip712Types.ts` + `readWriteUtilsV2.ts`'s `EIP712_FIELD_COMPLETERS` exactly —
6542
- * ported here rather than shared as a package because gswap-sdk has no frontend_v2 dependency
6543
- * and should not gain one for four small functions.
6544
- */
6161
+ //#region src/classes/storage_adapters.ts
6545
6162
  /**
6546
- * Derives the EIP-712 `types` for a DTO from its runtime shape.
6547
- *
6548
- * The generated `types` travel with the signed payload and are what BOTH sides hash, so they
6549
- * must enumerate EVERY field the chain-side DTO will carry. GalaChain refuses any signature
6550
- * whose `types` leave a DTO field uncovered (`assertNoUnsignedFields`, the fix for the
6551
- * 2026-08-18 unsigned-field-injection drain); when that happens signature recovery fails
6552
- * outright and the chain reports "Missing signerPublicKey or signerAddress field in dto".
6553
- */
6554
- function generateEIP712Types(typeName, params) {
6555
- const types = {};
6556
- types[typeName] = [];
6557
- function fieldsFor(name) {
6558
- const fields = types[name];
6559
- /* c8 ignore next */
6560
- if (!fields) throw new Error(`Missing EIP-712 type definition for ${name}`);
6561
- return fields;
6562
- }
6563
- function addField(name, fieldValue, parentTypeName, onlyGetType = false) {
6564
- if (Array.isArray(fieldValue)) {
6565
- const type = fieldValue.length > 0 ? addField(name, fieldValue[0], parentTypeName, true) : "string";
6566
- if (!onlyGetType) fieldsFor(parentTypeName).push({
6567
- name,
6568
- type: (type ?? name) + "[]"
6569
- });
6570
- } else if (typeof fieldValue === "object" && fieldValue !== null) {
6571
- if (types[name]) throw new GSwapSDKError("EIP712_NAME_COLLISION", { name });
6572
- types[name] = [];
6573
- Object.entries(fieldValue).forEach(([key, value]) => {
6574
- addField(key, value, name);
6575
- });
6576
- if (!onlyGetType) fieldsFor(parentTypeName).push({
6577
- name,
6578
- type: name
6579
- });
6580
- } else {
6581
- let eipType;
6582
- switch (typeof fieldValue) {
6583
- case "string":
6584
- eipType = "string";
6585
- break;
6586
- case "number":
6587
- eipType = "int256";
6588
- break;
6589
- case "boolean":
6590
- eipType = "bool";
6591
- break;
6592
- case "bigint":
6593
- case "symbol":
6594
- case "undefined":
6595
- case "function":
6596
- case "object": throw new GSwapSDKError("EIP712_UNSUPPORTED_VALUE", { valueType: typeof fieldValue });
6597
- }
6598
- if (onlyGetType) return eipType;
6599
- fieldsFor(parentTypeName).push({
6600
- name,
6601
- type: eipType
6602
- });
6603
- }
6604
- }
6605
- Object.entries(params).forEach(([key, value]) => {
6606
- addField(key, value, typeName);
6607
- });
6608
- return types;
6609
- }
6610
- /**
6611
- * Builds the EIP-712 domain from the wallet's LIVE `eth_chainId` result, exactly like frontend_v2
6612
- * (DEFI-1895): a wallet hashes whatever domain it is handed, and MetaMask refuses a `chainId` that
6613
- * differs from its active network, so a hardcoded `1` breaks every wallet that is not on Mainnet.
6614
- * defi-backend recovers the signer from the `domain` the payload carries (live-proven for chain
6615
- * ids 1, 56 and 11155111), so following the wallet is correct on every network.
6616
- *
6617
- * @param chainId - The raw `eth_chainId` result: a `0x` hex string (the JSON-RPC form) or an integer.
6618
- * @throws GSwapSDKError (`INVALID_CHAIN_ID`) when the wallet answers with anything else.
6619
- */
6620
- function eip712DomainFor(chainId) {
6621
- const parsed = typeof chainId === "string" && /^0x[0-9a-fA-F]+$/u.test(chainId) ? Number(chainId) : typeof chainId === "number" ? chainId : NaN;
6622
- if (!Number.isSafeInteger(parsed) || parsed <= 0) throw new GSwapSDKError("INVALID_CHAIN_ID", { chainId: typeof chainId === "string" || typeof chainId === "number" ? String(chainId) : typeof chainId });
6623
- return {
6624
- name: "ethereum",
6625
- chainId: parsed
6626
- };
6627
- }
6628
- /**
6629
- * The four v2 operations proven to recover the signer correctly via genuine EIP-712: `RequestTrade`
6630
- * never had the problem (one numeric field). `AddLiquidity`/`RemoveLiquidity`/
6631
- * `CollectPositionFees` each need their `types` completed with a handful of fields their real
6632
- * server-side DTO always carries — see the `complete*Fields` functions below. `CreatePool` is a
6633
- * genuinely different, unsolved problem (nested `TokenClassKey` structs, not a flat
6634
- * missing-field gap) and is NOT in this set — callers must keep using `personal-sign` for it.
6635
- */
6636
- const EIP712_CAPABLE_METHODS = /* @__PURE__ */ new Set([
6637
- "RequestTrade",
6638
- "AddLiquidity",
6639
- "RemoveLiquidity",
6640
- "CollectPositionFees"
6641
- ]);
6642
- /**
6643
- * Every `complete*Fields` function below shares the same two live-proven rules:
6644
- * 1. Numeric completeness fields MUST be JS `number`, never numeric strings —
6645
- * `generateEIP712Types` infers a field's Solidity type from `typeof`, and declaring one as
6646
- * EIP-712 `string` instead of `int256` breaks recovery identically to omitting the field
6647
- * entirely (verified live on AddLiquidity: `amount0Min: '0'` fails with the exact same
6648
- * "Missing signerPublicKey" error as `amount0Min` being absent; `amount0Min: 0` succeeds).
6649
- * 2. `positionId` has no such constraint — an empty string works, and does not target an
6650
- * existing position (`positionId: ''` created a fresh position on AddLiquidity, not an add
6651
- * to position id `''`; on RemoveLiquidity/CollectPositionFees the real position is still
6652
- * resolved from `token0`/`token1`/`fee`/`tickLower`/`tickUpper`, not from this field).
6653
- */
6654
- function numericOr0(dto, field) {
6655
- return typeof dto[field] === "number" ? dto[field] : 0;
6656
- }
6657
- /**
6658
- * `AddLiquidity`'s real server-side DTO always carries `amount0Desired`/`amount0Min`/
6659
- * `amount1Desired`/`amount1Min`/`positionId` once reconstructed, regardless of which deposit
6660
- * convention the caller used (`Positions.addLiquidityByTicks` only ever sends
6661
- * `depositQuantityToken0`/`depositQuantityToken1`). Proven empirically by bisection: none of the
6662
- * five alone fixes recovery, all five together does (live 201, tx `407d9b24...`, block
6663
- * `2429022`).
6664
- */
6665
- function completeAddLiquidityFields(dto) {
6666
- return {
6667
- ...dto,
6668
- amount0Desired: numericOr0(dto, "amount0Desired"),
6669
- amount0Min: numericOr0(dto, "amount0Min"),
6670
- amount1Desired: numericOr0(dto, "amount1Desired"),
6671
- amount1Min: numericOr0(dto, "amount1Min"),
6672
- positionId: dto["positionId"] ?? ""
6673
- };
6674
- }
6675
- /**
6676
- * `RemoveLiquidity`'s real server-side DTO always carries `amount`/`amount0Min`/`amount1Min`/
6677
- * `positionId` — `Positions.removeLiquidity` only ever sends a `withdrawalQuantityTokenX` (or
6678
- * neither, for a full close). Proven empirically: every subset of these four fails, all four
6679
- * together succeeds (live, random-signer probe — recovery confirmed by the response moving from
6680
- * "Missing signerPublicKey" to a genuine position-not-found 404).
6681
- */
6682
- function completeRemoveLiquidityFields(dto) {
6683
- return {
6684
- ...dto,
6685
- amount: numericOr0(dto, "amount"),
6686
- amount0Min: numericOr0(dto, "amount0Min"),
6687
- amount1Min: numericOr0(dto, "amount1Min"),
6688
- positionId: dto["positionId"] ?? ""
6689
- };
6690
- }
6691
- /**
6692
- * `CollectPositionFees`'s real server-side DTO always carries `amount0Requested`/
6693
- * `amount1Requested`/`positionId` — `Positions.collectPositionFees` sends only the five identity
6694
- * fields. Proven empirically the same way as `completeRemoveLiquidityFields`.
6695
- */
6696
- function completeCollectPositionFeesFields(dto) {
6697
- return {
6698
- ...dto,
6699
- amount0Requested: numericOr0(dto, "amount0Requested"),
6700
- amount1Requested: numericOr0(dto, "amount1Requested"),
6701
- positionId: dto["positionId"] ?? ""
6702
- };
6703
- }
6704
- const EIP712_FIELD_COMPLETERS = {
6705
- AddLiquidity: completeAddLiquidityFields,
6706
- RemoveLiquidity: completeRemoveLiquidityFields,
6707
- CollectPositionFees: completeCollectPositionFeesFields
6708
- };
6709
- /**
6710
- * Completes a DTO's fields for EIP-712 signing (a no-op for methods with no known gap, e.g.
6711
- * `RequestTrade`) and derives its `types`. Exported separately from the signing call itself so tests
6712
- * can assert on the exact payload without needing a live provider.
6713
- */
6714
- function buildEIP712Payload(methodName, dto) {
6715
- const completer = EIP712_FIELD_COMPLETERS[methodName];
6716
- const completedDto = completer ? completer(dto) : dto;
6717
- return {
6718
- completedDto,
6719
- types: generateEIP712Types(methodName, completedDto)
6720
- };
6721
- }
6722
-
6723
- //#endregion
6724
- //#region src/utils/signing.ts
6725
- /**
6726
- * SDK-owned signing primitives.
6727
- *
6728
- * Everything the SDK needs to produce a GalaChain signature lives here, with no dependency on
6729
- * `@gala-chain/api` (which drags `secp256k1` native bindings, `reflect-metadata`, `class-validator`
6730
- * and its own `bignumber.js` major into every consumer). The only runtime dependencies are the
6731
- * audited, pure-JS `@noble/curves` (secp256k1) and `@noble/hashes` (keccak256), which run
6732
- * unchanged in browsers, MV3 service workers, Node and React Native/Hermes.
6733
- *
6734
- * Every function here is pinned byte-for-byte to the signatures produced by `@gala-chain/api`
6735
- * (`serialize`, `signatures.getSignature`), `@gala-chain/connect`, the Gala Wallet extension and
6736
- * frontend_v2 by the golden-vector suite in `tests/signing-golden.test.ts`.
6737
- */
6738
- const textEncoder$1 = new TextEncoder();
6739
- function toDeterministicNode(node) {
6740
- if (node === null || node === void 0) return node;
6741
- if (node instanceof RegExp) return node.toString();
6742
- const candidate = node;
6743
- return typeof candidate.toJSON === "function" ? Reflect.apply(candidate.toJSON, node, []) : node;
6744
- }
6745
- function deterministicNode(rawNode) {
6746
- const node = toDeterministicNode(rawNode);
6747
- if (node === void 0) return void 0;
6748
- if (typeof node !== "object" || node === null) return JSON.stringify(node);
6749
- if (Array.isArray(node)) return `[${node.map((item) => deterministicNode(item) ?? "null").join(",")}]`;
6750
- const record = node;
6751
- const members = [];
6752
- for (const key of Object.keys(record).sort()) {
6753
- const value = deterministicNode(record[key]);
6754
- if (value === void 0) continue;
6755
- members.push(`${JSON.stringify(key)}:${value}`);
6756
- }
6757
- return `{${members.join(",")}}`;
6758
- }
6759
- /**
6760
- * GalaChain's deterministic JSON: object keys sorted (UTF-16 code-unit order) at every depth, no
6761
- * whitespace, `undefined`/function members dropped, `toJSON()` honored. Byte-identical to
6762
- * `serialize` from `@gala-chain/api` / `json-stringify-deterministic` for every JSON-shaped
6763
- * value, which is all a DTO ever is.
6764
- *
6765
- * @throws TypeError for circular structures and BigInt values (as `JSON.stringify` does) and
6766
- * when the value itself has no JSON representation (`undefined`, a function, a symbol).
6767
- */
6768
- function serialize(value) {
6769
- JSON.stringify(value);
6770
- const result = deterministicNode(value);
6771
- if (result === void 0) throw new TypeError("Cannot serialize a value with no JSON representation.");
6772
- return result;
6773
- }
6774
- /**
6775
- * The exact string GalaChain hashes for a DTO: the (optional, string) `prefix` field PREPENDED to
6776
- * the deterministic serialization of the DTO WITHOUT `signature`, `multisig`, `trace` and `prefix`.
6777
- * Equivalent to `@gala-chain/api` 3.x `getPayloadToSign` for every non-EIP-712 DTO.
6778
- */
6779
- function getPayloadToSign(dto) {
6780
- const { prefix, ...rest } = dto;
6781
- delete rest["signature"];
6782
- delete rest["multisig"];
6783
- delete rest["trace"];
6784
- return (typeof prefix === "string" ? prefix : "") + serialize(rest);
6785
- }
6786
- const ETHEREUM_SIGNED_MESSAGE = String.fromCharCode(25) + "Ethereum Signed Message:\n";
6787
- /**
6788
- * The EIP-191 header for `message`: `"\x19Ethereum Signed Message:\n"` + the message's UTF-8 BYTE
6789
- * length. This is what a wallet's `personal_sign` prepends, and what the chain recomputes when it
6790
- * recovers a personal-signed DTO.
6791
- */
6792
- function calculatePersonalSignPrefix(message) {
6793
- return ETHEREUM_SIGNED_MESSAGE + String(textEncoder$1.encode(message).length);
6794
- }
6795
- /**
6796
- * The EIP-191 header for `serialize(dto)` (any `prefix` already on the DTO is ignored). This is
6797
- * deliberately the same string the signers hand a wallet to sign, so the header's length can never
6798
- * disagree with the signed message. Wallet-neutral: it is the value both a browser wallet's
6799
- * `personal_sign` and Gala Wallet's `gala_signChainDto` (personal-sign scheme) prepend.
6800
- */
6801
- function personalSignPrefixFor(dto) {
6802
- const withoutPrefix = { ...dto };
6803
- delete withoutPrefix["prefix"];
6804
- return calculatePersonalSignPrefix(serialize(withoutPrefix));
6805
- }
6806
- /**
6807
- * The `@gala-chain/connect`-style prefix frontend_v2 puts in the body of an EIP-712 write: the
6808
- * EIP-191 header whose number counts the prefix string ITSELF plus the body (the UTF-8 length of
6809
- * `getPayloadToSign({ ...dto, prefix })`), found by fixpoint because adding the prefix changes the
6810
- * length (a body of 99 bytes settles on 101).
6811
- *
6812
- * It is NOT a personal-sign prefix (a personal-sign write with this value recovers a phantom
6813
- * signer; use `personalSignPrefixFor`). Under EIP-712 the field is an untyped extra that the
6814
- * typed-data hash ignores and defi-backend recomputes, so it only exists to keep the submitted
6815
- * body byte-identical to what frontend_v2 has always sent.
6816
- */
6817
- function fixpointPrefixFor(dto) {
6818
- const withoutPrefix = { ...dto };
6819
- delete withoutPrefix["prefix"];
6820
- let payloadLength = textEncoder$1.encode(getPayloadToSign(withoutPrefix)).length;
6821
- let prefix = ETHEREUM_SIGNED_MESSAGE + String(payloadLength);
6822
- let previousLength = -1;
6823
- while (payloadLength !== previousLength) {
6824
- previousLength = payloadLength;
6825
- prefix = ETHEREUM_SIGNED_MESSAGE + String(payloadLength);
6826
- payloadLength = textEncoder$1.encode(getPayloadToSign({
6827
- ...withoutPrefix,
6828
- prefix
6829
- })).length;
6830
- }
6831
- return prefix;
6832
- }
6833
- function invalidPrivateKey(reason) {
6834
- return new GSwapSDKError("INVALID_PRIVATE_KEY", { reason });
6835
- }
6836
- const BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
6837
- function decodeBase64(input) {
6838
- if (!/^[A-Za-z0-9+/]+={0,2}$/u.test(input) || input.length % 4 !== 0) return void 0;
6839
- const unpadded = input.replace(/=+$/u, "");
6840
- const bytes = [];
6841
- let buffer = 0;
6842
- let bits = 0;
6843
- for (const char of unpadded) {
6844
- buffer = buffer << 6 | BASE64_ALPHABET.indexOf(char);
6845
- bits += 6;
6846
- if (bits >= 8) {
6847
- bits -= 8;
6848
- bytes.push(buffer >> bits & 255);
6849
- }
6850
- }
6851
- return Uint8Array.from(bytes);
6852
- }
6853
- function decodeHex(input) {
6854
- const bytes = new Uint8Array(input.length / 2);
6855
- for (let index = 0; index < bytes.length; index += 1) bytes[index] = parseInt(input.slice(index * 2, index * 2 + 2), 16);
6856
- return bytes;
6857
- }
6858
- /**
6859
- * Normalizes a secp256k1 private key to a fresh 32-byte array (the caller may zero it). Also
6860
- * accepts 32 raw bytes, which are validated and copied. For strings it accepts what `@gala-chain/api`'s
6861
- * `normalizePrivateKey` accepted for a usable key: 64 hex characters (optionally `0x`-prefixed;
6862
- * a 63-character hex key is left-padded with a zero) or a 44-character base64 string.
6863
- *
6864
- * @throws GSwapSDKError (`INVALID_PRIVATE_KEY`; `params.reason` is one of
6865
- * `INVALID_PRIVATE_KEY_REASONS`: `wrong_length` for raw bytes that are not 32 long,
6866
- * `bad_encoding` for any other string shape, `not_on_curve` for an out-of-range scalar).
6867
- */
6868
- function normalizePrivateKey(input) {
6869
- if (typeof input !== "string") {
6870
- if (!(input instanceof Uint8Array) || input.length !== 32) throw invalidPrivateKey("wrong_length");
6871
- if (!secp256k1.utils.isValidSecretKey(input)) throw invalidPrivateKey("not_on_curve");
6872
- return Uint8Array.from(input);
6873
- }
6874
- const withoutPrefix = input.startsWith("0x") ? input.slice(2) : input;
6875
- let key;
6876
- if (/^[0-9a-fA-F]{63,64}$/u.test(withoutPrefix)) key = decodeHex(withoutPrefix.length === 63 ? `0${withoutPrefix}` : withoutPrefix);
6877
- else if (withoutPrefix.length === 44) key = decodeBase64(withoutPrefix);
6878
- if ((key === null || key === void 0 ? void 0 : key.length) !== 32) throw invalidPrivateKey("bad_encoding");
6879
- if (!secp256k1.utils.isValidSecretKey(key)) throw invalidPrivateKey("not_on_curve");
6880
- return key;
6881
- }
6882
- /**
6883
- * Signs a 32-byte keccak256 hash and returns GalaChain's recoverable signature: 64 hex `r` +
6884
- * 64 hex `s` + `1b`/`1c` (`v`), lowercase, no `0x`. Deterministic (RFC 6979) and low-S, exactly
6885
- * what `@gala-chain/api` and the Gala Wallet produce. A DER signature carries no recovery byte
6886
- * and cannot be used: the chain RECOVERS the signer from the signature.
6887
- */
6888
- function signHash(privateKey, hash) {
6889
- if (hash.length !== 32) throw new GSwapSDKError("INVALID_SIGNING_HASH_LENGTH", { length: hash.length });
6890
- const signature = secp256k1.sign(hash, privateKey, {
6891
- prehash: false,
6892
- lowS: true
6893
- });
6894
- return bytesToHex(signature.toBytes("compact")) + (signature.recovery === 1 ? "1c" : "1b");
6895
- }
6896
- /**
6897
- * The lowercase `0x` Ethereum address (last 20 bytes of keccak256 of the uncompressed public key)
6898
- * of a normalized private key. The Gala `eth|` identity of a key is this address.
6899
- */
6900
- function ethAddressOfPrivateKey(privateKey) {
6901
- const publicKey = secp256k1.getPublicKey(privateKey, false);
6902
- return `0x${bytesToHex(keccak_256(publicKey.slice(1)).slice(-20))}`;
6903
- }
6904
- /** keccak256 of a string's UTF-8 bytes. */
6905
- function keccak256Utf8(data) {
6906
- return keccak_256(textEncoder$1.encode(data));
6907
- }
6908
- /**
6909
- * GalaChain NATIVE signature over an already-serialized DTO: keccak256 of the exact bytes, with no
6910
- * prefix and no signature envelope. The caller owns serialization, so the bytes shown to a user
6911
- * and the bytes signed can never diverge.
6912
- */
6913
- function signSerializedNative(privateKey, serializedDto) {
6914
- return signHash(privateKey, keccak256Utf8(serializedDto));
6915
- }
6916
- /**
6917
- * GalaChain NATIVE signature for a DTO: keccak256 of `getPayloadToSign(dto)`, signed with
6918
- * secp256k1. Byte-identical to `signatures.getSignature` from `@gala-chain/api`.
6919
- */
6920
- function signNative(privateKey, dto) {
6921
- return signSerializedNative(privateKey, getPayloadToSign(dto));
6922
- }
6923
- /**
6924
- * Personal-sign hash and signature: keccak256(`prefix` + `message`), both hashed VERBATIM with no
6925
- * further EIP-191 wrapping. Passing `prefix = calculatePersonalSignPrefix(message)` reproduces a
6926
- * wallet's `personal_sign` of `message`. Gala Wallet's `gala_signChainDto` (personal-sign
6927
- * scheme) hashes `prefix` + the exact string it is handed, which can differ from the bare message
6928
- * (see the `signatureOverPrefixlessBody` vectors); that quirk is why the two inputs stay separate.
6929
- */
6930
- function signPersonalMessage(privateKey, prefix, message) {
6931
- return signHash(privateKey, keccak256Utf8(prefix + message));
6932
- }
6933
-
6934
- //#endregion
6935
- //#region src/classes/signers.ts
6936
- function getWalletProvider() {
6937
- var _globalObject$window;
6938
- const wallet = (_globalObject$window = globalThis.window) === null || _globalObject$window === void 0 ? void 0 : _globalObject$window.gala;
6939
- if (!wallet) throw new GSwapSDKError("GALA_WALLET_NOT_AVAILABLE", {});
6940
- return wallet;
6941
- }
6942
- function getNativeSignature(value) {
6943
- if (typeof value !== "string") throw new GSwapSDKError("INVALID_SIGNATURE", { scheme: "native" });
6944
- if (!/^(?:0x)?[0-9a-fA-F]{130}$/u.test(value)) throw new GSwapSDKError("INVALID_SIGNATURE", { scheme: "native" });
6945
- return value;
6946
- }
6947
- function getPersonalSignature(value) {
6948
- if (typeof value !== "string" || !/^(?:0x)?[0-9a-fA-F]{130}$/u.test(value)) throw new GSwapSDKError("INVALID_SIGNATURE", { scheme: "personal-sign" });
6949
- return value;
6950
- }
6951
- /**
6952
- * The `0x`-prefixed Ethereum address a Gala account is signed by, or `undefined` when the account
6953
- * names no key (a `client|` alias or a bare GalaChain id is not derivable from a key). Accepts
6954
- * `0x` plus 40 hex digits and `eth|` plus 40 hex digits, with or without `0x`; the caller's casing
6955
- * is kept. For {@link LazyPrivateKeySignerOptions.expectedAddress}, which should only be set for
6956
- * an account whose key can be checked.
6957
- *
6958
- * @example
6959
- * ```ts
6960
- * createLazyPrivateKeySigner(getKey, { expectedAddress: ethereumAddressOfAccount(account) });
6961
- * ```
6962
- */
6963
- function ethereumAddressOfAccount(account) {
6964
- const match = /^(?:eth\|(?:0x)?|0x)([0-9a-fA-F]{40})$/u.exec(account);
6965
- return (match === null || match === void 0 ? void 0 : match[1]) === void 0 ? void 0 : `0x${match[1]}`;
6966
- }
6967
- function normalizeEthereumAccount(address, allowGalaPrefix) {
6968
- const normalized = allowGalaPrefix ? ethereumAddressOfAccount(address) ?? address : address;
6969
- if (!/^0x[0-9a-fA-F]{40}$/u.test(normalized)) throw new GSwapSDKError("INVALID_WALLET_ADDRESS", {
6970
- field: "walletAddress",
6971
- value: address
6972
- });
6973
- return normalized;
6974
- }
6975
- /**
6976
- * The account handed to Gala Wallet as the signer address. An Ethereum account is held to the
6977
- * same strict check as ever (`0x` plus 40 hex digits; `eth|` plus 40 hex digits, with or without
6978
- * `0x`, is normalized to it). A GalaChain id the wallet can also be asked to sign for is passed
6979
- * through verbatim: `client|` plus 24 hex digits, or the bare 24 hex digits that frontend_v2 keeps
6980
- * in `state.user.wallet` after a direct `client|` login. Nothing else is accepted.
6981
- */
6982
- function normalizeGalaWalletAccount(address) {
6983
- const eth = /^eth\|(?:0x)?([0-9a-fA-F]{40})$/u.exec(address);
6984
- if ((eth === null || eth === void 0 ? void 0 : eth[1]) !== void 0) return `0x${eth[1]}`;
6985
- if (/^0x[0-9a-fA-F]{40}$/u.test(address) || /^(?:client\|)?[0-9a-fA-F]{24}$/u.test(address)) return address;
6986
- throw new GSwapSDKError("INVALID_WALLET_ADDRESS", {
6987
- field: "walletAddress",
6988
- value: address
6989
- });
6990
- }
6991
- /** An identity Gala Wallet can act as: an account form above, any `eth|`/`client|` alias, verbatim. */
6992
- function assertWalletIdentity(identity) {
6993
- 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("INVALID_WALLET_ADDRESS", {
6994
- field: "selectAddress",
6995
- value: identity
6996
- });
6997
- }
6998
- /** Provider-wide queue: `setAddress` + the signature request must not interleave across signers. */
6999
- const walletIdentityQueue = /* @__PURE__ */ new WeakMap();
7000
- /**
7001
- * Runs `signWith` with `identity` selected on a Gala Wallet that supports `setAddress`. Gala
7002
- * Wallet holds ONE current account, so two signatures for different identities in flight at once
7003
- * would sign as whichever selection came last; the pair (select, sign) is therefore serialized
7004
- * per provider. A failed selection fails the signature (nothing is requested).
7005
- */
7006
- function withSelectedIdentity(wallet, identity, signWith) {
7007
- const { setAddress } = wallet;
7008
- if (identity === void 0 || typeof setAddress !== "function") return signWith();
7009
- const run = (walletIdentityQueue.get(wallet) ?? Promise.resolve()).catch(() => void 0).then(async () => {
7010
- await setAddress.call(wallet, identity);
7011
- return signWith();
7012
- });
7013
- walletIdentityQueue.set(wallet, run);
7014
- return run;
7015
- }
7016
- function personalSign(provider, dto, walletAddress, methodName) {
7017
- const prefix = personalSignPrefixFor(dto);
7018
- const payload = {
7019
- ...dto,
7020
- prefix
7021
- };
7022
- const params = methodName !== void 0 ? [
7023
- serialize(payload),
7024
- walletAddress,
7025
- methodName
7026
- ] : [serialize(dto), walletAddress];
7027
- return provider.request({
7028
- method: methodName !== void 0 ? "gala_signChainDto" : "personal_sign",
7029
- params
7030
- }).then((value) => ({
7031
- ...payload,
7032
- signature: getPersonalSignature(value)
7033
- }));
7034
- }
7035
- /**
7036
- * Signs a DTO via genuine EIP-712 (`eth_signTypedData_v4`) — correctly resolves the `client|`
7037
- * identity, unlike `personal_sign`, for the operations in `EIP712_CAPABLE_METHODS`.
7038
- *
7039
- * Constructs the raw JSON-RPC payload by hand (`{domain, types: {...types, EIP712Domain},
7040
- * primaryType, message}`) rather than depending on ethers.js, matching exactly what
7041
- * `ethers.TypedDataEncoder.getPayload`/`Wallet.signTypedData` produce internally — verified
7042
- * byte-for-byte equivalent (same recovered address from the same signature against both
7043
- * constructions) before this shipped, since gswap-sdk deliberately has no ethers dependency.
7044
- */
7045
- async function signEIP712(provider, methodName, dto, walletAddress) {
7046
- const { completedDto, types } = buildEIP712Payload(methodName, dto);
7047
- const domain = eip712DomainFor(await provider.request({
7048
- method: "eth_chainId",
7049
- params: []
7050
- }));
7051
- const prefix = fixpointPrefixFor(completedDto);
7052
- const message = {
7053
- ...completedDto,
7054
- prefix
7055
- };
7056
- const eip712Domain = [{
7057
- name: "name",
7058
- type: "string"
7059
- }, {
7060
- name: "chainId",
7061
- type: "uint256"
7062
- }];
7063
- const payload = {
7064
- domain,
7065
- types: {
7066
- ...types,
7067
- EIP712Domain: eip712Domain
7068
- },
7069
- primaryType: methodName,
7070
- message
7071
- };
7072
- const value = await provider.request({
7073
- method: "eth_signTypedData_v4",
7074
- params: [walletAddress, JSON.stringify(payload)]
7075
- });
7076
- return {
7077
- ...message,
7078
- signature: getPersonalSignature(value),
7079
- types,
7080
- domain
7081
- };
7082
- }
7083
- /**
7084
- * A GalaChain native signer backed by a secp256k1 private key.
7085
- *
7086
- * Native signing produces the bare GalaChain signature format, without a personal-sign prefix
7087
- * or EIP-712 metadata. The private-key identity is recovered by the chain.
7088
- */
7089
- var PrivateKeySigner = class {
7090
- privateKey;
7091
- /**
7092
- * Creates a native signer from a hex or base64 secp256k1 private key.
7093
- *
7094
- * @example
7095
- * ```typescript
7096
- * const signer = new PrivateKeySigner('1'.padStart(64, '0'));
7097
- * const signed = await signer.signObject('RequestTrade', { token0: 'GALA', token1: 'GUSDC' });
7098
- * ```
7099
- */
7100
- constructor(privateKey) {
7101
- this.privateKey = normalizePrivateKey(privateKey);
7102
- }
7103
- /**
7104
- * Signs the DTO with GalaChain native signing.
7105
- *
7106
- * @example
7107
- * ```typescript
7108
- * const signed = await signer.signObject('RequestTrade', {
7109
- * token0: 'GALA',
7110
- * token1: 'GUSDC',
7111
- * fee: 3000,
7112
- * });
7113
- * ```
7114
- */
7115
- signObject(_methodName, dto) {
7116
- const signature = signNative(this.privateKey, dto);
7117
- return Promise.resolve({
7118
- ...dto,
7119
- signature
7120
- });
7121
- }
7122
- };
7123
- /**
7124
- * Calls the app's key provider. Whatever it throws or rejects with is the app's own failure to
7125
- * produce a key before anything was signed or sent, so it becomes `KEY_PROVIDER_FAILED` (the
7126
- * original is `cause`); an SDK error is already catalogued and passes through.
7127
- */
7128
- async function keyFrom(getPrivateKey) {
7129
- try {
7130
- return await getPrivateKey();
7131
- } catch (cause) {
7132
- throw isGSwapError(cause) ? cause : new GSwapSDKError("KEY_PROVIDER_FAILED", {}, { cause });
7133
- }
7134
- }
7135
- /**
7136
- * Creates the native-signing equivalent of {@link PrivateKeySigner} for apps that must not hold a
7137
- * key in a long-lived object (an extension that reads its session key at sign time).
7138
- *
7139
- * `getPrivateKey` is called once per `signObject` call, never cached, never shared between calls
7140
- * (concurrent signatures each fetch their own key), and the normalized key bytes are zeroed
7141
- * before `signObject` settles, also on failure. A rejection or exception from `getPrivateKey` (the
7142
- * app's "wallet is locked", a storage read that failed) is a `KEY_PROVIDER_FAILED` error with the
7143
- * original error as `cause`: nothing has been sent, so `classifyFailure` says `not_sent` and Retry
7144
- * is safe, and the app needs no error class of its own to tell this apart. A provider that returns
7145
- * no usable string (`''`, `undefined`) is `INVALID_PRIVATE_KEY` with `reason: 'no_key_provided'`,
7146
- * classified the same way; any other key that is not a valid secp256k1 key string is an
7147
- * `INVALID_PRIVATE_KEY` error. An SDK error the provider throws itself is passed through.
7148
- * Signatures are byte-identical to `new PrivateKeySigner(key).signObject(...)`.
7149
- *
7150
- * A JavaScript string cannot be wiped; the provider owns the lifetime of the string it returns.
7151
- *
7152
- * @example
7153
- * ```typescript
7154
- * const signer = createLazyPrivateKeySigner(
7155
- * () => SecurityHelper.getSessionSecret(SecurityHelper.SESSION_PRIVATE_KEY_KEY),
7156
- * { expectedAddress: account.address },
7157
- * );
7158
- * const gswap = new GSwap({ signer });
7159
- * ```
7160
- */
7161
- function createLazyPrivateKeySigner(getPrivateKey, options = {}) {
7162
- const expected = options.expectedAddress;
7163
- const expectedLower = expected === void 0 ? void 0 : normalizeEthereumAccount(expected, true).toLowerCase();
7164
- return { async signObject(_methodName, dto) {
7165
- const provided = await keyFrom(getPrivateKey);
7166
- if (typeof provided !== "string" || provided === "") throw new GSwapSDKError("INVALID_PRIVATE_KEY", { reason: "no_key_provided" });
7167
- const key = normalizePrivateKey(provided);
7168
- try {
7169
- if (expectedLower !== void 0 && ethAddressOfPrivateKey(key) !== expectedLower) throw new GSwapSDKError("SIGNER_ADDRESS_MISMATCH", {});
7170
- return {
7171
- ...dto,
7172
- signature: signNative(key, dto)
7173
- };
7174
- } finally {
7175
- key.fill(0);
7176
- }
7177
- } };
7178
- }
7179
- /**
7180
- * A Gala Wallet signer using native GalaChain signing, with an opt-in personal-sign mode.
7181
- *
7182
- * The selected Ethereum account is passed to Gala Wallet. There is NO automatic fallback: a
7183
- * wallet that rejects `native` surfaces its error to the caller, because the only alternative,
7184
- * `personal-sign`, silently recovers a phantom signer on every released Gala Wallet.
7185
- *
7186
- * `scheme: 'personal-sign'` calls
7187
- * `gala_signChainDto([serialize({ ...dto, prefix }), walletAddress, methodName])` and REQUIRES
7188
- * a wallet build that contains wallet_extension commit 34ca4e4 (strips the embedded `prefix`
7189
- * field and hashes `prefix` + the prefix-less canonical body, the chain's scheme). Wallets
7190
- * released before that fix hash the string with the `prefix` field still inside it and the
7191
- * signature recovers to an address nobody holds, so only opt in once the fixed wallet is
7192
- * released. See SIGNING-PARITY.md.
7193
- */
7194
- var GalaWalletSigner = class {
7195
- /** The wallet address supplied to Gala Wallet for each signing request. */
7196
- walletAddress;
7197
- /** The signing scheme used for every request; it never changes after construction. */
7198
- scheme;
7199
- /**
7200
- * Creates a Gala Wallet signer.
7201
- *
7202
- * @example
7203
- * ```typescript
7204
- * const signer = new GalaWalletSigner('0x0123456789012345678901234567890123456789');
7205
- * const signed = await signer.signObject('RequestTrade', { token0: 'GALA', token1: 'GUSDC' });
7206
- * ```
7207
- *
7208
- * @param walletAddress - The account passed to Gala Wallet as the signer address. An Ethereum
7209
- * account is `0x` plus 40 hex digits (Gala Wallet's `eth|...` form is normalized to it and held
7210
- * to the same check); a GalaChain id is `client|` plus 24 hex digits, or the bare 24 hex digits
7211
- * frontend_v2 keeps for a direct `client|` login, and is passed through unchanged.
7212
- * @param options - Select native signing (default) or personal-sign explicitly.
7213
- * `'personal-sign'` requires a Gala Wallet build with wallet_extension commit 34ca4e4.
7214
- * `selectAddress` is the identity (`client|...` / `eth|...`) Gala Wallet is switched to with
7215
- * `window.gala.setAddress` before every signature, when the wallet offers it.
7216
- */
7217
- constructor(walletAddress, options = {}) {
7218
- this.walletAddress = normalizeGalaWalletAccount(walletAddress);
7219
- this.scheme = options.scheme ?? "native";
7220
- if (options.selectAddress !== void 0) assertWalletIdentity(options.selectAddress);
7221
- this.selectAddress = options.selectAddress;
7222
- }
7223
- /** The identity Gala Wallet is switched to before each signature, if one was given. */
7224
- selectAddress;
7225
- /**
7226
- * Signs a DTO through `gala_signChainDto`.
7227
- *
7228
- * @example
7229
- * ```typescript
7230
- * const signed = await signer.signObject('AddLiquidity', {
7231
- * token0: 'GALA',
7232
- * token1: 'GUSDC',
7233
- * tickLower: -60,
7234
- * tickUpper: 60,
7235
- * });
7236
- * ```
7237
- */
7238
- async signObject(methodName, dto) {
7239
- const wallet = getWalletProvider();
7240
- return withSelectedIdentity(wallet, this.selectAddress, async () => {
7241
- if (this.scheme === "personal-sign") return personalSign(wallet, dto, this.walletAddress, methodName);
7242
- const signature = await wallet.request({
7243
- method: "gala_signChainDto",
7244
- params: [
7245
- serialize(dto),
7246
- this.walletAddress,
7247
- methodName,
7248
- "native"
7249
- ]
7250
- });
7251
- return {
7252
- ...dto,
7253
- signature: getNativeSignature(signature)
7254
- };
7255
- });
7256
- }
7257
- };
7258
- /**
7259
- * A personal-sign signer for MetaMask and other EIP-1193 browser wallets.
7260
- *
7261
- * The chain recovers this signature using the EIP-191 wrap applied by the wallet to
7262
- * `personal_sign`. This is the browser-wallet path used by the v2 frontend and resolves the
7263
- * bare `eth|` identity.
7264
- */
7265
- var BrowserWalletSigner = class {
7266
- provider;
7267
- /**
7268
- * Creates a browser-wallet signer.
7269
- *
7270
- * Defaults to `personal_sign` — unchanged from every existing integration, since it only ever
7271
- * resolves the bare `eth|` identity and changing that silently would be a real behavior
7272
- * break for anyone already depending on it. Pass `{ scheme: 'eip712' }` to correctly resolve
7273
- * a registered `client|` alias instead, for `RequestTrade`/`AddLiquidity`/`RemoveLiquidity`/
7274
- * `CollectPositionFees` (`EIP712_CAPABLE_METHODS`) — `CreatePool` and any other method still
7275
- * falls back to `personal_sign` even in `'eip712'` mode, since its EIP-712 signing is a
7276
- * separate, unsolved chain-side problem.
7277
- *
7278
- * @example
7279
- * ```typescript
7280
- * const signer = new BrowserWalletSigner(window.ethereum, '0x0123456789012345678901234567890123456789');
7281
- * const signed = await signer.signObject('RequestTrade', { token0: 'GALA', token1: 'GUSDC' });
7282
- * ```
7283
- * @example
7284
- * ```typescript
7285
- * // Correctly resolves a registered client| alias instead of the bare eth| address.
7286
- * const signer = new BrowserWalletSigner(window.ethereum, walletAddress, { scheme: 'eip712' });
7287
- * ```
7288
- */
7289
- constructor(provider, walletAddress, options = {}) {
7290
- this.provider = provider;
7291
- this.walletAddress = normalizeEthereumAccount(walletAddress, false);
7292
- this.scheme = options.scheme ?? "personal-sign";
7293
- }
7294
- /** The selected `0x` Ethereum account every signature is requested for. */
7295
- walletAddress;
7296
- scheme;
7297
- /**
7298
- * Signs the DTO with `eth_signTypedData_v4` (when constructed with `{ scheme: 'eip712' }` and
7299
- * `methodName` is in `EIP712_CAPABLE_METHODS`) or the EIP-1193 `personal_sign` method
7300
- * (otherwise — the default).
7301
- *
7302
- * @example
7303
- * ```typescript
7304
- * const signed = await signer.signObject('RequestTrade', {
7305
- * token0: 'GALA',
7306
- * token1: 'GUSDC',
7307
- * fee: 3000,
7308
- * });
7309
- * ```
7310
- */
7311
- signObject(methodName, dto) {
7312
- if (this.scheme === "eip712" && EIP712_CAPABLE_METHODS.has(methodName)) return signEIP712(this.provider, methodName, dto, this.walletAddress);
7313
- return personalSign(this.provider, dto, this.walletAddress);
7314
- }
7315
- };
7316
-
7317
- //#endregion
7318
- //#region src/classes/storage_adapters.ts
7319
- /**
7320
- * A {@link StorageAdapter} over a `chrome.storage` area, the shape the wallet stores pending swaps
7321
- * in: the value lands under the key as the structured value it is (the browser clones it; nothing
7322
- * is serialized here). A missing key resolves `undefined`; a rejection of the area reaches the
7323
- * store, which treats it as "storage unavailable".
6163
+ * A {@link StorageAdapter} over a `chrome.storage` area, the shape the wallet stores pending swaps
6164
+ * in: the value lands under the key as the structured value it is (the browser clones it; nothing
6165
+ * is serialized here). A missing key resolves `undefined`; a rejection of the area reaches the
6166
+ * store, which treats it as "storage unavailable".
7324
6167
  *
7325
6168
  * Pass a FUNCTION that returns the area to look it up at each call instead of when the adapter is
7326
6169
  * built: a module that is imported where `chrome` does not exist yet (a test, a closing extension
@@ -7382,658 +6225,6 @@ function webStorageAdapter(area) {
7382
6225
  };
7383
6226
  }
7384
6227
 
7385
- //#endregion
7386
- //#region src/utils/chain_dto_signing.ts
7387
- /**
7388
- * String-key signing entry points for apps that hold a private key only briefly (the Gala Wallet
7389
- * extension reads its session key at sign time and must not keep it in a long-lived object).
7390
- *
7391
- * These are thin, byte-exact ports of the functions the wallet shipped in
7392
- * `wallet_extension/src/helpers/wallet/signing.ts` (including the fail-closed personal-sign
7393
- * payload resolver from its commit 34ca4e4), re-expressed over the SDK's own primitives. Every
7394
- * function takes the key as a string (`0x`-optional hex, or base64) or as bytes, normalizes it per
7395
- * call, and zeroes the normalized copy before returning, so nothing in this module retains a key.
7396
- * A JavaScript string cannot be wiped; the caller owns the lifetime of the string it passes in.
7397
- */
7398
- /**
7399
- * Runs `action` with a private normalized copy of the key, zeroed afterwards (also when `action`
7400
- * throws). Bytes given by the caller are copied, never modified: the caller owns them.
7401
- */
7402
- function withPrivateKey(privateKey, action) {
7403
- const key = normalizePrivateKey(privateKey);
7404
- try {
7405
- return action(key);
7406
- } finally {
7407
- key.fill(0);
7408
- }
7409
- }
7410
- /**
7411
- * GalaChain NATIVE signature over an already-serialized DTO: keccak256 of the exact bytes with no
7412
- * prefix and no envelope (wallet `signChainDtoNative`). The caller owns serialization, so the
7413
- * bytes a user reviewed and the bytes signed cannot diverge.
7414
- *
7415
- * @returns 130 lowercase hex characters, `r || s || v` with `v` = `1b`/`1c`, no `0x`.
7416
- */
7417
- function signChainDtoNative(privateKey, serializedDto) {
7418
- return withPrivateKey(privateKey, (key) => signSerializedNative(key, serializedDto));
7419
- }
7420
- /**
7421
- * The personal-sign hash primitive (wallet `signChainDtoHash`): keccak256(`prefix` + `message`),
7422
- * both verbatim, with no further EIP-191 wrap. `message` must be the DTO's serialization WITHOUT
7423
- * its `prefix` field; callers holding a dApp-supplied string go through `signChainDtoPersonalSign`,
7424
- * which derives both inputs from it.
7425
- */
7426
- function signChainDtoHash(privateKey, prefix, message) {
7427
- return withPrivateKey(privateKey, (key) => signPersonalMessage(key, prefix, message));
7428
- }
7429
- const PERSONAL_SIGN_HEADER = String.fromCharCode(25) + "Ethereum Signed Message:\n";
7430
- const PERSONAL_SIGN_LENGTH_PATTERN = /^(?:0|[1-9][0-9]*)$/u;
7431
- function isPersonalSignHeader(prefix) {
7432
- return prefix.startsWith(PERSONAL_SIGN_HEADER) && PERSONAL_SIGN_LENGTH_PATTERN.test(prefix.slice(26));
7433
- }
7434
- /**
7435
- * Top-level fields that would make the chain (`getPayloadToSign`) or defi-backend's personal-sign
7436
- * verifier hash something other than `prefix + serialize(dto minus prefix)`. An unsigned DTO from
7437
- * a dApp never legitimately carries any of them, so they fail closed. Order is part of the error
7438
- * message the wallet shows its user.
7439
- */
7440
- const PERSONAL_SIGN_FORBIDDEN_FIELDS = [
7441
- "signature",
7442
- "multisig",
7443
- "trace",
7444
- "types",
7445
- "domain",
7446
- "stringsInstructions"
7447
- ];
7448
- /**
7449
- * Derives what to hash for a `gala_signChainDto` personal-sign request from the exact string the
7450
- * dApp sent (and the approval card displayed): wallet `resolvePersonalSignChainDtoPayload`.
7451
- *
7452
- * The dApp submits `{ ...dto, prefix, signature }`, which the chain recovers from
7453
- * `keccak256(prefix + serialize(dto WITHOUT prefix))`. So when the string embeds a `prefix`
7454
- * field, that field is removed and the remainder re-serialized; the prefix used is the dApp's own
7455
- * value byte for byte. A string with no `prefix` field is hashed verbatim with an empty prefix
7456
- * (chain-equivalent to a native signature of that exact string).
7457
- *
7458
- * Fails closed (throws `GSwapSDKError` `PERSONAL_SIGN_NOT_JSON`, `PERSONAL_SIGN_NOT_OBJECT`,
7459
- * `PERSONAL_SIGN_BAD_PREFIX`, `PERSONAL_SIGN_ENVELOPE_FIELDS` or `PERSONAL_SIGN_NOT_CANONICAL`) on:
7460
- * malformed JSON; JSON that is not a plain object; a `prefix` that is not an EIP-191
7461
- * "Ethereum Signed Message" header; any signing-envelope field (`signature`, `multisig`, `trace`,
7462
- * `types`, `domain`, `stringsInstructions`); and a string that is not already the canonical
7463
- * serialization of what it parses to (duplicate keys, lossy numbers, key-order tricks the card
7464
- * would render differently). Stripping `prefix` is therefore the ONLY byte change between the
7465
- * reviewed string and the signed message.
7466
- */
7467
- function resolvePersonalSignChainDtoPayload(data) {
7468
- let parsed;
7469
- try {
7470
- parsed = JSON.parse(data);
7471
- } catch {
7472
- throw new GSwapSDKError("PERSONAL_SIGN_NOT_JSON", {});
7473
- }
7474
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) throw new GSwapSDKError("PERSONAL_SIGN_NOT_OBJECT", {});
7475
- const dto = parsed;
7476
- if (!Object.prototype.hasOwnProperty.call(dto, "prefix")) return {
7477
- prefix: "",
7478
- message: data
7479
- };
7480
- const { prefix, ...withoutPrefix } = dto;
7481
- if (typeof prefix !== "string" || !isPersonalSignHeader(prefix)) throw new GSwapSDKError("PERSONAL_SIGN_BAD_PREFIX", {});
7482
- const forbidden = PERSONAL_SIGN_FORBIDDEN_FIELDS.filter((field) => Object.prototype.hasOwnProperty.call(withoutPrefix, field));
7483
- if (forbidden.length > 0) throw new GSwapSDKError("PERSONAL_SIGN_ENVELOPE_FIELDS", { fields: forbidden.join(", ") });
7484
- if (serialize(dto) !== data) throw new GSwapSDKError("PERSONAL_SIGN_NOT_CANONICAL", {});
7485
- return {
7486
- prefix,
7487
- message: serialize(withoutPrefix)
7488
- };
7489
- }
7490
- /**
7491
- * Signs a GalaChain personal-sign scheme DTO from the exact string a dApp sent through
7492
- * `gala_signChainDto` (wallet `signChainDtoPersonalSign`). See
7493
- * `resolvePersonalSignChainDtoPayload` for what is signed and what is refused; the signature
7494
- * recovers, under the chain's `getPayloadToSign`, to this key for the DTO the dApp submits with
7495
- * its `prefix`, and equals a `personal_sign` of the bare body when that prefix is the EIP-191
7496
- * header of the bare body's byte length.
7497
- *
7498
- * @throws GSwapSDKError (`PERSONAL_SIGN_*`, see `resolvePersonalSignChainDtoPayload`) before any
7499
- * signing when the input is refused.
7500
- */
7501
- function signChainDtoPersonalSign(privateKey, data) {
7502
- const { prefix, message } = resolvePersonalSignChainDtoPayload(data);
7503
- return signChainDtoHash(privateKey, prefix, message);
7504
- }
7505
- /**
7506
- * Raw object signing (wallet `signObject`), used for transfers, bridge and fee-dry-run DTOs the
7507
- * wallet signs itself. Removes ONLY `signature`, signs `keccak256(serialize(rest))` natively
7508
- * (no `prefix` handling, and `trace` / `multisig` stay in the signed bytes, unlike
7509
- * `signNative`), and returns `{ signature, ...rest }` with the signature as the FIRST key, exactly
7510
- * like the wallet. Callers that must not sign extra fields strip them first.
7511
- */
7512
- function signRawObject(privateKey, obj) {
7513
- const rest = { ...obj };
7514
- delete rest["signature"];
7515
- return {
7516
- signature: signChainDtoNative(privateKey, serialize(rest)),
7517
- ...rest
7518
- };
7519
- }
7520
- /**
7521
- * Drops the `EIP712Domain` entry from a dApp-supplied types map (a typed-data encoder derives the
7522
- * domain type itself and rejects an explicit one). The order of the remaining types is preserved.
7523
- */
7524
- function stripEip712DomainType(types) {
7525
- const stripped = {};
7526
- for (const typeName of Object.keys(types)) if (typeName !== "EIP712Domain") stripped[typeName] = types[typeName];
7527
- return stripped;
7528
- }
7529
- /**
7530
- * `collection$category$type$additionalKey` for a token-class object (wallet `decodeTokenData`).
7531
- * Lenient on purpose: any missing or non-string part becomes `''`, so a display layer never
7532
- * throws on odd input.
7533
- */
7534
- function tokenClassDollarKey(token) {
7535
- const part = (field) => {
7536
- const value = token === null || token === void 0 ? void 0 : token[field];
7537
- return typeof value === "string" ? value : "";
7538
- };
7539
- return `${part("collection")}$${part("category")}$${part("type")}$${part("additionalKey")}`;
7540
- }
7541
- /**
7542
- * A quantity in the form GalaChain's `IsBigNumber` validator accepts: FIXED notation, no leading
7543
- * zeros, no trailing decimal zeros (`BigNumber#toFixed()`; never `toString()`, which turns
7544
- * `0.00000001` into `1e-8`, and never a JS number in between).
7545
- *
7546
- * @throws GSwapSDKError (`INVALID_DECIMAL`) for a value that is not a finite number. The wallet's
7547
- * `cleanNumber` returned the string `'NaN'` there; signing that text can only fail on chain.
7548
- */
7549
- function canonicalDecimalString(value) {
7550
- let parsed = new BigNumber(NaN);
7551
- try {
7552
- parsed = new BigNumber(value);
7553
- } catch {}
7554
- if (!parsed.isFinite()) throw new GSwapSDKError("INVALID_DECIMAL", { value: String(value) });
7555
- return parsed.toFixed();
7556
- }
7557
-
7558
- //#endregion
7559
- //#region src/utils/typed_data.ts
7560
- /**
7561
- * EIP-712 (`eth_signTypedData_v4`) hashing and signing for ARBITRARY dApp typed data, built on the
7562
- * SDK's own primitives (`@noble/curves`, `@noble/hashes`) so a wallet can sign a dApp's typed-data
7563
- * approval without ethers. It replaces the wallet's `new ethers.Wallet(key).signTypedData(...)`
7564
- * and is pinned to it: the golden suite reproduces every fixture signature exactly and a seeded
7565
- * differential fuzz compares the digest and signature with ethers for every supported type.
7566
- *
7567
- * Compared with ethers this encoder is deliberately STRICTER (it fails closed rather than coerce):
7568
- * `bool` accepts only a boolean, numbers accept only safe integers / decimal or `0x` strings,
7569
- * strings must be well-formed UTF-16, struct values must be objects whose fields are their own
7570
- * properties, and every struct, field and type name must be a plain identifier. Anything ethers
7571
- * accepts that is NOT in that set is refused here instead of being signed in a different shape
7572
- * from what a user reviewed.
7573
- */
7574
- const textEncoder = new TextEncoder();
7575
- const IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/u;
7576
- const TYPE_SYNTAX = /^([A-Za-z_$][A-Za-z0-9_$]*)((?:\[(?:0|[1-9][0-9]*)?\])*)$/u;
7577
- const BIGINT_DECIMAL = /^-?[0-9]+$/u;
7578
- const BIGINT_HEX = /^-?0x[0-9a-fA-F]+$/u;
7579
- /** The EIP-712 domain members and their types, in the order ethers (and the spec) hash them. */
7580
- const DOMAIN_FIELDS = [
7581
- {
7582
- name: "name",
7583
- type: "string"
7584
- },
7585
- {
7586
- name: "version",
7587
- type: "string"
7588
- },
7589
- {
7590
- name: "chainId",
7591
- type: "uint256"
7592
- },
7593
- {
7594
- name: "verifyingContract",
7595
- type: "address"
7596
- },
7597
- {
7598
- name: "salt",
7599
- type: "bytes32"
7600
- }
7601
- ];
7602
- function concat(parts) {
7603
- const out = new Uint8Array(parts.reduce((total, part) => total + part.length, 0));
7604
- let offset = 0;
7605
- for (const part of parts) {
7606
- out.set(part, offset);
7607
- offset += part.length;
7608
- }
7609
- return out;
7610
- }
7611
- function bigintToWord(value) {
7612
- const word = /* @__PURE__ */ new Uint8Array(32);
7613
- let rest = value < 0n ? value + (1n << 256n) : value;
7614
- for (let index = 31; index >= 0; index -= 1) {
7615
- word[index] = Number(rest & 255n);
7616
- rest >>= 8n;
7617
- }
7618
- return word;
7619
- }
7620
- function hexToBytes(hex) {
7621
- const body = hex.slice(2);
7622
- const out = new Uint8Array(body.length / 2);
7623
- for (let index = 0; index < out.length; index += 1) out[index] = parseInt(body.slice(index * 2, index * 2 + 2), 16);
7624
- return out;
7625
- }
7626
- function toBytes(value, what) {
7627
- if (value instanceof Uint8Array) return value;
7628
- if (typeof value === "string" && /^0x(?:[0-9a-fA-F]{2})*$/u.test(value)) return hexToBytes(value);
7629
- throw new GSwapSDKError("TYPED_DATA_NOT_HEX_BYTES", { what });
7630
- }
7631
- function toBigInt(value, what) {
7632
- if (typeof value === "bigint") return value;
7633
- if (typeof value === "number") {
7634
- if (!Number.isSafeInteger(value)) throw new GSwapSDKError("TYPED_DATA_NOT_SAFE_INTEGER", { what });
7635
- return BigInt(value);
7636
- }
7637
- if (typeof value === "string") {
7638
- if (BIGINT_DECIMAL.test(value)) return BigInt(value);
7639
- if (BIGINT_HEX.test(value)) return value.startsWith("-") ? -BigInt(value.slice(1)) : BigInt(value);
7640
- }
7641
- throw new GSwapSDKError("TYPED_DATA_NOT_INTEGER", { what });
7642
- }
7643
- function utf8(value, what) {
7644
- if (typeof value !== "string") throw new GSwapSDKError("TYPED_DATA_NOT_STRING", { what });
7645
- for (let index = 0; index < value.length; index += 1) {
7646
- const unit = value.charCodeAt(index);
7647
- if (unit >= 55296 && unit <= 56319) {
7648
- const next = value.charCodeAt(index + 1);
7649
- if (!(next >= 56320 && next <= 57343)) throw new GSwapSDKError("TYPED_DATA_UNPAIRED_SURROGATE", { what });
7650
- index += 1;
7651
- } else if (unit >= 56320 && unit <= 57343) throw new GSwapSDKError("TYPED_DATA_UNPAIRED_SURROGATE", { what });
7652
- }
7653
- return textEncoder.encode(value);
7654
- }
7655
- /** EIP-55: a mixed-case address must carry the right checksum; all-lower / all-upper is accepted. */
7656
- function addressBytes(value) {
7657
- if (typeof value !== "string" || !/^0x[0-9a-fA-F]{40}$/u.test(value)) throw new GSwapSDKError("TYPED_DATA_BAD_ADDRESS", {});
7658
- const digits = value.slice(2);
7659
- if (/[A-F]/u.test(digits) && /[a-f]/u.test(digits)) {
7660
- const hash = keccak_256(textEncoder.encode(digits.toLowerCase()));
7661
- for (let index = 0; index < 40; index += 1) {
7662
- const nibble = hash[index >> 1] >> (index % 2 === 0 ? 4 : 0) & 15;
7663
- const char = digits.charAt(index);
7664
- if (char !== (nibble >= 8 ? char.toUpperCase() : char.toLowerCase())) throw new GSwapSDKError("TYPED_DATA_BAD_CHECKSUM", {});
7665
- }
7666
- }
7667
- return hexToBytes(value);
7668
- }
7669
- /** The encoder for a non-struct, non-array type, or undefined when `base` is not one. */
7670
- function leafEncoder(base) {
7671
- if (base === "string") return (value) => keccak_256(utf8(value, "string"));
7672
- if (base === "bytes") return (value) => keccak_256(toBytes(value, "bytes"));
7673
- if (base === "bool") return (value) => {
7674
- if (typeof value !== "boolean") throw new GSwapSDKError("TYPED_DATA_NOT_BOOLEAN", {});
7675
- return bigintToWord(value ? 1n : 0n);
7676
- };
7677
- if (base === "address") return (value) => concat([/* @__PURE__ */ new Uint8Array(12), addressBytes(value)]);
7678
- const fixed = /^bytes([1-9][0-9]?)$/u.exec(base);
7679
- if (fixed !== null) {
7680
- const width = Number(fixed[1]);
7681
- if (width > 32 || String(width) !== fixed[1]) return void 0;
7682
- return (value) => {
7683
- const bytes = toBytes(value, base);
7684
- if (bytes.length !== width) throw new GSwapSDKError("TYPED_DATA_BYTES_LENGTH", {
7685
- type: base,
7686
- width
7687
- });
7688
- return concat([bytes, new Uint8Array(32 - width)]);
7689
- };
7690
- }
7691
- const integer = /^(u?)int([1-9][0-9]*)$/u.exec(base);
7692
- if (integer !== null) {
7693
- const width = Number(integer[2]);
7694
- if (width % 8 !== 0 || width > 256 || String(width) !== integer[2]) return void 0;
7695
- const signed = integer[1] === "";
7696
- const lower = signed ? -(1n << BigInt(width - 1)) : 0n;
7697
- const upper = signed ? (1n << BigInt(width - 1)) - 1n : (1n << BigInt(width)) - 1n;
7698
- return (value) => {
7699
- const number = toBigInt(value, base);
7700
- if (number < lower || number > upper) throw new GSwapSDKError("TYPED_DATA_OUT_OF_BOUNDS", { type: base });
7701
- return bigintToWord(number);
7702
- };
7703
- }
7704
- }
7705
- function parseType(type) {
7706
- const match = TYPE_SYNTAX.exec(type);
7707
- if (match === null) throw new GSwapSDKError("TYPED_DATA_UNSUPPORTED_TYPE", { type });
7708
- const dimensions = [...(match[2] ?? "").matchAll(/\[([0-9]*)\]/gu)].map((dimension) => dimension[1] === "" ? -1 : Number(dimension[1]));
7709
- return {
7710
- base: match[1],
7711
- dimensions
7712
- };
7713
- }
7714
- function parseFields(typeName, rawFields) {
7715
- if (!Array.isArray(rawFields)) throw new GSwapSDKError("TYPED_DATA_FIELDS_NOT_LIST", { typeName });
7716
- const seen = /* @__PURE__ */ new Set();
7717
- return rawFields.map((raw) => {
7718
- const field = raw;
7719
- if (typeof (field === null || field === void 0 ? void 0 : field.name) !== "string" || typeof field.type !== "string") throw new GSwapSDKError("TYPED_DATA_FIELD_MALFORMED", { typeName });
7720
- if (!IDENTIFIER.test(field.name)) throw new GSwapSDKError("TYPED_DATA_INVALID_FIELD_NAME", { name: field.name });
7721
- if (seen.has(field.name)) throw new GSwapSDKError("TYPED_DATA_DUPLICATE_FIELD", {
7722
- name: field.name,
7723
- typeName
7724
- });
7725
- seen.add(field.name);
7726
- const parsed = parseType(field.type);
7727
- const base = parsed.base === "uint" || parsed.base === "int" ? `${parsed.base}256` : parsed.base;
7728
- return {
7729
- name: field.name,
7730
- type: base + field.type.slice(parsed.base.length)
7731
- };
7732
- });
7733
- }
7734
- /**
7735
- * Builds the struct encoder for a set of type definitions: validates the type graph (unknown or
7736
- * circular references, duplicate fields) and infers the primary type as the single type no other
7737
- * type references, exactly like ethers' `TypedDataEncoder`.
7738
- */
7739
- function createStructEncoder(rawTypes) {
7740
- const names = Object.keys(rawTypes);
7741
- const fields = /* @__PURE__ */ new Map();
7742
- for (const name of names) {
7743
- if (!IDENTIFIER.test(name)) throw new GSwapSDKError("TYPED_DATA_INVALID_TYPE_NAME", { name });
7744
- fields.set(name, parseFields(name, rawTypes[name]));
7745
- }
7746
- const children = /* @__PURE__ */ new Map();
7747
- const parents = /* @__PURE__ */ new Map();
7748
- for (const name of names) {
7749
- children.set(name, /* @__PURE__ */ new Set());
7750
- parents.set(name, /* @__PURE__ */ new Set());
7751
- }
7752
- for (const [name, list] of fields) for (const field of list) {
7753
- var _children$get, _parents$get;
7754
- const { base } = parseType(field.type);
7755
- if (base === name) throw new GSwapSDKError("TYPED_DATA_CIRCULAR_TYPE", { typeName: name });
7756
- if (leafEncoder(base) !== void 0) continue;
7757
- if (!fields.has(base)) throw new GSwapSDKError("TYPED_DATA_UNKNOWN_FIELD_TYPE", {
7758
- type: base,
7759
- typeName: name
7760
- });
7761
- (_children$get = children.get(name)) === null || _children$get === void 0 || _children$get.add(base);
7762
- (_parents$get = parents.get(base)) === null || _parents$get === void 0 || _parents$get.add(name);
7763
- }
7764
- const roots = names.filter((name) => {
7765
- var _parents$get2;
7766
- return ((_parents$get2 = parents.get(name)) === null || _parents$get2 === void 0 ? void 0 : _parents$get2.size) === 0;
7767
- });
7768
- if (roots.length === 0) throw new GSwapSDKError("TYPED_DATA_NO_PRIMARY_TYPE", {});
7769
- if (roots.length > 1) throw new GSwapSDKError("TYPED_DATA_AMBIGUOUS_PRIMARY_TYPE", { roots: roots.join(", ") });
7770
- const primaryType = roots[0];
7771
- const visiting = /* @__PURE__ */ new Set();
7772
- const checked = /* @__PURE__ */ new Set();
7773
- const checkCircular = (name) => {
7774
- if (visiting.has(name)) throw new GSwapSDKError("TYPED_DATA_CIRCULAR_TYPE", { typeName: name });
7775
- if (checked.has(name)) return;
7776
- visiting.add(name);
7777
- for (const child of children.get(name) ?? []) checkCircular(child);
7778
- visiting.delete(name);
7779
- checked.add(name);
7780
- };
7781
- checkCircular(primaryType);
7782
- const dependencies = (name, found) => {
7783
- for (const child of children.get(name) ?? []) if (!found.has(child)) {
7784
- found.add(child);
7785
- dependencies(child, found);
7786
- }
7787
- return found;
7788
- };
7789
- const encodeOne = (name) => `${name}(${(fields.get(name) ?? []).map((field) => `${field.type} ${field.name}`).join(",")})`;
7790
- const typeHashes = /* @__PURE__ */ new Map();
7791
- for (const name of names) {
7792
- const sorted = [...dependencies(name, /* @__PURE__ */ new Set())].filter((dep) => dep !== name).sort();
7793
- typeHashes.set(name, keccak_256(textEncoder.encode([name, ...sorted].map(encodeOne).join(""))));
7794
- }
7795
- const encodeValue = (parsed, value, depth) => {
7796
- const dimension = parsed.dimensions[parsed.dimensions.length - 1 - depth];
7797
- if (dimension !== void 0) {
7798
- if (!Array.isArray(value)) throw new GSwapSDKError("TYPED_DATA_ARRAY_EXPECTED", { type: parsed.base });
7799
- if (dimension !== -1 && dimension !== value.length) throw new GSwapSDKError("TYPED_DATA_ARRAY_LENGTH_MISMATCH", {});
7800
- return keccak_256(concat(value.map((item) => encodeValue(parsed, item, depth + 1))));
7801
- }
7802
- const leaf = leafEncoder(parsed.base);
7803
- if (leaf !== void 0) return leaf(value);
7804
- return hashStruct(parsed.base, value);
7805
- };
7806
- function hashStruct(typeName, value) {
7807
- const list = fields.get(typeName);
7808
- const typeHash = typeHashes.get(typeName);
7809
- if (list === void 0 || typeHash === void 0) throw new Error(`Internal error: no encoder for struct ${typeName}`);
7810
- if (value === null || typeof value !== "object" || Array.isArray(value)) throw new GSwapSDKError("TYPED_DATA_STRUCT_NOT_OBJECT", { typeName });
7811
- const record = value;
7812
- const words = list.map((field) => {
7813
- if (!Object.prototype.hasOwnProperty.call(record, field.name)) throw new GSwapSDKError("TYPED_DATA_MISSING_FIELD", {
7814
- typeName,
7815
- field: field.name
7816
- });
7817
- return encodeValue(parseType(field.type), record[field.name], 0);
7818
- });
7819
- return keccak_256(concat([typeHash, ...words]));
7820
- }
7821
- return {
7822
- primaryType,
7823
- hashStruct
7824
- };
7825
- }
7826
- function hashDomain(domain) {
7827
- if (domain === null || typeof domain !== "object" || Array.isArray(domain)) throw new GSwapSDKError("TYPED_DATA_DOMAIN_NOT_OBJECT", {});
7828
- for (const key of Object.keys(domain)) if (!DOMAIN_FIELDS.some((field) => field.name === key)) throw new GSwapSDKError("TYPED_DATA_INVALID_DOMAIN_KEY", { key: JSON.stringify(key) });
7829
- return createStructEncoder({ EIP712Domain: DOMAIN_FIELDS.filter((field) => Object.prototype.hasOwnProperty.call(domain, field.name)) }).hashStruct("EIP712Domain", domain);
7830
- }
7831
- /**
7832
- * The EIP-712 digest `keccak256(0x1901 || domainSeparator || hashStruct(primaryType, message))`
7833
- * that a wallet signs for `eth_signTypedData_v4`. The primary type is inferred, as ethers does.
7834
- *
7835
- * @throws GSwapSDKError (`TYPED_DATA_*`) for any type graph or value it cannot encode
7836
- * exactly (see the module note on strictness).
7837
- */
7838
- function hashTypedData(payload) {
7839
- const domainHash = hashDomain(payload.domain);
7840
- const encoder = createStructEncoder(payload.types);
7841
- const structHash = encoder.hashStruct(encoder.primaryType, payload.message);
7842
- return keccak_256(concat([
7843
- Uint8Array.of(25, 1),
7844
- domainHash,
7845
- structHash
7846
- ]));
7847
- }
7848
- /**
7849
- * Signs a dApp's EIP-712 typed-data approval (the wallet's `signTypedMessage` path), replacing
7850
- * `new ethers.Wallet(key).signTypedData(domain, types, message)`. `types` must already have
7851
- * `EIP712Domain` removed (`stripEip712DomainType`).
7852
- *
7853
- * @returns `0x` + r + s + v with `v` = `0x1b`/`0x1c` (27/28): ethers' serialized signature form,
7854
- * recoverable with `verifyTypedData` / `eth_signTypedData_v4` verifiers. Rejects (never throws
7855
- * synchronously) on invalid input.
7856
- */
7857
- async function signTypedDataPayload(privateKey, payload) {
7858
- const digest = hashTypedData(payload);
7859
- return withPrivateKey(privateKey, (key) => `0x${signHash(key, digest)}`);
7860
- }
7861
-
7862
- //#endregion
7863
- //#region src/utils/axios_requestor.ts
7864
- function isRecord$1(value) {
7865
- return typeof value === "object" && value !== null && !Array.isArray(value);
7866
- }
7867
- /**
7868
- * An axios interceptor may resolve something that is not a response at all (for example a string
7869
- * for ECONNREFUSED). That is a transport failure, not an HTTP answer.
7870
- */
7871
- function isAxiosLikeResponse(value) {
7872
- return isRecord$1(value) && typeof value["status"] === "number";
7873
- }
7874
- function readAxiosHeader(headers, name) {
7875
- if (!isRecord$1(headers)) return null;
7876
- const getter = headers["get"];
7877
- if (typeof getter === "function") {
7878
- const value = getter.call(headers, name);
7879
- return typeof value === "string" ? value : null;
7880
- }
7881
- const match = Object.entries(headers).find(([key]) => key.toLowerCase() === name.toLowerCase());
7882
- return typeof (match === null || match === void 0 ? void 0 : match[1]) === "string" ? match[1] : null;
7883
- }
7884
- function toHttpResponse(answer, url) {
7885
- if (!isAxiosLikeResponse(answer)) throw new GSwapSDKError("TRANSPORT_ERROR", { url }, { url });
7886
- const { status, data, headers } = answer;
7887
- return {
7888
- ok: status >= 200 && status < 300,
7889
- status,
7890
- json: () => Promise.resolve(data),
7891
- text: () => Promise.resolve(typeof data === "string" ? data : JSON.stringify(data)),
7892
- headers: { get: (name) => readAxiosHeader(headers, name) }
7893
- };
7894
- }
7895
- function headersFor(init, omit) {
7896
- const omitted = new Set(omit.map((name) => name.toLowerCase()));
7897
- const raw = init === null || init === void 0 ? void 0 : init.headers;
7898
- const entries = raw === void 0 ? [] : raw instanceof Headers ? [...raw.entries()] : Array.isArray(raw) ? raw.map(([name, value]) => [String(name), String(value)]) : Object.entries(raw).map(([name, value]) => [name, String(value)]);
7899
- return Object.fromEntries(entries.filter(([name]) => !omitted.has(name.toLowerCase())));
7900
- }
7901
- /**
7902
- * An SDK {@link HttpRequestor} over an axios instance, so the SDK's requests travel through the
7903
- * app's own base URL, interceptors and error handling.
7904
- *
7905
- * A POST's string body is parsed back to the object the SDK serialized it from (axios serializes
7906
- * again, and an interceptor may read the object); a GET goes through `instance.get`. The answer
7907
- * is an HTTP response whose `json()`/`text()` read `response.data`. An answer that is not an
7908
- * axios response is a `TRANSPORT_ERROR` rejection, and so is a rejection that means no HTTP
7909
- * answer arrived (the original error is `cause`): any rejection with `rejectOnHttpError: false`
7910
- * (every status resolves there), an axios error without a `response` with `true`. Any other
7911
- * rejection (an interceptor's error for a 4xx, say) is rethrown unchanged, and so is an abort.
7912
- *
7913
- * Pair it with `ChainGateway`'s `dexBackendBaseUrl: ''` when the instance owns the base URL.
7914
- *
7915
- * @example
7916
- * ```ts
7917
- * const submitGateway = new ChainGateway({
7918
- * dexBackendBaseUrl: '',
7919
- * httpRequestor: httpRequestorFromAxios(chainGatewayInstance, { rejectOnHttpError: true }),
7920
- * });
7921
- * ```
7922
- */
7923
- function httpRequestorFromAxios(instance, options) {
7924
- const omit = options.omitHeaders ?? ["content-type"];
7925
- return async (uri, init) => {
7926
- const signal = (init === null || init === void 0 ? void 0 : init.signal) ?? void 0;
7927
- const config = {
7928
- headers: headersFor(init, omit),
7929
- ...signal ? { signal } : {},
7930
- ...options.rejectOnHttpError ? {} : { validateStatus: () => true }
7931
- };
7932
- const isPost = ((init === null || init === void 0 ? void 0 : init.method) ?? "GET").toUpperCase() === "POST";
7933
- const body = isPost && typeof (init === null || init === void 0 ? void 0 : init.body) === "string" ? JSON.parse(init.body) : void 0;
7934
- let answer;
7935
- try {
7936
- answer = await (isPost ? instance.post(uri, body, config) : instance.get(uri, config));
7937
- } catch (error) {
7938
- if ((signal === null || signal === void 0 ? void 0 : signal.aborted) === true || !isTransportRejection(error, options.rejectOnHttpError)) throw error;
7939
- throw new GSwapSDKError("TRANSPORT_ERROR", { url: uri }, {
7940
- url: uri,
7941
- cause: error
7942
- });
7943
- }
7944
- return toHttpResponse(answer, uri);
7945
- };
7946
- }
7947
- /**
7948
- * Whether an axios rejection means no HTTP answer arrived. With `rejectOnHttpError: false` every
7949
- * status resolves, so any rejection is one. With `true`, a 4xx/5xx rejects too, so only an axios
7950
- * error that carries no `response` is (an interceptor's own error is an app answer and passes
7951
- * through; an app whose interceptors turn network errors into their own class names it in
7952
- * `ChainGatewayOptions.isTransportFailure`).
7953
- */
7954
- function isTransportRejection(error, rejectOnHttpError) {
7955
- if (!rejectOnHttpError) return true;
7956
- return isRecord$1(error) && error["isAxiosError"] === true && error["response"] === void 0;
7957
- }
7958
-
7959
- //#endregion
7960
- //#region src/utils/ethers_provider.ts
7961
- function isRecord(value) {
7962
- return typeof value === "object" && value !== null && !Array.isArray(value);
7963
- }
7964
- function isTypedDataTypes(value) {
7965
- return isRecord(value) && Object.values(value).every((fields) => Array.isArray(fields) && fields.every((field) => isRecord(field) && typeof field["name"] === "string" && typeof field["type"] === "string"));
7966
- }
7967
- function malformed(method) {
7968
- return new GSwapSDKError("MALFORMED_WALLET_REQUEST", { method });
7969
- }
7970
- /** Splits `eth_signTypedData_v4` params (`[account, JSON payload]`) into what ethers' signer takes. */
7971
- function readTypedDataRequest(params) {
7972
- const raw = params === null || params === void 0 ? void 0 : params[1];
7973
- let payload;
7974
- try {
7975
- payload = typeof raw === "string" ? JSON.parse(raw) : raw;
7976
- } catch {
7977
- throw malformed("eth_signTypedData_v4");
7978
- }
7979
- if (!isRecord(payload)) throw malformed("eth_signTypedData_v4");
7980
- const { domain, types, message } = payload;
7981
- if (!isRecord(domain) || !isTypedDataTypes(types) || !isRecord(message)) throw malformed("eth_signTypedData_v4");
7982
- return {
7983
- domain,
7984
- types: stripEip712DomainType(types),
7985
- message
7986
- };
7987
- }
7988
- /**
7989
- * An EIP-1193 provider over an ethers v6 provider, for {@link BrowserWalletSigner}: apps whose
7990
- * wallet connection is an ethers `BrowserProvider` sign through the very ethers calls they always
7991
- * made, with no hand-written shim.
7992
- *
7993
- * - `eth_chainId` is `provider.send('eth_chainId', [])`: the wallet's LIVE chain. Never
7994
- * `provider.getNetwork()`, which ethers caches and does not invalidate on `chainChanged`, so a
7995
- * user who switches networks after connecting would sign against a stale chainId.
7996
- * - `eth_signTypedData_v4` parses the `[account, JSON]` params and calls
7997
- * `signer.signTypedData(domain, types, message)` with `EIP712Domain` removed from `types`.
7998
- * - `personal_sign` is `options.personalSign(message)` when given, else `signer.signMessage`.
7999
- * - Anything else is `UNSUPPORTED_WALLET_METHOD`; a malformed request is
8000
- * `MALFORMED_WALLET_REQUEST`; a missing provider is `WALLET_UNAVAILABLE`.
8001
- *
8002
- * `provider` may be `null`/`undefined` (nothing connected): a request that needs it then fails
8003
- * with `WALLET_UNAVAILABLE` rather than at construction, so a signer can be built before the
8004
- * connection state is known.
8005
- *
8006
- * @example
8007
- * ```ts
8008
- * const signer = new BrowserWalletSigner(eip1193ProviderFromEthers(browserProvider), account, {
8009
- * scheme: 'eip712',
8010
- * });
8011
- * ```
8012
- */
8013
- function eip1193ProviderFromEthers(provider, options = {}) {
8014
- const connected = () => {
8015
- if (provider === null || provider === void 0) throw new GSwapSDKError("WALLET_UNAVAILABLE", {});
8016
- return provider;
8017
- };
8018
- return { request: async ({ method, params }) => {
8019
- switch (method) {
8020
- case "eth_chainId": return connected().send("eth_chainId", []);
8021
- case "eth_signTypedData_v4": {
8022
- const wallet = connected();
8023
- const { domain, types, message } = readTypedDataRequest(params);
8024
- return (await wallet.getSigner()).signTypedData(domain, types, message);
8025
- }
8026
- case "personal_sign": {
8027
- const message = params === null || params === void 0 ? void 0 : params[0];
8028
- if (typeof message !== "string") throw malformed("personal_sign");
8029
- if (options.personalSign !== void 0) return options.personalSign(message);
8030
- return (await connected().getSigner()).signMessage(message);
8031
- }
8032
- default: throw new GSwapSDKError("UNSUPPORTED_WALLET_METHOD", { method });
8033
- }
8034
- } };
8035
- }
8036
-
8037
6228
  //#endregion
8038
6229
  //#region src/utils/confirmation_state.ts
8039
6230
  /** The state of every confirmation code (a typo is caught by the all-codes test). */
@@ -8074,43 +6265,6 @@ function isConfirmationTimeout(error) {
8074
6265
  return confirmationStateOf(error) === "timeout";
8075
6266
  }
8076
6267
 
8077
- //#endregion
8078
- //#region src/utils/fetch_failure.ts
8079
- /**
8080
- * True when a rejection of a `fetch`-based requestor means NO HTTP ANSWER ARRIVED: the network
8081
- * failed, the connection was reset, the host did not resolve, CORS blocked it. For a write that
8082
- * is not a refusal (the request may have been queued), so this is the default
8083
- * `ChainGatewayOptions.isTransportFailure` for a requestor that wraps `fetch` and rejects with
8084
- * its own errors.
8085
- *
8086
- * - Yes: a `TypeError` (what `fetch` rejects with in every engine: `Failed to fetch`, `Load failed`,
8087
- * `NetworkError when attempting to fetch resource`, undici's `fetch failed`), recognised by name
8088
- * too so one from another realm (an extension page, an iframe) still counts, and a
8089
- * `DOMException` named `NetworkError`.
8090
- * - No: an abort (`AbortError`) or a timeout (`TimeoutError`). The SDK's own request layer knows
8091
- * whether its timer or the caller's signal aborted, and reports those itself
8092
- * (`REQUEST_TIMEOUT`, or the caller's own abort unchanged); claiming them here would turn the
8093
- * caller's cancel into "maybe queued".
8094
- * - No: anything else, including an SDK error and a value that is not an object.
8095
- *
8096
- * It never throws.
8097
- *
8098
- * @example
8099
- * ```ts
8100
- * new ChainGateway({ dexBackendBaseUrl, httpRequestor: galaFetch, isTransportFailure: isFetchTransportFailure });
8101
- * ```
8102
- */
8103
- function isFetchTransportFailure(error) {
8104
- if (typeof error !== "object" || error === null) return false;
8105
- try {
8106
- const name = "name" in error ? error.name : void 0;
8107
- if (name === "AbortError" || name === "TimeoutError") return false;
8108
- return error instanceof TypeError || name === "TypeError" || name === "NetworkError";
8109
- } catch {
8110
- return false;
8111
- }
8112
- }
8113
-
8114
6268
  //#endregion
8115
6269
  //#region src/utils/failure_guards.ts
8116
6270
  /**
@@ -8298,5 +6452,5 @@ function classifySubmitEnvelope(envelope) {
8298
6452
  }
8299
6453
 
8300
6454
  //#endregion
8301
- export { ALL_FEE_TIERS, AppliedTradeError, Assets, BrowserWalletSigner, CanonicalSymbols, ChainGateway, DEFAULT_UNIQUE_KEY_PREFIX, DEX_FEE_METHODS, DRY_RUN_METHOD, DRY_RUN_UNIQUE_KEY_PREFIX, DexFees, EIP712_CAPABLE_METHODS, FAILURE_OUTCOMES, FEE_TIER, GC_MAX_TICK, GC_MIN_TICK, GSWAP_ENVIRONMENTS, GSWAP_ERROR_CATALOG, GSWAP_ERROR_PHASES, GSWAP_ERROR_PHASE_BY_CODE, GSwap, GSwapSDKError, GalaWalletSigner, GdexStreamError, GdexStreamService, INVALID_PRIVATE_KEY_REASONS, NOT_QUEUED_FINAL, PENDING_SWAPS_STORAGE_KEY, PENDING_SWAP_EXPIRY_MS, PENDING_SWAP_FATES, PENDING_SWAP_PRUNE_REASONS, PendingSwapStore, Pools, Positions, PrivateKeySigner, Quoting, SHADOW_FAILURE_KINDS, SHADOW_SUBMIT_CODES, SIGNING_FAILURE_REASONS, SUBMISSION_OUTCOME_UNKNOWN_REASONS, SubmittedTransaction, Swaps, Symbols, TICK_SPACING_BY_FEE, UPDATE_REQUIRED_STATUS, alignTickDown, alignTickDownOrNaN, alignTickUp, alignTickUpOrNaN, assertCanonicalPair, assertCanonicalV2Dto, assertTickRange, assertUniqueKey, buildAddLiquidityDto, buildCollectPositionFeesDto, buildCreatePoolDto, buildDryRunWrapper, buildEIP712Payload, buildRemoveLiquidityDto, buildRequestTradeDto, calculatePersonalSignPrefix, canonicalDecimalString, chainErrorKeyOf, classification, classifyFailure, classifyShadowSubmitError, classifyShadowSubmitStatus, classifySubmitEnvelope, compositeKeyOf, confirmAppliedTrade, confirmationStateOf, createLazyPrivateKeySigner, createUniqueKey, eip1193ProviderFromEthers, eip712DomainFor, ethAddressOfPrivateKey, ethereumAddressOfAccount, fixpointPrefixFor, frameSlot0, generateEIP712Types, getObjectProperty, getPayloadToSign, getStringProperty, gswapErrorTemplates, hashTypedData, httpRequestorFromAxios, isConfirmationAborted, isConfirmationTimeout, isCurrentContractDexMessage, isDexFeeMethod, isFetchTransportFailure, isGSwapError, isGSwapErrorLike, isGdexLiquidityEvent, isNoMarket, isPreSendError, isProposableTradingSymbol, isRelayableRefusal, isShadowError, isSqrtPriceInRange, isSubmitExecuted, isSubmitQueued, isSubmitRejected, isTickInRange, isUninterpretedAnswer, isUniqueKeyConsumed, isUnprovenAcknowledgement, isValidUniqueKey, isWalletUserRejection, isWriteView, mayHaveBeenQueued, normalizePrivateKey, notSentClassification, orderSymbols, parseJson, parseTokenClassKey, pendingSwapFateFor, pendingSwapFateOfOutcome, personalSignPrefixFor, queryTokenOf, rawTokenOf, readOrderedSymbols, readShadowSubmitOutcome, renderGSwapErrorTemplate, resolvePersonalSignChainDtoPayload, resolveUniqueKeyPrefix, serialize, signChainDtoHash, signChainDtoNative, signChainDtoPersonalSign, signNative, signPersonalMessage, signRawObject, signSerializedNative, signTypedDataPayload, sqrtPriceToTick, storageAdapterFromChromeStorage, stripEip712DomainType, swapRouteFromQuote, tickFromPrice, tickToPrice, tickToSqrtPrice, toChainDto, toFailureView, tokenClassDollarKey, tradingSymbolOf, tryParseCompositeKey, tryParseTokenClassKey, validateFee, validateNumericAmount, validatePriceValues, validateTickRange, validateTickSpacing, validateTokenDecimals, validateWalletAddress, walletDeclineCode, webStorageAdapter, withPrivateKey };
6455
+ export { ALL_FEE_TIERS, AppliedTradeError, Assets, CanonicalSymbols, ChainGateway, DEX_FEE_METHODS, DRY_RUN_METHOD, DRY_RUN_UNIQUE_KEY_PREFIX, DexFees, FEE_TIER, GC_MAX_TICK, GC_MIN_TICK, GSWAP_ENVIRONMENTS, GSWAP_ERROR_CATALOG, GSWAP_ERROR_PHASE_BY_CODE, GSwap, GSwapSDKError, GdexStreamError, GdexStreamService, Leaderboard, MARKET_PRICE_BATCH_LIMIT, MARKET_PRICE_QUERY_BYTES, Market, NOT_QUEUED_FINAL, PENDING_SWAPS_STORAGE_KEY, PENDING_SWAP_EXPIRY_MS, PENDING_SWAP_FATES, PENDING_SWAP_PRUNE_REASONS, PRICE_SERIES_TIMEFRAMES, PendingSwapStore, Pools, Positions, Quoting, SHADOW_FAILURE_KINDS, SHADOW_SUBMIT_CODES, SUBMISSION_OUTCOME_UNKNOWN_REASONS, SubmittedTransaction, Swaps, Symbols, TICK_SPACING_BY_FEE, UPDATE_REQUIRED_STATUS, alignTickDown, alignTickDownOrNaN, alignTickUp, alignTickUpOrNaN, assertCanonicalPair, assertCanonicalV2Dto, assertTickRange, buildAddLiquidityDto, buildCollectPositionFeesDto, buildCreatePoolDto, buildDryRunWrapper, buildRemoveLiquidityDto, buildRequestTradeDto, chainErrorKeyOf, classification, classifyFailure, classifyShadowSubmitError, classifyShadowSubmitStatus, classifySubmitEnvelope, confirmAppliedTrade, confirmationStateOf, frameSlot0, getObjectProperty, getStringProperty, gswapErrorTemplates, isConfirmationAborted, isConfirmationTimeout, isCurrentContractDexMessage, isDexFeeMethod, isGSwapError, isGdexLiquidityEvent, isNoMarket, isPreSendError, isProposableTradingSymbol, isRelayableRefusal, isShadowError, isSqrtPriceInRange, isSubmitExecuted, isSubmitQueued, isSubmitRejected, isTickInRange, isUninterpretedAnswer, isUniqueKeyConsumed, isUnprovenAcknowledgement, mayHaveBeenQueued, notSentClassification, orderSymbols, parseTokenClassKey, pendingSwapFateFor, pendingSwapFateOfOutcome, priceBatches, queryTokenOf, rawTokenOf, readOrderedSymbols, readShadowSubmitOutcome, sqrtPriceToTick, storageAdapterFromChromeStorage, swapRouteFromQuote, tickFromPrice, tickToPrice, tickToSqrtPrice, toChainDto, tradingSymbolOf, validateFee, validateNumericAmount, validatePriceValues, validateTickRange, validateTickSpacing, validateTokenDecimals, validateWalletAddress, webStorageAdapter };
8302
6456
  //# sourceMappingURL=index.mjs.map