@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.
- package/README.md +42 -8
- package/dist/browser/classes/apply_results.js +1 -1
- package/dist/browser/classes/apply_results.js.map +1 -1
- package/dist/browser/classes/assets.js +1 -1
- package/dist/browser/classes/assets.js.map +1 -1
- package/dist/browser/classes/dex_fees.js +1 -1
- package/dist/browser/classes/dex_fees.js.map +1 -1
- package/dist/browser/classes/gateway.js +1 -1
- package/dist/browser/classes/gateway.js.map +1 -1
- package/dist/browser/classes/gdex_stream.js +1 -1
- package/dist/browser/classes/gdex_stream.js.map +1 -1
- package/dist/browser/classes/gswap.js +1 -1
- package/dist/browser/classes/gswap.js.map +1 -1
- package/dist/browser/classes/gswap_error_catalog.js +2 -0
- package/dist/browser/classes/gswap_error_catalog.js.map +1 -0
- package/dist/browser/classes/gswap_error_phase_table.js +2 -0
- package/dist/browser/classes/gswap_error_phase_table.js.map +1 -0
- package/dist/browser/classes/gswap_sdk_error.js +1 -1
- package/dist/browser/classes/gswap_sdk_error.js.map +1 -1
- package/dist/browser/classes/http_client.js +1 -1
- package/dist/browser/classes/http_client.js.map +1 -1
- package/dist/browser/classes/leaderboard.js +2 -0
- package/dist/browser/classes/leaderboard.js.map +1 -0
- package/dist/browser/classes/market.js +2 -0
- package/dist/browser/classes/market.js.map +1 -0
- package/dist/browser/classes/pending_swap_fate.js.map +1 -1
- package/dist/browser/classes/pending_swaps.js +1 -1
- package/dist/browser/classes/pending_swaps.js.map +1 -1
- package/dist/browser/classes/pools.js +1 -1
- package/dist/browser/classes/pools.js.map +1 -1
- package/dist/browser/classes/positions.js +1 -1
- package/dist/browser/classes/positions.js.map +1 -1
- package/dist/browser/classes/quoting.js +1 -1
- package/dist/browser/classes/quoting.js.map +1 -1
- package/dist/browser/classes/shadow_submit.js.map +1 -1
- package/dist/browser/classes/storage_adapters.js.map +1 -1
- package/dist/browser/classes/submitted_transaction.js +1 -1
- package/dist/browser/classes/submitted_transaction.js.map +1 -1
- package/dist/browser/classes/swaps.js +1 -1
- package/dist/browser/classes/swaps.js.map +1 -1
- package/dist/browser/classes/symbols.js +1 -1
- package/dist/browser/classes/symbols.js.map +1 -1
- package/dist/browser/index.js +1 -1
- package/dist/browser/types/env.js.map +1 -1
- package/dist/browser/types/fees.js.map +1 -1
- package/dist/browser/types/market.js +2 -0
- package/dist/browser/types/market.js.map +1 -0
- package/dist/browser/utils/classification.js.map +1 -1
- package/dist/browser/utils/confirmation_schedule.js.map +1 -1
- package/dist/browser/utils/confirmation_state.js +1 -1
- package/dist/browser/utils/confirmation_state.js.map +1 -1
- package/dist/browser/utils/debug.js.map +1 -1
- package/dist/browser/utils/dto_builders.js +1 -1
- package/dist/browser/utils/dto_builders.js.map +1 -1
- package/dist/browser/utils/failure_classification.js +1 -1
- package/dist/browser/utils/failure_classification.js.map +1 -1
- package/dist/browser/utils/failure_guards.js +1 -1
- package/dist/browser/utils/failure_guards.js.map +1 -1
- package/dist/browser/utils/lenient.js +1 -1
- package/dist/browser/utils/lenient.js.map +1 -1
- package/dist/browser/utils/ordering.js +1 -1
- package/dist/browser/utils/ordering.js.map +1 -1
- package/dist/browser/utils/reconnection.js +1 -1
- package/dist/browser/utils/reconnection.js.map +1 -1
- package/dist/browser/utils/resolve_pre_send.js +2 -0
- package/dist/browser/utils/resolve_pre_send.js.map +1 -0
- package/dist/browser/utils/server_message.js.map +1 -1
- package/dist/browser/utils/shadow_errors.js +1 -1
- package/dist/browser/utils/shadow_errors.js.map +1 -1
- package/dist/browser/utils/submit_state.js.map +1 -1
- package/dist/browser/utils/tick_align.js.map +1 -1
- package/dist/browser/utils/tick_math.js.map +1 -1
- package/dist/browser/utils/tick_range.js.map +1 -1
- package/dist/browser/utils/ticks.js +1 -1
- package/dist/browser/utils/ticks.js.map +1 -1
- package/dist/browser/utils/validation.js +1 -1
- package/dist/browser/utils/validation.js.map +1 -1
- package/dist/index.cjs +1033 -2925
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +439 -1604
- package/dist/index.d.ts +439 -1604
- package/dist/index.mjs +964 -2810
- package/dist/index.mjs.map +1 -1
- package/package.json +36 -60
- package/dist/browser/classes/error_catalog.js +0 -2
- package/dist/browser/classes/error_catalog.js.map +0 -1
- package/dist/browser/classes/error_phase_table.js +0 -2
- package/dist/browser/classes/error_phase_table.js.map +0 -1
- package/dist/browser/classes/error_phases.js +0 -2
- package/dist/browser/classes/error_phases.js.map +0 -1
- package/dist/browser/classes/signers.js +0 -2
- package/dist/browser/classes/signers.js.map +0 -1
- package/dist/browser/utils/axios_requestor.js +0 -2
- package/dist/browser/utils/axios_requestor.js.map +0 -1
- package/dist/browser/utils/chain_dto_signing.js +0 -3
- package/dist/browser/utils/chain_dto_signing.js.map +0 -1
- package/dist/browser/utils/class_key.js +0 -2
- package/dist/browser/utils/class_key.js.map +0 -1
- package/dist/browser/utils/eip712.js +0 -4
- package/dist/browser/utils/eip712.js.map +0 -1
- package/dist/browser/utils/error_brand.js +0 -2
- package/dist/browser/utils/error_brand.js.map +0 -1
- package/dist/browser/utils/ethers_provider.js +0 -2
- package/dist/browser/utils/ethers_provider.js.map +0 -1
- package/dist/browser/utils/failure_outcomes.js +0 -2
- package/dist/browser/utils/failure_outcomes.js.map +0 -1
- package/dist/browser/utils/failure_view.js +0 -2
- package/dist/browser/utils/failure_view.js.map +0 -1
- package/dist/browser/utils/fetch_failure.js +0 -2
- package/dist/browser/utils/fetch_failure.js.map +0 -1
- package/dist/browser/utils/pre_send.js +0 -2
- package/dist/browser/utils/pre_send.js.map +0 -1
- package/dist/browser/utils/signing.js +0 -3
- package/dist/browser/utils/signing.js.map +0 -1
- package/dist/browser/utils/transport.js +0 -2
- package/dist/browser/utils/transport.js.map +0 -1
- package/dist/browser/utils/typed_data.js +0 -2
- package/dist/browser/utils/typed_data.js.map +0 -1
- package/dist/browser/utils/unique_key.js +0 -2
- package/dist/browser/utils/unique_key.js.map +0 -1
- package/dist/browser/utils/unique_key_syntax.js +0 -2
- package/dist/browser/utils/unique_key_syntax.js.map +0 -1
- package/dist/browser/utils/wallet_rejection.js +0 -2
- package/dist/browser/utils/wallet_rejection.js.map +0 -1
- package/dist/browser/version.js +0 -2
- package/dist/browser/version.js.map +0 -1
- package/dist/browser/wallet.js +0 -2
- package/dist/browser/wallet.js.map +0 -1
- package/dist/wallet.cjs +0 -159
- package/dist/wallet.cjs.map +0 -1
- package/dist/wallet.d.cts +0 -74
- package/dist/wallet.d.ts +0 -74
- package/dist/wallet.mjs +0 -156
- package/dist/wallet.mjs.map +0 -1
- /package/dist/browser/_virtual/{_@oxc-project_runtime@0.151.0 → _@oxc-project_runtime@0.153.0}/helpers/esm/defineProperty.js +0 -0
- /package/dist/browser/_virtual/{_@oxc-project_runtime@0.151.0 → _@oxc-project_runtime@0.153.0}/helpers/esm/toPrimitive.js +0 -0
- /package/dist/browser/_virtual/{_@oxc-project_runtime@0.151.0 → _@oxc-project_runtime@0.153.0}/helpers/esm/toPropertyKey.js +0 -0
- /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/
|
|
99
|
-
/**
|
|
100
|
-
* The
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
*
|
|
202
|
-
*
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
*
|
|
433
|
-
*
|
|
434
|
-
*
|
|
435
|
-
*
|
|
436
|
-
*
|
|
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
|
-
|
|
384
|
+
TOKEN_RESOLUTION_FAILED: {
|
|
741
385
|
phase: "pre_send",
|
|
742
|
-
template: "
|
|
743
|
-
params: {}
|
|
386
|
+
template: "Token {token} could not be looked up; nothing was sent.",
|
|
387
|
+
params: { token: "string" }
|
|
744
388
|
},
|
|
745
|
-
|
|
389
|
+
MISSING_WALLET_ADDRESS: {
|
|
746
390
|
phase: "pre_send",
|
|
747
|
-
template: "No wallet
|
|
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
|
-
|
|
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: "
|
|
758
|
-
params: {
|
|
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
|
-
|
|
402
|
+
UNORDERABLE_SYMBOLS: {
|
|
761
403
|
phase: "pre_send",
|
|
762
|
-
template: "
|
|
763
|
-
params: {
|
|
404
|
+
template: "Unable to order trading symbols: expected a pair of symbols, got {received}.",
|
|
405
|
+
params: { received: "string" }
|
|
764
406
|
},
|
|
765
|
-
|
|
407
|
+
INVALID_TOKEN_CLASS_KEY: {
|
|
766
408
|
phase: "pre_send",
|
|
767
|
-
template: "
|
|
768
|
-
params: {
|
|
409
|
+
template: "Invalid token class key \"{tokenClassKey}\": expected a collection followed by category, type and additionalKey",
|
|
410
|
+
params: { tokenClassKey: "string" }
|
|
769
411
|
},
|
|
770
|
-
|
|
412
|
+
INCOMPLETE_TOKEN_CLASS_KEY: {
|
|
771
413
|
phase: "pre_send",
|
|
772
|
-
template: "
|
|
773
|
-
params: {
|
|
414
|
+
template: "Token class key \"{compositeKey}\" is missing {part}.",
|
|
415
|
+
params: {
|
|
416
|
+
compositeKey: "string",
|
|
417
|
+
part: "string"
|
|
418
|
+
}
|
|
774
419
|
},
|
|
775
|
-
|
|
420
|
+
INVALID_TRADING_SYMBOL: {
|
|
776
421
|
phase: "pre_send",
|
|
777
|
-
template: "Invalid
|
|
778
|
-
params: {
|
|
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
|
-
|
|
425
|
+
NO_VALID_TRADING_SYMBOL: {
|
|
781
426
|
phase: "pre_send",
|
|
782
|
-
template: "
|
|
427
|
+
template: "No valid trading symbol for token class {classKey}: \"{displaySymbol}\" is not an acceptable symbol and none is registered",
|
|
783
428
|
params: {
|
|
784
|
-
|
|
785
|
-
|
|
429
|
+
classKey: "string",
|
|
430
|
+
displaySymbol: "string"
|
|
786
431
|
}
|
|
787
432
|
},
|
|
788
|
-
|
|
433
|
+
TOKEN_SYMBOL_NOT_REGISTERED: {
|
|
789
434
|
phase: "pre_send",
|
|
790
|
-
template: "
|
|
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
|
-
|
|
438
|
+
SYMBOL_DISAGREES_WITH_REGISTRY: {
|
|
794
439
|
phase: "pre_send",
|
|
795
|
-
template: "
|
|
796
|
-
params: {
|
|
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
|
-
|
|
447
|
+
CREATE_POOL_NEEDS_CLASS_KEY: {
|
|
799
448
|
phase: "pre_send",
|
|
800
|
-
template: "
|
|
801
|
-
params: {
|
|
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
|
-
|
|
452
|
+
AMOUNT_IS_JS_NUMBER: {
|
|
804
453
|
phase: "pre_send",
|
|
805
|
-
template: "Invalid
|
|
806
|
-
params: {}
|
|
454
|
+
template: "Invalid {field}: use a decimal string or BigNumber, not a JavaScript number",
|
|
455
|
+
params: { field: "string" }
|
|
807
456
|
},
|
|
808
|
-
|
|
457
|
+
AMOUNT_NOT_FINITE: {
|
|
809
458
|
phase: "pre_send",
|
|
810
|
-
template: "Invalid {field}:
|
|
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
|
-
|
|
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: "
|
|
824
|
-
params: {
|
|
467
|
+
template: "Invalid {field}: must be positive (got {value})",
|
|
468
|
+
params: {
|
|
469
|
+
field: "string",
|
|
470
|
+
value: "string"
|
|
471
|
+
}
|
|
825
472
|
},
|
|
826
|
-
|
|
473
|
+
AMOUNT_NEGATIVE: {
|
|
827
474
|
phase: "pre_send",
|
|
828
|
-
template: "
|
|
829
|
-
params: {
|
|
475
|
+
template: "Invalid {field}: must not be negative (got {value})",
|
|
476
|
+
params: {
|
|
477
|
+
field: "string",
|
|
478
|
+
value: "string"
|
|
479
|
+
}
|
|
830
480
|
},
|
|
831
|
-
|
|
481
|
+
PRICE_VALUES_IS_JS_NUMBER: {
|
|
832
482
|
phase: "pre_send",
|
|
833
|
-
template: "
|
|
483
|
+
template: "Invalid price values: use decimal strings or BigNumber values, not JavaScript numbers",
|
|
834
484
|
params: {}
|
|
835
485
|
},
|
|
836
|
-
|
|
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
|
|
1286
|
-
*
|
|
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/
|
|
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
|
-
*
|
|
1308
|
-
*
|
|
1309
|
-
*
|
|
1310
|
-
*
|
|
1311
|
-
*
|
|
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
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
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
|
|
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
|
|
1609
|
-
* `boolean`, not a type guard (a view is not a `GSwapSDKError`); use
|
|
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
|
|
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
|
|
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
|
|
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" }, {
|
|
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:
|
|
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
|
|
2704
|
-
* callers (a registry read many swaps wait on), and only the shadow caller's
|
|
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
|
-
|
|
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
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
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
|
|
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` }, {
|
|
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/
|
|
2846
|
+
//#region src/classes/leaderboard.ts
|
|
3683
2847
|
/**
|
|
3684
|
-
*
|
|
3685
|
-
*
|
|
3686
|
-
*
|
|
3687
|
-
*
|
|
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
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
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
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
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
|
-
|
|
3716
|
-
category,
|
|
2983
|
+
name,
|
|
3717
2984
|
type,
|
|
3718
|
-
|
|
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
|
|
3723
|
-
*
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
*
|
|
3728
|
-
*
|
|
3729
|
-
*
|
|
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
|
-
*
|
|
3732
|
-
*
|
|
3733
|
-
*
|
|
3734
|
-
*
|
|
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
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
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
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
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
|
-
*
|
|
3755
|
-
*
|
|
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
|
|
3772
|
-
|
|
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
|
|
4143
|
-
throw new GSwapSDKError("REQUEST_FAILED", { operation: "pool" }, {
|
|
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" }, {
|
|
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
|
|
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
|
|
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 }, {
|
|
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" }, {
|
|
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" }, {
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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/
|
|
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
|
-
*
|
|
6547
|
-
*
|
|
6548
|
-
*
|
|
6549
|
-
*
|
|
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,
|
|
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
|