@gala-chain/gswap-sdk 1.2.0-rc.8 → 1.2.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 +25 -10
- package/dist/browser/classes/dex_fees.js.map +1 -1
- package/dist/browser/classes/gateway.js.map +1 -1
- package/dist/browser/classes/gdex_stream.js.map +1 -1
- package/dist/browser/classes/gswap.js.map +1 -1
- package/dist/browser/classes/gswap_sdk_error.js.map +1 -1
- package/dist/browser/classes/pending_swap_fate.js +2 -0
- package/dist/browser/classes/pending_swap_fate.js.map +1 -0
- package/dist/browser/classes/pending_swaps.js +1 -1
- package/dist/browser/classes/pending_swaps.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/shadow_submit.js +1 -1
- package/dist/browser/classes/shadow_submit.js.map +1 -1
- package/dist/browser/classes/signers.js.map +1 -1
- package/dist/browser/classes/storage_adapters.js +2 -0
- package/dist/browser/classes/storage_adapters.js.map +1 -0
- package/dist/browser/classes/submitted_transaction.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/utils/chain_dto_signing.js.map +1 -1
- package/dist/browser/utils/class_key.js +2 -0
- package/dist/browser/utils/class_key.js.map +1 -0
- package/dist/browser/utils/classification.js +2 -0
- package/dist/browser/utils/classification.js.map +1 -0
- package/dist/browser/utils/dto_builders.js +1 -1
- package/dist/browser/utils/dto_builders.js.map +1 -1
- package/dist/browser/utils/eip712.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 +2 -0
- package/dist/browser/utils/failure_guards.js.map +1 -0
- package/dist/browser/utils/ordering.js +1 -1
- package/dist/browser/utils/ordering.js.map +1 -1
- package/dist/browser/utils/tick_align.js +2 -0
- package/dist/browser/utils/tick_align.js.map +1 -0
- package/dist/browser/utils/tick_math.js +2 -0
- package/dist/browser/utils/tick_math.js.map +1 -0
- package/dist/browser/utils/tick_range.js +2 -0
- package/dist/browser/utils/tick_range.js.map +1 -0
- package/dist/browser/utils/ticks.js +1 -1
- package/dist/browser/utils/ticks.js.map +1 -1
- package/dist/browser/utils/transport.js +1 -1
- package/dist/browser/utils/transport.js.map +1 -1
- package/dist/browser/utils/validation.js +1 -1
- package/dist/browser/utils/validation.js.map +1 -1
- package/dist/browser/version.js +1 -1
- package/dist/browser/version.js.map +1 -1
- package/dist/index.cjs +641 -336
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +470 -186
- package/dist/index.d.ts +470 -186
- package/dist/index.mjs +636 -337
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dto_builders.js","names":[],"sources":["../../../src/utils/dto_builders.ts"],"sourcesContent":["import BigNumber from 'bignumber.js';\nimport { GSwapSDKError } from '../classes/gswap_sdk_error.js';\nimport type { NumericAmount, SwapAmount } from '../types/amounts.js';\nimport type {\n AddLiquidityDTO,\n CollectPositionFeesDTO,\n CreatePoolDTO,\n RemoveLiquidityDTO,\n RequestTradeDTO,\n} from '../types/v2_dtos.js';\nimport type { GalaChainTokenClassKey } from '../types/token.js';\nimport { assertTickRange } from './ticks.js';\nimport { assertUniqueKey } from './unique_key.js';\nimport {\n assertCanonicalPair,\n isProposableTradingSymbol,\n validateFee,\n validateNumericAmount,\n} from './validation.js';\n\n// The pure builders of every v2 write DTO. They validate, canonicalize decimals\n// (`BigNumber#toFixed`) and fix the key order, and nothing else: no network, no signer, no\n// clock, no randomness — the `uniqueKey` is an input. The `Swaps` and `Positions` services call\n// exactly these functions, so a DTO an app builds here (to show in an approval popup, to sign on\n// another device, to submit later) is the DTO the services would have built.\n//\n// Inputs are already CANONICAL: `token0 < token1` by plain string comparison, ticks and prices in\n// that orientation. Orienting a caller's pair (and inverting its ticks and price) is the\n// services' job; a reversed pair is refused (`NON_CANONICAL_PAIR`) rather than silently signed.\n\nconst decimalString = (amount: NumericAmount): string => new BigNumber(amount).toFixed();\n\n/** The pool a RequestTrade trades in, its fee tier, the `uniqueKey` and what is traded. */\nexport type RequestTradeInput = {\n /** Canonical pool symbols: `token0 < token1`. Take them from a quote verbatim. */\n token0: string;\n token1: string;\n fee: number;\n /** Which side of the pool is sold: `true` when the input token is `token0`. */\n tokenInIsToken0: boolean;\n uniqueKey: string;\n} & SwapAmount;\n\n/**\n * Builds the RequestTrade DTO: `exactIn` sells that much of the input token (`sell0Qty` or\n * `sell1Qty`), `exactOut` buys that much of the output token (`buy1Qty` or `buy0Qty`), plus the\n * side's slippage bound and an optional `priceLimit`. Key order: `token0, token1, fee, uniqueKey,\n * <quantity>, amountOutMinimum | amountInMaximum, priceLimit`.\n *\n * @example\n * ```ts\n * const dto = buildRequestTradeDto({\n * token0: quote.token0Symbol, token1: quote.token1Symbol,\n * tokenInIsToken0: quote.tokenInIsToken0, fee: quote.fee,\n * uniqueKey: createUniqueKey('galaswap-operation-'),\n * exactIn: '100', amountOutMinimum: '14',\n * });\n * ```\n */\nexport function buildRequestTradeDto(input: RequestTradeInput): RequestTradeDTO {\n validateFee(input.fee);\n assertCanonicalPair(input.token0, input.token1);\n assertUniqueKey(input.uniqueKey);\n const dto: Record<string, unknown> = {\n token0: input.token0,\n token1: input.token1,\n fee: input.fee,\n uniqueKey: input.uniqueKey,\n };\n if ('exactIn' in input) {\n const exactIn = input.exactIn;\n if (exactIn === undefined) {\n throw new GSwapSDKError('EXACT_AMOUNT_REQUIRED', { field: 'exactIn' });\n }\n validateNumericAmount(exactIn, 'exactIn');\n dto[input.tokenInIsToken0 ? 'sell0Qty' : 'sell1Qty'] = decimalString(exactIn);\n if (input.amountOutMinimum !== undefined) {\n validateNumericAmount(input.amountOutMinimum, 'amountOutMinimum', true);\n dto['amountOutMinimum'] = decimalString(input.amountOutMinimum);\n }\n } else {\n const exactOut = input.exactOut;\n if (exactOut === undefined) {\n throw new GSwapSDKError('EXACT_AMOUNT_REQUIRED', { field: 'exactOut' });\n }\n validateNumericAmount(exactOut, 'exactOut');\n dto[input.tokenInIsToken0 ? 'buy1Qty' : 'buy0Qty'] = decimalString(exactOut);\n if (input.amountInMaximum !== undefined) {\n validateNumericAmount(input.amountInMaximum, 'amountInMaximum', true);\n dto['amountInMaximum'] = decimalString(input.amountInMaximum);\n }\n }\n if (input.priceLimit !== undefined) {\n validateNumericAmount(input.priceLimit, 'priceLimit');\n dto['priceLimit'] = decimalString(input.priceLimit);\n }\n return dto as unknown as RequestTradeDTO;\n}\n\n/** A canonical pool position: the pair, fee tier, tick range and the attempt's `uniqueKey`. */\nexport interface PoolPositionInput {\n token0: string;\n token1: string;\n fee: number;\n tickLower: number;\n tickUpper: number;\n uniqueKey: string;\n}\n\nfunction assertPoolPosition(input: PoolPositionInput): void {\n validateFee(input.fee);\n assertCanonicalPair(input.token0, input.token1);\n assertTickRange(input.tickLower, input.tickUpper, input.fee);\n assertUniqueKey(input.uniqueKey);\n}\n\n/** Exactly one canonical deposit side. */\nexport type AddLiquidityInput = PoolPositionInput &\n (\n | { depositQuantityToken0: NumericAmount; depositQuantityToken1?: undefined }\n | { depositQuantityToken0?: undefined; depositQuantityToken1: NumericAmount }\n );\n\n/**\n * Builds the AddLiquidity DTO: exactly one of `depositQuantityToken0` / `depositQuantityToken1`\n * (the other side is derived on chain). Key order: `token0, token1, fee, tickLower, tickUpper,\n * uniqueKey, <deposit>`.\n */\nexport function buildAddLiquidityDto(input: AddLiquidityInput): AddLiquidityDTO {\n assertPoolPosition(input);\n const side = onlyOne(\n input.depositQuantityToken0,\n input.depositQuantityToken1,\n 'depositQuantityToken0',\n 'depositQuantityToken1',\n );\n if (side === undefined) {\n throw new GSwapSDKError('EXACTLY_ONE_OF_REQUIRED', {\n first: 'depositQuantityToken0',\n second: 'depositQuantityToken1',\n });\n }\n validateNumericAmount(side.amount, side.name);\n return {\n token0: input.token0,\n token1: input.token1,\n fee: input.fee,\n tickLower: input.tickLower,\n tickUpper: input.tickUpper,\n uniqueKey: input.uniqueKey,\n [side.name]: decimalString(side.amount),\n } as AddLiquidityDTO;\n}\n\n/** At most one canonical withdrawal side; neither closes the position. */\nexport type RemoveLiquidityInput = PoolPositionInput & {\n withdrawalQuantityToken0?: NumericAmount | undefined;\n withdrawalQuantityToken1?: NumericAmount | undefined;\n};\n\n/**\n * Builds the RemoveLiquidity DTO: at most one of `withdrawalQuantityToken0` /\n * `withdrawalQuantityToken1`; omitting both closes the position and sweeps its fees.\n */\nexport function buildRemoveLiquidityDto(input: RemoveLiquidityInput): RemoveLiquidityDTO {\n assertPoolPosition(input);\n const side = onlyOne(\n input.withdrawalQuantityToken0,\n input.withdrawalQuantityToken1,\n 'withdrawalQuantityToken0',\n 'withdrawalQuantityToken1',\n true,\n );\n if (side !== undefined) validateNumericAmount(side.amount, side.name);\n return {\n token0: input.token0,\n token1: input.token1,\n fee: input.fee,\n tickLower: input.tickLower,\n tickUpper: input.tickUpper,\n uniqueKey: input.uniqueKey,\n ...(side === undefined ? {} : { [side.name]: decimalString(side.amount) }),\n };\n}\n\n/** Builds the CollectPositionFees DTO (the contract sweeps all of a position's fees). */\nexport function buildCollectPositionFeesDto(input: PoolPositionInput): CollectPositionFeesDTO {\n assertPoolPosition(input);\n return {\n token0: input.token0,\n token1: input.token1,\n fee: input.fee,\n tickLower: input.tickLower,\n tickUpper: input.tickUpper,\n uniqueKey: input.uniqueKey,\n };\n}\n\n/** A CreatePool in canonical orientation, with exactly one starting price. */\nexport type CreatePoolInput = {\n token0Key: GalaChainTokenClassKey;\n token1Key: GalaChainTokenClassKey;\n /** The symbols to register; `token0Symbol < token1Symbol` and both proposable. */\n token0Symbol: string;\n token1Symbol: string;\n fee: number;\n /** Optional display name; omitted when undefined or empty. */\n name?: string | undefined;\n isPrivate?: boolean | undefined;\n privateAccess?: string[] | undefined;\n uniqueKey: string;\n} & (\n | { startingPrice: NumericAmount; startingSqrtPrice?: undefined }\n | { startingPrice?: undefined; startingSqrtPrice: NumericAmount }\n);\n\n/**\n * Builds the CreatePool DTO. Key order: `token0Key, token1Key, fee, startingPrice |\n * startingSqrtPrice, token0Symbol, token1Symbol, uniqueKey, name, isPrivate, privateAccess`.\n * Prices are those of the canonical pair (token1 per token0); a price quoted the other way round\n * is inverted by the caller before it gets here.\n */\nexport function buildCreatePoolDto(input: CreatePoolInput): CreatePoolDTO {\n validateFee(input.fee);\n assertUniqueKey(input.uniqueKey);\n assertCanonicalPair(input.token0Symbol, input.token1Symbol);\n for (const symbol of [input.token0Symbol, input.token1Symbol]) {\n if (!isProposableTradingSymbol(symbol)) {\n throw new GSwapSDKError('INVALID_TRADING_SYMBOL', { symbol });\n }\n }\n for (const key of [input.token0Key, input.token1Key]) assertClassKey(key);\n const price = onlyOne(\n input.startingPrice,\n input.startingSqrtPrice,\n 'startingPrice',\n 'startingSqrtPrice',\n );\n if (price === undefined) {\n throw new GSwapSDKError('EXACTLY_ONE_OF_REQUIRED', {\n first: 'startingPrice',\n second: 'startingSqrtPrice',\n });\n }\n if (typeof price.amount === 'number') {\n throw new GSwapSDKError('AMOUNT_IS_JS_NUMBER', { field: price.name });\n }\n const parsed = new BigNumber(price.amount);\n if (!parsed.isFinite() || parsed.isLessThanOrEqualTo(0)) {\n throw new GSwapSDKError('INVALID_STARTING_PRICE', { value: String(price.amount) });\n }\n return {\n token0Key: input.token0Key,\n token1Key: input.token1Key,\n fee: input.fee,\n [price.name]: parsed.toFixed(),\n token0Symbol: input.token0Symbol,\n token1Symbol: input.token1Symbol,\n uniqueKey: input.uniqueKey,\n // An empty name is \"no name\", as in frontend_v2's builder (its form yields '').\n ...(input.name === undefined || input.name === '' ? {} : { name: input.name }),\n ...(input.isPrivate === undefined ? {} : { isPrivate: input.isPrivate }),\n ...(input.privateAccess === undefined ? {} : { privateAccess: input.privateAccess }),\n } as CreatePoolDTO;\n}\n\nfunction assertClassKey(key: GalaChainTokenClassKey): void {\n for (const part of ['collection', 'category', 'type', 'additionalKey'] as const) {\n if (typeof key[part] !== 'string' || key[part].length === 0) {\n throw new GSwapSDKError('INCOMPLETE_TOKEN_CLASS_KEY', {\n compositeKey: [key.collection, key.category, key.type, key.additionalKey].join('$'),\n part,\n });\n }\n }\n}\n\n/**\n * Of two mutually exclusive fields, the one that is set (`undefined` when neither is);\n * both set is an error (`AT_MOST_ONE_OF_ALLOWED` or `EXACTLY_ONE_OF_REQUIRED`), and so is\n * neither unless `allowNeither`.\n */\nfunction onlyOne<T>(\n first: T | undefined,\n second: T | undefined,\n firstName: string,\n secondName: string,\n allowNeither = false,\n): { name: string; amount: T } | undefined {\n if (first !== undefined && second !== undefined) {\n throw allowNeither\n ? new GSwapSDKError('AT_MOST_ONE_OF_ALLOWED', { first: firstName, second: secondName })\n : new GSwapSDKError('EXACTLY_ONE_OF_REQUIRED', { first: firstName, second: secondName });\n }\n if (first !== undefined) return { name: firstName, amount: first };\n if (second !== undefined) return { name: secondName, amount: second };\n return undefined;\n}\n\n/** The methods whose DTOs the builders produce. */\nexport type V2WriteMethod =\n 'RequestTrade' | 'AddLiquidity' | 'RemoveLiquidity' | 'CollectPositionFees' | 'CreatePool';\n\ntype DtoExpectation =\n 'string' | 'number' | 'boolean' | 'class_key' | 'string_array' | 'exactly_once';\n\nfunction invalidDto(\n method: string,\n field: string,\n expected: DtoExpectation,\n): GSwapSDKError<'INVALID_DTO'> {\n return new GSwapSDKError('INVALID_DTO', { method, field, expected });\n}\n\n/**\n * Rebuilds a DTO from its own fields with the builder of its method: the canonical form of the\n * DTO, with the same validation. Key order and decimal text are the builder's.\n */\nfunction rebuildV2Dto(\n method: V2WriteMethod,\n dto: Record<string, unknown>,\n): Record<string, unknown> {\n const text = (field: string): string => {\n const value = dto[field];\n if (typeof value !== 'string') throw invalidDto(method, field, 'string');\n return value;\n };\n const integer = (field: string): number => {\n const value = dto[field];\n if (typeof value !== 'number') throw invalidDto(method, field, 'number');\n return value;\n };\n const optionalText = (field: string): string | undefined =>\n dto[field] === undefined ? undefined : text(field);\n const classKey = (field: string): GalaChainTokenClassKey => {\n const value = dto[field];\n if (typeof value !== 'object' || value === null) throw invalidDto(method, field, 'class_key');\n const key = value as Record<string, unknown>;\n const part = (name: string): string => {\n if (typeof key[name] !== 'string') throw invalidDto(method, `${field}.${name}`, 'string');\n return key[name];\n };\n return {\n collection: part('collection'),\n category: part('category'),\n type: part('type'),\n additionalKey: part('additionalKey'),\n };\n };\n // The pool pair is not a field of CreatePool, so these are read per method, not up front.\n const base = () => ({ token0: text('token0'), token1: text('token1'), fee: integer('fee') });\n const uniqueKey = text('uniqueKey');\n const position = () => ({\n ...base(),\n uniqueKey,\n tickLower: integer('tickLower'),\n tickUpper: integer('tickUpper'),\n });\n switch (method) {\n case 'RequestTrade': {\n const qty = ['sell0Qty', 'sell1Qty', 'buy0Qty', 'buy1Qty'].filter(\n (f) => dto[f] !== undefined,\n );\n const [field] = qty;\n if (qty.length !== 1 || field === undefined) {\n throw invalidDto(method, 'sell0Qty|sell1Qty|buy0Qty|buy1Qty', 'exactly_once');\n }\n const tokenInIsToken0 = field === 'sell0Qty' || field === 'buy1Qty';\n const shared = { ...base(), uniqueKey, tokenInIsToken0 };\n const priceLimit = optionalText('priceLimit');\n return field.startsWith('sell')\n ? buildRequestTradeDto({\n ...shared,\n exactIn: text(field),\n amountOutMinimum: optionalText('amountOutMinimum'),\n priceLimit,\n })\n : buildRequestTradeDto({\n ...shared,\n exactOut: text(field),\n amountInMaximum: optionalText('amountInMaximum'),\n priceLimit,\n });\n }\n case 'AddLiquidity': {\n const token0Side = optionalText('depositQuantityToken0');\n const token1Side = optionalText('depositQuantityToken1');\n return buildAddLiquidityDto({\n ...position(),\n ...(token0Side === undefined\n ? { depositQuantityToken1: token1Side as string }\n : { depositQuantityToken0: token0Side }),\n });\n }\n case 'RemoveLiquidity':\n return buildRemoveLiquidityDto({\n ...position(),\n withdrawalQuantityToken0: optionalText('withdrawalQuantityToken0'),\n withdrawalQuantityToken1: optionalText('withdrawalQuantityToken1'),\n });\n case 'CollectPositionFees':\n return buildCollectPositionFeesDto(position());\n case 'CreatePool': {\n const price = optionalText('startingPrice');\n const sqrtPrice = optionalText('startingSqrtPrice');\n const privateAccess = dto['privateAccess'];\n if (\n privateAccess !== undefined &&\n (!Array.isArray(privateAccess) || privateAccess.some((entry) => typeof entry !== 'string'))\n ) {\n throw invalidDto(method, 'privateAccess', 'string_array');\n }\n if (dto['isPrivate'] !== undefined && typeof dto['isPrivate'] !== 'boolean') {\n throw invalidDto(method, 'isPrivate', 'boolean');\n }\n return buildCreatePoolDto({\n token0Key: classKey('token0Key'),\n token1Key: classKey('token1Key'),\n token0Symbol: text('token0Symbol'),\n token1Symbol: text('token1Symbol'),\n fee: integer('fee'),\n uniqueKey,\n name: optionalText('name'),\n isPrivate: dto['isPrivate'],\n privateAccess: privateAccess as string[] | undefined,\n ...(price === undefined\n ? { startingSqrtPrice: sqrtPrice as string }\n : { startingPrice: price }),\n });\n }\n }\n}\n\n/**\n * Asserts that `dto` is EXACTLY what the builder of `method` produces for its own fields — the\n * same fields, canonical decimal text, builder key order, nothing extra (no `signature`). The\n * `submit*` methods use it so that what an app showed a user (a DTO it built) is byte for byte\n * what gets signed; a DTO edited by hand, or built elsewhere, is refused rather than signed.\n *\n * @throws GSwapSDKError `INVALID_DTO` or `DTO_NOT_CANONICAL`.\n */\nexport function assertCanonicalV2Dto(method: V2WriteMethod, dto: Record<string, unknown>): void {\n const rebuilt = rebuildV2Dto(method, dto);\n if (JSON.stringify(rebuilt) !== JSON.stringify(dto)) {\n throw new GSwapSDKError('DTO_NOT_CANONICAL', { method });\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"dto_builders.js","names":[],"sources":["../../../src/utils/dto_builders.ts"],"sourcesContent":["import BigNumber from 'bignumber.js';\nimport { GSwapSDKError } from '../classes/gswap_sdk_error.js';\nimport type { NumericAmount, SwapAmount } from '../types/amounts.js';\nimport type {\n AddLiquidityDTO,\n CollectPositionFeesDTO,\n CreatePoolDTO,\n RemoveLiquidityDTO,\n RequestTradeDTO,\n} from '../types/v2_dtos.js';\nimport type { GalaChainTokenClassKey } from '../types/token.js';\nimport { assertTickRange } from './ticks.js';\nimport { assertUniqueKey } from './unique_key.js';\nimport {\n assertCanonicalPair,\n isProposableTradingSymbol,\n validateFee,\n validateNumericAmount,\n} from './validation.js';\n\n// The pure builders of every v2 write DTO. They validate, canonicalize decimals\n// (`BigNumber#toFixed`) and fix the key order, and nothing else: no network, no signer, no\n// clock, no randomness — the `uniqueKey` is an input. The `Swaps` and `Positions` services call\n// exactly these functions, so a DTO an app builds here (to show in an approval popup, to sign on\n// another device, to submit later) is the DTO the services would have built.\n//\n// Inputs are already CANONICAL: `token0 < token1` by plain string comparison, ticks and prices in\n// that orientation. Orienting a caller's pair (and inverting its ticks and price) is the\n// services' job; a reversed pair is refused (`NON_CANONICAL_PAIR`) rather than silently signed.\n\nconst decimalString = (amount: NumericAmount): string => new BigNumber(amount).toFixed();\n\n/** The pool a RequestTrade trades in, its fee tier, the `uniqueKey` and what is traded. */\nexport type RequestTradeInput = {\n /** Canonical pool symbols: `token0 < token1`. Take them from a quote verbatim. */\n token0: string;\n token1: string;\n fee: number;\n /** Which side of the pool is sold: `true` when the input token is `token0`. */\n tokenInIsToken0: boolean;\n uniqueKey: string;\n} & SwapAmount;\n\n/**\n * Builds the RequestTrade DTO: `exactIn` sells that much of the input token (`sell0Qty` or\n * `sell1Qty`), `exactOut` buys that much of the output token (`buy1Qty` or `buy0Qty`), plus the\n * side's slippage bound and an optional `priceLimit`. Key order is\n * `token0, token1, fee, uniqueKey`, then the one quantity, then the slippage bound, then\n * `priceLimit`.\n *\n * @example\n * ```ts\n * const dto = buildRequestTradeDto({\n * token0: quote.token0Symbol, token1: quote.token1Symbol,\n * tokenInIsToken0: quote.tokenInIsToken0, fee: quote.fee,\n * uniqueKey: createUniqueKey('galaswap-operation-'),\n * exactIn: '100', amountOutMinimum: '14',\n * });\n * ```\n */\nexport function buildRequestTradeDto(input: RequestTradeInput): RequestTradeDTO {\n validateFee(input.fee);\n assertCanonicalPair(input.token0, input.token1);\n assertUniqueKey(input.uniqueKey);\n const dto: Record<string, unknown> = {\n token0: input.token0,\n token1: input.token1,\n fee: input.fee,\n uniqueKey: input.uniqueKey,\n };\n if ('exactIn' in input) {\n const exactIn = input.exactIn;\n if (exactIn === undefined) {\n throw new GSwapSDKError('EXACT_AMOUNT_REQUIRED', { field: 'exactIn' });\n }\n validateNumericAmount(exactIn, 'exactIn');\n dto[input.tokenInIsToken0 ? 'sell0Qty' : 'sell1Qty'] = decimalString(exactIn);\n if (input.amountOutMinimum !== undefined) {\n validateNumericAmount(input.amountOutMinimum, 'amountOutMinimum', true);\n dto['amountOutMinimum'] = decimalString(input.amountOutMinimum);\n }\n } else {\n const exactOut = input.exactOut;\n if (exactOut === undefined) {\n throw new GSwapSDKError('EXACT_AMOUNT_REQUIRED', { field: 'exactOut' });\n }\n validateNumericAmount(exactOut, 'exactOut');\n dto[input.tokenInIsToken0 ? 'buy1Qty' : 'buy0Qty'] = decimalString(exactOut);\n if (input.amountInMaximum !== undefined) {\n validateNumericAmount(input.amountInMaximum, 'amountInMaximum', true);\n dto['amountInMaximum'] = decimalString(input.amountInMaximum);\n }\n }\n if (input.priceLimit !== undefined) {\n validateNumericAmount(input.priceLimit, 'priceLimit');\n dto['priceLimit'] = decimalString(input.priceLimit);\n }\n return dto as unknown as RequestTradeDTO;\n}\n\n/** A canonical pool position: the pair, fee tier, tick range and the attempt's `uniqueKey`. */\nexport interface PoolPositionInput {\n token0: string;\n token1: string;\n fee: number;\n tickLower: number;\n tickUpper: number;\n uniqueKey: string;\n}\n\nfunction assertPoolPosition(input: PoolPositionInput): void {\n validateFee(input.fee);\n assertCanonicalPair(input.token0, input.token1);\n assertTickRange(input.tickLower, input.tickUpper, input.fee);\n assertUniqueKey(input.uniqueKey);\n}\n\n/** Exactly one canonical deposit side. */\nexport type AddLiquidityInput = PoolPositionInput &\n (\n | { depositQuantityToken0: NumericAmount; depositQuantityToken1?: undefined }\n | { depositQuantityToken0?: undefined; depositQuantityToken1: NumericAmount }\n );\n\n/**\n * Builds the AddLiquidity DTO: exactly one of `depositQuantityToken0` / `depositQuantityToken1`\n * (the other side is derived on chain). Key order: `token0, token1, fee, tickLower, tickUpper,\n * uniqueKey, <deposit>`.\n */\nexport function buildAddLiquidityDto(input: AddLiquidityInput): AddLiquidityDTO {\n assertPoolPosition(input);\n const side = onlyOne(\n input.depositQuantityToken0,\n input.depositQuantityToken1,\n 'depositQuantityToken0',\n 'depositQuantityToken1',\n );\n if (side === undefined) {\n throw new GSwapSDKError('EXACTLY_ONE_OF_REQUIRED', {\n first: 'depositQuantityToken0',\n second: 'depositQuantityToken1',\n });\n }\n validateNumericAmount(side.amount, side.name);\n return {\n token0: input.token0,\n token1: input.token1,\n fee: input.fee,\n tickLower: input.tickLower,\n tickUpper: input.tickUpper,\n uniqueKey: input.uniqueKey,\n [side.name]: decimalString(side.amount),\n } as AddLiquidityDTO;\n}\n\n/** At most one canonical withdrawal side; neither closes the position. */\nexport type RemoveLiquidityInput = PoolPositionInput & {\n withdrawalQuantityToken0?: NumericAmount | undefined;\n withdrawalQuantityToken1?: NumericAmount | undefined;\n};\n\n/**\n * Builds the RemoveLiquidity DTO: at most one of `withdrawalQuantityToken0` /\n * `withdrawalQuantityToken1`; omitting both closes the position and sweeps its fees.\n */\nexport function buildRemoveLiquidityDto(input: RemoveLiquidityInput): RemoveLiquidityDTO {\n assertPoolPosition(input);\n const side = onlyOne(\n input.withdrawalQuantityToken0,\n input.withdrawalQuantityToken1,\n 'withdrawalQuantityToken0',\n 'withdrawalQuantityToken1',\n true,\n );\n if (side !== undefined) validateNumericAmount(side.amount, side.name);\n return {\n token0: input.token0,\n token1: input.token1,\n fee: input.fee,\n tickLower: input.tickLower,\n tickUpper: input.tickUpper,\n uniqueKey: input.uniqueKey,\n ...(side === undefined ? {} : { [side.name]: decimalString(side.amount) }),\n };\n}\n\n/** Builds the CollectPositionFees DTO (the contract sweeps all of a position's fees). */\nexport function buildCollectPositionFeesDto(input: PoolPositionInput): CollectPositionFeesDTO {\n assertPoolPosition(input);\n return {\n token0: input.token0,\n token1: input.token1,\n fee: input.fee,\n tickLower: input.tickLower,\n tickUpper: input.tickUpper,\n uniqueKey: input.uniqueKey,\n };\n}\n\n/** A CreatePool in canonical orientation, with exactly one starting price. */\nexport type CreatePoolInput = {\n token0Key: GalaChainTokenClassKey;\n token1Key: GalaChainTokenClassKey;\n /** The symbols to register; `token0Symbol < token1Symbol` and both proposable. */\n token0Symbol: string;\n token1Symbol: string;\n fee: number;\n /** Optional display name; omitted when undefined or empty. */\n name?: string | undefined;\n isPrivate?: boolean | undefined;\n privateAccess?: string[] | undefined;\n uniqueKey: string;\n} & (\n | { startingPrice: NumericAmount; startingSqrtPrice?: undefined }\n | { startingPrice?: undefined; startingSqrtPrice: NumericAmount }\n);\n\n/**\n * Builds the CreatePool DTO. Key order: `token0Key, token1Key, fee, startingPrice |\n * startingSqrtPrice, token0Symbol, token1Symbol, uniqueKey, name, isPrivate, privateAccess`.\n * Prices are those of the canonical pair (token1 per token0); a price quoted the other way round\n * is inverted by the caller before it gets here.\n */\nexport function buildCreatePoolDto(input: CreatePoolInput): CreatePoolDTO {\n validateFee(input.fee);\n assertUniqueKey(input.uniqueKey);\n assertCanonicalPair(input.token0Symbol, input.token1Symbol);\n for (const symbol of [input.token0Symbol, input.token1Symbol]) {\n if (!isProposableTradingSymbol(symbol)) {\n throw new GSwapSDKError('INVALID_TRADING_SYMBOL', { symbol });\n }\n }\n for (const key of [input.token0Key, input.token1Key]) assertClassKey(key);\n const price = onlyOne(\n input.startingPrice,\n input.startingSqrtPrice,\n 'startingPrice',\n 'startingSqrtPrice',\n );\n if (price === undefined) {\n throw new GSwapSDKError('EXACTLY_ONE_OF_REQUIRED', {\n first: 'startingPrice',\n second: 'startingSqrtPrice',\n });\n }\n if (typeof price.amount === 'number') {\n throw new GSwapSDKError('AMOUNT_IS_JS_NUMBER', { field: price.name });\n }\n const parsed = new BigNumber(price.amount);\n if (!parsed.isFinite() || parsed.isLessThanOrEqualTo(0)) {\n throw new GSwapSDKError('INVALID_STARTING_PRICE', { value: String(price.amount) });\n }\n return {\n token0Key: input.token0Key,\n token1Key: input.token1Key,\n fee: input.fee,\n [price.name]: parsed.toFixed(),\n token0Symbol: input.token0Symbol,\n token1Symbol: input.token1Symbol,\n uniqueKey: input.uniqueKey,\n // An empty name is \"no name\", as in frontend_v2's builder (its form yields '').\n ...(input.name === undefined || input.name === '' ? {} : { name: input.name }),\n ...(input.isPrivate === undefined ? {} : { isPrivate: input.isPrivate }),\n ...(input.privateAccess === undefined ? {} : { privateAccess: input.privateAccess }),\n } as CreatePoolDTO;\n}\n\nfunction assertClassKey(key: GalaChainTokenClassKey): void {\n for (const part of ['collection', 'category', 'type', 'additionalKey'] as const) {\n if (typeof key[part] !== 'string' || key[part].length === 0) {\n throw new GSwapSDKError('INCOMPLETE_TOKEN_CLASS_KEY', {\n compositeKey: [key.collection, key.category, key.type, key.additionalKey].join('$'),\n part,\n });\n }\n }\n}\n\n/**\n * Of two mutually exclusive fields, the one that is set (`undefined` when neither is);\n * both set is an error (`AT_MOST_ONE_OF_ALLOWED` or `EXACTLY_ONE_OF_REQUIRED`), and so is\n * neither unless `allowNeither`.\n */\nfunction onlyOne<T>(\n first: T | undefined,\n second: T | undefined,\n firstName: string,\n secondName: string,\n allowNeither = false,\n): { name: string; amount: T } | undefined {\n if (first !== undefined && second !== undefined) {\n throw allowNeither\n ? new GSwapSDKError('AT_MOST_ONE_OF_ALLOWED', { first: firstName, second: secondName })\n : new GSwapSDKError('EXACTLY_ONE_OF_REQUIRED', { first: firstName, second: secondName });\n }\n if (first !== undefined) return { name: firstName, amount: first };\n if (second !== undefined) return { name: secondName, amount: second };\n return undefined;\n}\n\n/** The methods whose DTOs the builders produce. */\nexport type V2WriteMethod =\n 'RequestTrade' | 'AddLiquidity' | 'RemoveLiquidity' | 'CollectPositionFees' | 'CreatePool';\n\ntype DtoExpectation =\n 'string' | 'number' | 'boolean' | 'class_key' | 'string_array' | 'exactly_once';\n\nfunction invalidDto(\n method: string,\n field: string,\n expected: DtoExpectation,\n): GSwapSDKError<'INVALID_DTO'> {\n return new GSwapSDKError('INVALID_DTO', { method, field, expected });\n}\n\n/**\n * Rebuilds a DTO from its own fields with the builder of its method: the canonical form of the\n * DTO, with the same validation. Key order and decimal text are the builder's.\n */\nfunction rebuildV2Dto(\n method: V2WriteMethod,\n dto: Record<string, unknown>,\n): Record<string, unknown> {\n const text = (field: string): string => {\n const value = dto[field];\n if (typeof value !== 'string') throw invalidDto(method, field, 'string');\n return value;\n };\n const integer = (field: string): number => {\n const value = dto[field];\n if (typeof value !== 'number') throw invalidDto(method, field, 'number');\n return value;\n };\n const optionalText = (field: string): string | undefined =>\n dto[field] === undefined ? undefined : text(field);\n const classKey = (field: string): GalaChainTokenClassKey => {\n const value = dto[field];\n if (typeof value !== 'object' || value === null) throw invalidDto(method, field, 'class_key');\n const key = value as Record<string, unknown>;\n const part = (name: string): string => {\n if (typeof key[name] !== 'string') throw invalidDto(method, `${field}.${name}`, 'string');\n return key[name];\n };\n return {\n collection: part('collection'),\n category: part('category'),\n type: part('type'),\n additionalKey: part('additionalKey'),\n };\n };\n // The pool pair is not a field of CreatePool, so these are read per method, not up front.\n const base = () => ({ token0: text('token0'), token1: text('token1'), fee: integer('fee') });\n const uniqueKey = text('uniqueKey');\n const position = () => ({\n ...base(),\n uniqueKey,\n tickLower: integer('tickLower'),\n tickUpper: integer('tickUpper'),\n });\n switch (method) {\n case 'RequestTrade': {\n const qty = ['sell0Qty', 'sell1Qty', 'buy0Qty', 'buy1Qty'].filter(\n (f) => dto[f] !== undefined,\n );\n const [field] = qty;\n if (qty.length !== 1 || field === undefined) {\n throw invalidDto(method, 'sell0Qty|sell1Qty|buy0Qty|buy1Qty', 'exactly_once');\n }\n const tokenInIsToken0 = field === 'sell0Qty' || field === 'buy1Qty';\n const shared = { ...base(), uniqueKey, tokenInIsToken0 };\n const priceLimit = optionalText('priceLimit');\n return field.startsWith('sell')\n ? buildRequestTradeDto({\n ...shared,\n exactIn: text(field),\n amountOutMinimum: optionalText('amountOutMinimum'),\n priceLimit,\n })\n : buildRequestTradeDto({\n ...shared,\n exactOut: text(field),\n amountInMaximum: optionalText('amountInMaximum'),\n priceLimit,\n });\n }\n case 'AddLiquidity': {\n const token0Side = optionalText('depositQuantityToken0');\n const token1Side = optionalText('depositQuantityToken1');\n return buildAddLiquidityDto({\n ...position(),\n ...(token0Side === undefined\n ? { depositQuantityToken1: token1Side as string }\n : { depositQuantityToken0: token0Side }),\n });\n }\n case 'RemoveLiquidity':\n return buildRemoveLiquidityDto({\n ...position(),\n withdrawalQuantityToken0: optionalText('withdrawalQuantityToken0'),\n withdrawalQuantityToken1: optionalText('withdrawalQuantityToken1'),\n });\n case 'CollectPositionFees':\n return buildCollectPositionFeesDto(position());\n case 'CreatePool': {\n const price = optionalText('startingPrice');\n const sqrtPrice = optionalText('startingSqrtPrice');\n const privateAccess = dto['privateAccess'];\n if (\n privateAccess !== undefined &&\n (!Array.isArray(privateAccess) || privateAccess.some((entry) => typeof entry !== 'string'))\n ) {\n throw invalidDto(method, 'privateAccess', 'string_array');\n }\n if (dto['isPrivate'] !== undefined && typeof dto['isPrivate'] !== 'boolean') {\n throw invalidDto(method, 'isPrivate', 'boolean');\n }\n return buildCreatePoolDto({\n token0Key: classKey('token0Key'),\n token1Key: classKey('token1Key'),\n token0Symbol: text('token0Symbol'),\n token1Symbol: text('token1Symbol'),\n fee: integer('fee'),\n uniqueKey,\n name: optionalText('name'),\n isPrivate: dto['isPrivate'],\n privateAccess: privateAccess as string[] | undefined,\n ...(price === undefined\n ? { startingSqrtPrice: sqrtPrice as string }\n : { startingPrice: price }),\n });\n }\n }\n}\n\n/**\n * Asserts that `dto` is EXACTLY what the builder of `method` produces for its own fields — the\n * same fields, canonical decimal text, builder key order, nothing extra (no `signature`). The\n * `submit*` methods use it so that what an app showed a user (a DTO it built) is byte for byte\n * what gets signed; a DTO edited by hand, or built elsewhere, is refused rather than signed.\n *\n * @throws GSwapSDKError `INVALID_DTO` or `DTO_NOT_CANONICAL`.\n */\nexport function assertCanonicalV2Dto(method: V2WriteMethod, dto: Record<string, unknown>): void {\n const rebuilt = rebuildV2Dto(method, dto);\n if (JSON.stringify(rebuilt) !== JSON.stringify(dto)) {\n throw new GSwapSDKError('DTO_NOT_CANONICAL', { method });\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eip712.js","names":[],"sources":["../../../src/utils/eip712.ts"],"sourcesContent":["/**\n * Genuine EIP-712 typed-data signing for the four v2 dex-contract operations proven (2026-09-16,\n * live bisection against stage — see defi-backend's `chain-findings-artifact` memory /\n * https://claude.ai/artifact/3N9MevSjoQdU7gqnVB3dfU) to recover the `client|` identity correctly\n * via `eth_signTypedData_v4`, unlike `personal_sign` (which only ever resolves the bare `eth|`\n * form — see `BrowserWalletSigner`'s own doc comment). Mirrors frontend_v2's\n * `src/Services/eip712Types.ts` + `readWriteUtilsV2.ts`'s `EIP712_FIELD_COMPLETERS` exactly —\n * ported here rather than shared as a package because gswap-sdk has no frontend_v2 dependency\n * and should not gain one for four small functions.\n */\nimport { GSwapSDKError } from '../classes/gswap_sdk_error.js';\n\nexport type EIP712Types = Record<string, Array<{ name: string; type: string }>>;\n\n/**\n * Derives the EIP-712 `types` for a DTO from its runtime shape.\n *\n * The generated `types` travel with the signed payload and are what BOTH sides hash, so they\n * must enumerate EVERY field the chain-side DTO will carry. GalaChain refuses any signature\n * whose `types` leave a DTO field uncovered (`assertNoUnsignedFields`, the fix for the\n * 2026-08-18 unsigned-field-injection drain); when that happens signature recovery fails\n * outright and the chain reports \"Missing signerPublicKey or signerAddress field in dto\".\n */\nexport function generateEIP712Types(\n typeName: string,\n params: Record<string, unknown>,\n): EIP712Types {\n const types: EIP712Types = {};\n types[typeName] = [];\n\n // Every `name` this is ever called with (the top-level `typeName`, or a nested struct's own\n // name) is unconditionally assigned `types[name] = []` on the line immediately before any\n // recursive call that could reach here; TypeScript still types `types[name]` as\n // possibly-undefined (index-signature access), so the guard below stays for real type safety\n // even though no current call path can actually trigger it.\n function fieldsFor(name: string): Array<{ name: string; type: string }> {\n const fields = types[name];\n /* c8 ignore next */\n if (!fields) throw new Error(`Missing EIP-712 type definition for ${name}`);\n return fields;\n }\n\n function addField(\n name: string,\n fieldValue: unknown,\n parentTypeName: string,\n onlyGetType = false,\n ): string | undefined {\n if (Array.isArray(fieldValue)) {\n const type =\n fieldValue.length > 0 ? addField(name, fieldValue[0], parentTypeName, true) : 'string';\n if (!onlyGetType) fieldsFor(parentTypeName).push({ name, type: (type ?? name) + '[]' });\n } else if (typeof fieldValue === 'object' && fieldValue !== null) {\n if (types[name]) {\n throw new GSwapSDKError('EIP712_NAME_COLLISION', { name });\n }\n types[name] = [];\n Object.entries(fieldValue).forEach(([key, value]) => {\n addField(key, value, name);\n });\n if (!onlyGetType) fieldsFor(parentTypeName).push({ name, type: name });\n } else {\n let eipType: string;\n switch (typeof fieldValue) {\n case 'string':\n eipType = 'string';\n break;\n case 'number':\n eipType = 'int256';\n break;\n case 'boolean':\n eipType = 'bool';\n break;\n case 'bigint':\n case 'symbol':\n case 'undefined':\n case 'function':\n case 'object':\n throw new GSwapSDKError('EIP712_UNSUPPORTED_VALUE', { valueType: typeof fieldValue });\n }\n if (onlyGetType) {\n return eipType;\n }\n fieldsFor(parentTypeName).push({ name, type: eipType });\n }\n return undefined;\n }\n\n Object.entries(params).forEach(([key, value]) => {\n addField(key, value, typeName);\n });\n\n return types;\n}\n\n/** The EIP-712 domain carried by a signed payload: GalaChain only uses `name` and `chainId`. */\nexport interface EIP712DomainValue {\n name: string;\n chainId: number;\n}\n\n/**\n * Builds the EIP-712 domain from the wallet's LIVE `eth_chainId` result, exactly like frontend_v2\n * (DEFI-1895): a wallet hashes whatever domain it is handed, and MetaMask refuses a `chainId` that\n * differs from its active network, so a hardcoded `1` breaks every wallet that is not on Mainnet.\n * defi-backend recovers the signer from the `domain` the payload carries (live-proven for chain\n * ids 1, 56 and 11155111), so following the wallet is correct on every network.\n *\n * @param chainId - The raw `eth_chainId` result: a `0x` hex string (the JSON-RPC form) or an integer.\n * @throws GSwapSDKError (`INVALID_CHAIN_ID`) when the wallet answers with anything else.\n */\nexport function eip712DomainFor(chainId: unknown): EIP712DomainValue {\n const parsed =\n typeof chainId === 'string' && /^0x[0-9a-fA-F]+$/u.test(chainId)\n ? Number(chainId)\n : typeof chainId === 'number'\n ? chainId\n : Number.NaN;\n if (!Number.isSafeInteger(parsed) || parsed <= 0) {\n throw new GSwapSDKError('INVALID_CHAIN_ID', {\n chainId:\n typeof chainId === 'string' || typeof chainId === 'number'\n ? String(chainId)\n : typeof chainId,\n });\n }\n return { name: 'ethereum', chainId: parsed };\n}\n\n/**\n * The four v2 operations proven to recover the signer correctly via genuine EIP-712: `RequestTrade`\n * never had the problem (one numeric field). `AddLiquidity`/`RemoveLiquidity`/\n * `CollectPositionFees` each need their `types` completed with a handful of fields their real\n * server-side DTO always carries — see the `complete*Fields` functions below. `CreatePool` is a\n * genuinely different, unsolved problem (nested `TokenClassKey` structs, not a flat\n * missing-field gap) and is NOT in this set — callers must keep using `personal-sign` for it.\n */\nexport const EIP712_CAPABLE_METHODS: ReadonlySet<string> = new Set([\n 'RequestTrade',\n 'AddLiquidity',\n 'RemoveLiquidity',\n 'CollectPositionFees',\n]);\n\n/**\n * Every `complete*Fields` function below shares the same two live-proven rules:\n * 1. Numeric completeness fields MUST be JS `number`, never numeric strings —\n * `generateEIP712Types` infers a field's Solidity type from `typeof`, and declaring one as\n * EIP-712 `string` instead of `int256` breaks recovery identically to omitting the field\n * entirely (verified live on AddLiquidity: `amount0Min: '0'` fails with the exact same\n * \"Missing signerPublicKey\" error as `amount0Min` being absent; `amount0Min: 0` succeeds).\n * 2. `positionId` has no such constraint — an empty string works, and does not target an\n * existing position (`positionId: ''` created a fresh position on AddLiquidity, not an add\n * to position id `''`; on RemoveLiquidity/CollectPositionFees the real position is still\n * resolved from `token0`/`token1`/`fee`/`tickLower`/`tickUpper`, not from this field).\n */\nfunction numericOr0(dto: Record<string, unknown>, field: string): number {\n return typeof dto[field] === 'number' ? dto[field] : 0;\n}\n\n/**\n * `AddLiquidity`'s real server-side DTO always carries `amount0Desired`/`amount0Min`/\n * `amount1Desired`/`amount1Min`/`positionId` once reconstructed, regardless of which deposit\n * convention the caller used (`Positions.addLiquidityByTicks` only ever sends\n * `depositQuantityToken0`/`depositQuantityToken1`). Proven empirically by bisection: none of the\n * five alone fixes recovery, all five together does (live 201, tx `407d9b24...`, block\n * `2429022`).\n */\nfunction completeAddLiquidityFields(dto: Record<string, unknown>): Record<string, unknown> {\n return {\n ...dto,\n amount0Desired: numericOr0(dto, 'amount0Desired'),\n amount0Min: numericOr0(dto, 'amount0Min'),\n amount1Desired: numericOr0(dto, 'amount1Desired'),\n amount1Min: numericOr0(dto, 'amount1Min'),\n positionId: dto['positionId'] ?? '',\n };\n}\n\n/**\n * `RemoveLiquidity`'s real server-side DTO always carries `amount`/`amount0Min`/`amount1Min`/\n * `positionId` — `Positions.removeLiquidity` only ever sends a `withdrawalQuantityTokenX` (or\n * neither, for a full close). Proven empirically: every subset of these four fails, all four\n * together succeeds (live, random-signer probe — recovery confirmed by the response moving from\n * \"Missing signerPublicKey\" to a genuine position-not-found 404).\n */\nfunction completeRemoveLiquidityFields(dto: Record<string, unknown>): Record<string, unknown> {\n return {\n ...dto,\n amount: numericOr0(dto, 'amount'),\n amount0Min: numericOr0(dto, 'amount0Min'),\n amount1Min: numericOr0(dto, 'amount1Min'),\n positionId: dto['positionId'] ?? '',\n };\n}\n\n/**\n * `CollectPositionFees`'s real server-side DTO always carries `amount0Requested`/\n * `amount1Requested`/`positionId` — `Positions.collectPositionFees` sends only the five identity\n * fields. Proven empirically the same way as `completeRemoveLiquidityFields`.\n */\nfunction completeCollectPositionFeesFields(dto: Record<string, unknown>): Record<string, unknown> {\n return {\n ...dto,\n amount0Requested: numericOr0(dto, 'amount0Requested'),\n amount1Requested: numericOr0(dto, 'amount1Requested'),\n positionId: dto['positionId'] ?? '',\n };\n}\n\nexport const EIP712_FIELD_COMPLETERS: Readonly<\n Record<string, (dto: Record<string, unknown>) => Record<string, unknown>>\n> = {\n AddLiquidity: completeAddLiquidityFields,\n RemoveLiquidity: completeRemoveLiquidityFields,\n CollectPositionFees: completeCollectPositionFeesFields,\n};\n\n/**\n * Completes a DTO's fields for EIP-712 signing (a no-op for methods with no known gap, e.g.\n * `RequestTrade`) and derives its `types`. Exported separately from the signing call itself so tests\n * can assert on the exact payload without needing a live provider.\n */\nexport function buildEIP712Payload(\n methodName: string,\n dto: Record<string, unknown>,\n): { completedDto: Record<string, unknown>; types: EIP712Types } {\n const completer = EIP712_FIELD_COMPLETERS[methodName];\n const completedDto = completer ? completer(dto) : dto;\n const types = generateEIP712Types(methodName, completedDto);\n return { completedDto, types };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"eip712.js","names":[],"sources":["../../../src/utils/eip712.ts"],"sourcesContent":["/**\n * Genuine EIP-712 typed-data signing for the four v2 dex-contract operations proven (2026-09-16,\n * live bisection against stage — see defi-backend's `chain-findings-artifact` memory /\n * https://claude.ai/artifact/3N9MevSjoQdU7gqnVB3dfU) to recover the `client|` identity correctly\n * via `eth_signTypedData_v4`, unlike `personal_sign` (which only ever resolves the bare `eth|`\n * form — see `BrowserWalletSigner`'s own doc comment). Mirrors frontend_v2's\n * `src/Services/eip712Types.ts` + `readWriteUtilsV2.ts`'s `EIP712_FIELD_COMPLETERS` exactly —\n * ported here rather than shared as a package because gswap-sdk has no frontend_v2 dependency\n * and should not gain one for four small functions.\n */\nimport { GSwapSDKError } from '../classes/gswap_sdk_error.js';\n\n/** EIP-712 `types`: each struct name mapped to its ordered `{ name, type }` fields. */\nexport type EIP712Types = Record<string, Array<{ name: string; type: string }>>;\n\n/**\n * Derives the EIP-712 `types` for a DTO from its runtime shape.\n *\n * The generated `types` travel with the signed payload and are what BOTH sides hash, so they\n * must enumerate EVERY field the chain-side DTO will carry. GalaChain refuses any signature\n * whose `types` leave a DTO field uncovered (`assertNoUnsignedFields`, the fix for the\n * 2026-08-18 unsigned-field-injection drain); when that happens signature recovery fails\n * outright and the chain reports \"Missing signerPublicKey or signerAddress field in dto\".\n */\nexport function generateEIP712Types(\n typeName: string,\n params: Record<string, unknown>,\n): EIP712Types {\n const types: EIP712Types = {};\n types[typeName] = [];\n\n // Every `name` this is ever called with (the top-level `typeName`, or a nested struct's own\n // name) is unconditionally assigned `types[name] = []` on the line immediately before any\n // recursive call that could reach here; TypeScript still types `types[name]` as\n // possibly-undefined (index-signature access), so the guard below stays for real type safety\n // even though no current call path can actually trigger it.\n function fieldsFor(name: string): Array<{ name: string; type: string }> {\n const fields = types[name];\n /* c8 ignore next */\n if (!fields) throw new Error(`Missing EIP-712 type definition for ${name}`);\n return fields;\n }\n\n function addField(\n name: string,\n fieldValue: unknown,\n parentTypeName: string,\n onlyGetType = false,\n ): string | undefined {\n if (Array.isArray(fieldValue)) {\n const type =\n fieldValue.length > 0 ? addField(name, fieldValue[0], parentTypeName, true) : 'string';\n if (!onlyGetType) fieldsFor(parentTypeName).push({ name, type: (type ?? name) + '[]' });\n } else if (typeof fieldValue === 'object' && fieldValue !== null) {\n if (types[name]) {\n throw new GSwapSDKError('EIP712_NAME_COLLISION', { name });\n }\n types[name] = [];\n Object.entries(fieldValue).forEach(([key, value]) => {\n addField(key, value, name);\n });\n if (!onlyGetType) fieldsFor(parentTypeName).push({ name, type: name });\n } else {\n let eipType: string;\n switch (typeof fieldValue) {\n case 'string':\n eipType = 'string';\n break;\n case 'number':\n eipType = 'int256';\n break;\n case 'boolean':\n eipType = 'bool';\n break;\n case 'bigint':\n case 'symbol':\n case 'undefined':\n case 'function':\n case 'object':\n throw new GSwapSDKError('EIP712_UNSUPPORTED_VALUE', { valueType: typeof fieldValue });\n }\n if (onlyGetType) {\n return eipType;\n }\n fieldsFor(parentTypeName).push({ name, type: eipType });\n }\n return undefined;\n }\n\n Object.entries(params).forEach(([key, value]) => {\n addField(key, value, typeName);\n });\n\n return types;\n}\n\n/** The EIP-712 domain carried by a signed payload: GalaChain only uses `name` and `chainId`. */\nexport interface EIP712DomainValue {\n name: string;\n chainId: number;\n}\n\n/**\n * Builds the EIP-712 domain from the wallet's LIVE `eth_chainId` result, exactly like frontend_v2\n * (DEFI-1895): a wallet hashes whatever domain it is handed, and MetaMask refuses a `chainId` that\n * differs from its active network, so a hardcoded `1` breaks every wallet that is not on Mainnet.\n * defi-backend recovers the signer from the `domain` the payload carries (live-proven for chain\n * ids 1, 56 and 11155111), so following the wallet is correct on every network.\n *\n * @param chainId - The raw `eth_chainId` result: a `0x` hex string (the JSON-RPC form) or an integer.\n * @throws GSwapSDKError (`INVALID_CHAIN_ID`) when the wallet answers with anything else.\n */\nexport function eip712DomainFor(chainId: unknown): EIP712DomainValue {\n const parsed =\n typeof chainId === 'string' && /^0x[0-9a-fA-F]+$/u.test(chainId)\n ? Number(chainId)\n : typeof chainId === 'number'\n ? chainId\n : Number.NaN;\n if (!Number.isSafeInteger(parsed) || parsed <= 0) {\n throw new GSwapSDKError('INVALID_CHAIN_ID', {\n chainId:\n typeof chainId === 'string' || typeof chainId === 'number'\n ? String(chainId)\n : typeof chainId,\n });\n }\n return { name: 'ethereum', chainId: parsed };\n}\n\n/**\n * The four v2 operations proven to recover the signer correctly via genuine EIP-712: `RequestTrade`\n * never had the problem (one numeric field). `AddLiquidity`/`RemoveLiquidity`/\n * `CollectPositionFees` each need their `types` completed with a handful of fields their real\n * server-side DTO always carries — see the `complete*Fields` functions below. `CreatePool` is a\n * genuinely different, unsolved problem (nested `TokenClassKey` structs, not a flat\n * missing-field gap) and is NOT in this set — callers must keep using `personal-sign` for it.\n */\nexport const EIP712_CAPABLE_METHODS: ReadonlySet<string> = new Set([\n 'RequestTrade',\n 'AddLiquidity',\n 'RemoveLiquidity',\n 'CollectPositionFees',\n]);\n\n/**\n * Every `complete*Fields` function below shares the same two live-proven rules:\n * 1. Numeric completeness fields MUST be JS `number`, never numeric strings —\n * `generateEIP712Types` infers a field's Solidity type from `typeof`, and declaring one as\n * EIP-712 `string` instead of `int256` breaks recovery identically to omitting the field\n * entirely (verified live on AddLiquidity: `amount0Min: '0'` fails with the exact same\n * \"Missing signerPublicKey\" error as `amount0Min` being absent; `amount0Min: 0` succeeds).\n * 2. `positionId` has no such constraint — an empty string works, and does not target an\n * existing position (`positionId: ''` created a fresh position on AddLiquidity, not an add\n * to position id `''`; on RemoveLiquidity/CollectPositionFees the real position is still\n * resolved from `token0`/`token1`/`fee`/`tickLower`/`tickUpper`, not from this field).\n */\nfunction numericOr0(dto: Record<string, unknown>, field: string): number {\n return typeof dto[field] === 'number' ? dto[field] : 0;\n}\n\n/**\n * `AddLiquidity`'s real server-side DTO always carries `amount0Desired`/`amount0Min`/\n * `amount1Desired`/`amount1Min`/`positionId` once reconstructed, regardless of which deposit\n * convention the caller used (`Positions.addLiquidityByTicks` only ever sends\n * `depositQuantityToken0`/`depositQuantityToken1`). Proven empirically by bisection: none of the\n * five alone fixes recovery, all five together does (live 201, tx `407d9b24...`, block\n * `2429022`).\n */\nfunction completeAddLiquidityFields(dto: Record<string, unknown>): Record<string, unknown> {\n return {\n ...dto,\n amount0Desired: numericOr0(dto, 'amount0Desired'),\n amount0Min: numericOr0(dto, 'amount0Min'),\n amount1Desired: numericOr0(dto, 'amount1Desired'),\n amount1Min: numericOr0(dto, 'amount1Min'),\n positionId: dto['positionId'] ?? '',\n };\n}\n\n/**\n * `RemoveLiquidity`'s real server-side DTO always carries `amount`/`amount0Min`/`amount1Min`/\n * `positionId` — `Positions.removeLiquidity` only ever sends a `withdrawalQuantityTokenX` (or\n * neither, for a full close). Proven empirically: every subset of these four fails, all four\n * together succeeds (live, random-signer probe — recovery confirmed by the response moving from\n * \"Missing signerPublicKey\" to a genuine position-not-found 404).\n */\nfunction completeRemoveLiquidityFields(dto: Record<string, unknown>): Record<string, unknown> {\n return {\n ...dto,\n amount: numericOr0(dto, 'amount'),\n amount0Min: numericOr0(dto, 'amount0Min'),\n amount1Min: numericOr0(dto, 'amount1Min'),\n positionId: dto['positionId'] ?? '',\n };\n}\n\n/**\n * `CollectPositionFees`'s real server-side DTO always carries `amount0Requested`/\n * `amount1Requested`/`positionId` — `Positions.collectPositionFees` sends only the five identity\n * fields. Proven empirically the same way as `completeRemoveLiquidityFields`.\n */\nfunction completeCollectPositionFeesFields(dto: Record<string, unknown>): Record<string, unknown> {\n return {\n ...dto,\n amount0Requested: numericOr0(dto, 'amount0Requested'),\n amount1Requested: numericOr0(dto, 'amount1Requested'),\n positionId: dto['positionId'] ?? '',\n };\n}\n\nexport const EIP712_FIELD_COMPLETERS: Readonly<\n Record<string, (dto: Record<string, unknown>) => Record<string, unknown>>\n> = {\n AddLiquidity: completeAddLiquidityFields,\n RemoveLiquidity: completeRemoveLiquidityFields,\n CollectPositionFees: completeCollectPositionFeesFields,\n};\n\n/**\n * Completes a DTO's fields for EIP-712 signing (a no-op for methods with no known gap, e.g.\n * `RequestTrade`) and derives its `types`. Exported separately from the signing call itself so tests\n * can assert on the exact payload without needing a live provider.\n */\nexport function buildEIP712Payload(\n methodName: string,\n dto: Record<string, unknown>,\n): { completedDto: Record<string, unknown>; types: EIP712Types } {\n const completer = EIP712_FIELD_COMPLETERS[methodName];\n const completedDto = completer ? completer(dto) : dto;\n const types = generateEIP712Types(methodName, completedDto);\n return { completedDto, types };\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{isWriteView as e,numberParamOf as t,stringParamOf as n,toFailureView as r}from"./failure_view.js";import{isCatalogCode as i}from"../classes/error_phase_table.js";
|
|
1
|
+
import{isWriteView as e,numberParamOf as t,stringParamOf as n,toFailureView as r}from"./failure_view.js";import{isCatalogCode as i}from"../classes/error_phase_table.js";import{NOT_QUEUED_FINAL as a,classification as o}from"./classification.js";const s=o(`rejected`),c=o(`unknown`),l=o(`not_queued`),u=o(`not_sent`),d=o(`invalid_input`),f=o(`transient`),p={REQUEST_TIMEOUT:f,TRANSPORT_ERROR:f,REQUEST_FAILED:f,SUBMISSION_OUTCOME_UNKNOWN:c,GATEWAY_REPORTED_ERROR:c,GATEWAY_DATA_MISSING:c,GATEWAY_MODE_INVALID:c,GATEWAY_SYNC_REQUEST_TRADE:c,MISSING_UNIQUE_KEY:c,UNIQUE_KEY_MISMATCH:c,SHADOW_NOT_QUEUED:a,METHOD_NOT_ALLOWED:l,SIGNATURE_INVALID:l,SIGNER_MISMATCH:l,DTO_INVALID:l,BOUNDS_VIOLATION:l,SYMBOL_CONFLICT:l,RATE_LIMITED:l,GATEWAY_REJECTED:l,CHAIN_REJECTED:s,INVALID_CHAIN_RESPONSE:f,INVALID_POSITION_RESPONSE:f,CONFIRMATION_TIMEOUT:c,CONFIRMATION_ABORTED:c,CONFIRMATION_FAILED:c,CONFIRMATION_MALFORMED:c,CONFIRMATION_KEY_MISMATCH:c,STREAM_DISCONNECTED:f,STREAM_REQUEST_TIMEOUT:f,STREAM_CONNECT_FAILED:f,STREAM_CLOSED_BEFORE_CONNECT:f,STREAM_CONNECTION_CLOSED:f,SIGNING_FAILED:u,KEY_PROVIDER_FAILED:u,TOKEN_RESOLUTION_FAILED:u,NO_WALLET_ADDRESS:d,NO_POOL_AVAILABLE:d,INSUFFICIENT_LIQUIDITY:d};function m(e){return n(e,`reason`)===`no_key_provided`?u:d}function h(e){return e.status??t(e,`status`)}function g(t){let n=h(t);return n===void 0?c:e(t)?n===429?l:n===408||n>=500?c:a:n===408||n===429||n>=500?f:d}function _(t){return e(t)?a:d}function v(e){let t=e.status;return t===void 0||t===409||t>=500?c:t===422?s:l}function y(e,t={}){let n=b(r(e));return t.afterQueued===!0&&n.outcome!==`rejected`?{...n,retryable:!1}:n}function b(e){if(e===void 0)return{...c};if(!i(e.code))return e.outcome===void 0?{...c}:e.retryable===!1?o(e.outcome,{retryable:!1}):o(e.outcome);let{code:t}=e;return t===`HTTP_ERROR`?{...g(e)}:t===`UPDATE_REQUIRED`?{..._(e)}:t===`CHAIN_DISPATCH_FAILED`?{...v(e)}:t===`INVALID_PRIVATE_KEY`?{...m(e)}:{...p[t]??d}}function x(e){return y(e).outcome===`unknown`}export{y as classifyFailure,x as mayHaveBeenQueued};
|
|
2
2
|
//# sourceMappingURL=failure_classification.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"failure_classification.js","names":[],"sources":["../../../src/utils/failure_classification.ts"],"sourcesContent":["import type { GSWAP_ERROR_CATALOG, GSwapErrorCode } from '../classes/error_catalog.js';\nimport type { GSwapSDKError } from '../classes/gswap_sdk_error.js';\nimport { isCatalogCode } from '../classes/error_phase_table.js';\nimport type { FailureOutcome } from './failure_outcomes.js';\nimport { isWriteView, numberParamOf, stringParamOf, toFailureView } from './failure_view.js';\nimport type { FailureView } from './failure_view.js';\n\n/** The result of {@link classifyFailure}. */\nexport interface FailureClassification {\n readonly outcome: FailureOutcome;\n /**\n * May the app offer the user Retry: repeat the operation as a NEW request (a fresh key for a\n * write) without any risk of applying it twice? Always `false` for `unknown` (confirm the same\n * key instead) and `invalid_input` (the same call fails again). It says a repeat is SAFE, not\n * that it will succeed, and it is the SDK's floor: an app may still withhold Retry (the\n * wallet and frontend_v2 do after a Dark swap: see `isShadowError`).\n */\n readonly retryable: boolean;\n}\n\n/**\n * The SDK's default Retry answer for each outcome. A decision that departs from it (a refusal the\n * gateway will repeat, `SHADOW_NOT_QUEUED`) is written out in the table below.\n */\nconst DEFAULT_RETRYABLE = {\n rejected: true,\n unknown: false,\n not_queued: true,\n not_sent: true,\n invalid_input: false,\n transient: true,\n} as const satisfies Record<FailureOutcome, boolean>;\n\n/**\n * Builds the classification the SDK itself gives an outcome, so an app that classifies a step of\n * its OWN (one that is not an SDK call) uses the SDK's type and its values instead of a\n * hard-coded `{ outcome, retryable }` literal. `retryable` is the SDK's default for that outcome\n * (`false` for `unknown` and `invalid_input`, `true` for the rest). Each call returns a new\n * object, safe to mutate or freeze.\n *\n * `options.retryable: false` WITHHOLDS Retry from an outcome that would otherwise allow it: the\n * SDK's own `not_queued` for a refusal the gateway will repeat is `classification('not_queued',\n * { retryable: false })` (also exported, frozen, as {@link NOT_QUEUED_FINAL}). An app may take\n * Retry away from an outcome but never grant it to one that cannot have it, so only `false` is\n * accepted.\n *\n * @example\n * ```ts\n * // The app refreshes a quote before it asks the SDK to sign; the refresh failed.\n * throw new AppSwapError(text, classification('not_sent'));\n * // The app refuses a request for a reason a repeat cannot fix.\n * throw new AppSwapError(text, classification('not_queued', { retryable: false }));\n * ```\n */\nexport function classification(\n outcome: FailureOutcome,\n options: { readonly retryable?: false } = {},\n): FailureClassification {\n return { outcome, retryable: options.retryable === false ? false : DEFAULT_RETRYABLE[outcome] };\n}\n\n/**\n * `classification('not_queued', { retryable: false })`: the gateway refused the write before\n * queueing it AND will refuse a repeat the same way (the version wall, a dark swap that did not\n * queue, an HTTP 4xx). Nothing is pending and Retry is not offered. It is frozen: use it as it is\n * (`classification: NOT_QUEUED_FINAL`) instead of spreading `classification('not_queued')` and\n * overriding `retryable`; `{ ...NOT_QUEUED_FINAL }` is a mutable copy.\n */\nexport const NOT_QUEUED_FINAL: Readonly<FailureClassification> = Object.freeze(\n classification('not_queued', { retryable: false }),\n);\n\n/**\n * `classification('not_sent')`: a step of the app's own failed before anything was signed or\n * sent (a quote refresh, a balance read), so nothing is pending and a repeat is safe.\n */\nexport function notSentClassification(): FailureClassification {\n return classification('not_sent');\n}\n\nconst REJECTED: FailureClassification = classification('rejected');\nconst UNKNOWN: FailureClassification = classification('unknown');\nconst NOT_QUEUED: FailureClassification = classification('not_queued');\nconst NOT_SENT: FailureClassification = classification('not_sent');\nconst INVALID_INPUT: FailureClassification = classification('invalid_input');\nconst TRANSIENT: FailureClassification = classification('transient');\n\n/**\n * The classification of every code whose meaning does not depend on the failing answer. A code\n * missing here is a compile error (unless it is `pre_send`, see {@link CodeTable}), so adding a\n * catalog code forces a decision.\n */\nconst BY_CODE = {\n // ----------------------------------------------------------------------------------- transport\n REQUEST_TIMEOUT: TRANSIENT,\n TRANSPORT_ERROR: TRANSIENT,\n REQUEST_FAILED: TRANSIENT,\n\n // ------------------------------------------------------------------------------- write: unknown\n // No answer was read, or a 2xx answer that does not prove the write was queued under the signed\n // key: it may have been applied.\n SUBMISSION_OUTCOME_UNKNOWN: UNKNOWN,\n GATEWAY_REPORTED_ERROR: UNKNOWN,\n GATEWAY_DATA_MISSING: UNKNOWN,\n GATEWAY_MODE_INVALID: UNKNOWN,\n GATEWAY_SYNC_REQUEST_TRADE: UNKNOWN,\n MISSING_UNIQUE_KEY: UNKNOWN,\n UNIQUE_KEY_MISMATCH: UNKNOWN,\n\n // --------------------------------------------------------------------- write: refused, not queued\n SHADOW_NOT_QUEUED: NOT_QUEUED_FINAL,\n METHOD_NOT_ALLOWED: NOT_QUEUED,\n SIGNATURE_INVALID: NOT_QUEUED,\n SIGNER_MISMATCH: NOT_QUEUED,\n DTO_INVALID: NOT_QUEUED,\n BOUNDS_VIOLATION: NOT_QUEUED,\n SYMBOL_CONFLICT: NOT_QUEUED,\n RATE_LIMITED: NOT_QUEUED,\n GATEWAY_REJECTED: NOT_QUEUED,\n\n // ----------------------------------------------------------------------- the chain's own verdict\n CHAIN_REJECTED: REJECTED,\n\n // ----------------------------------------------------------------------- reads of backend data\n INVALID_CHAIN_RESPONSE: TRANSIENT,\n INVALID_POSITION_RESPONSE: TRANSIENT,\n\n // -------------------------------------------------------------------------------- confirmation\n CONFIRMATION_TIMEOUT: UNKNOWN,\n CONFIRMATION_ABORTED: UNKNOWN,\n CONFIRMATION_FAILED: UNKNOWN,\n CONFIRMATION_MALFORMED: UNKNOWN,\n CONFIRMATION_KEY_MISMATCH: UNKNOWN,\n\n // ------------------------------------------------------------------------------ the realtime stream\n STREAM_DISCONNECTED: TRANSIENT,\n STREAM_REQUEST_TIMEOUT: TRANSIENT,\n STREAM_CONNECT_FAILED: TRANSIENT,\n STREAM_CLOSED_BEFORE_CONNECT: TRANSIENT,\n STREAM_CONNECTION_CLOSED: TRANSIENT,\n\n // ----------------------------------------------- pre-send: an app-supplied callback failed\n // The signer, the key provider or the token resolver threw before anything was transmitted. The\n // original error is `cause`. A repeat cannot apply anything twice.\n SIGNING_FAILED: NOT_SENT,\n KEY_PROVIDER_FAILED: NOT_SENT,\n TOKEN_RESOLUTION_FAILED: NOT_SENT,\n\n // ------------------------------------------- the call cannot work as written, found AFTER an answer\n // (a pre_send code needs no row: it is `invalid_input` by default, see `CodeTable`)\n NO_WALLET_ADDRESS: INVALID_INPUT,\n NO_POOL_AVAILABLE: INVALID_INPUT,\n INSUFFICIENT_LIQUIDITY: INVALID_INPUT,\n} as const satisfies CodeTable;\n\n/** A code whose catalog phase is `pre_send`. */\ntype PreSendCode = {\n [C in GSwapErrorCode]: (typeof GSWAP_ERROR_CATALOG)[C]['phase'] extends 'pre_send' ? C : never;\n}[GSwapErrorCode];\n\n/** The codes whose classification depends on the failing answer, not on a table row. */\ntype AnswerDependentCode =\n 'HTTP_ERROR' | 'UPDATE_REQUIRED' | 'CHAIN_DISPATCH_FAILED' | 'INVALID_PRIVATE_KEY';\n\n/**\n * Every code that is NOT `pre_send` needs a row (a new one is a compile error until it has one);\n * a `pre_send` code may have one (`SIGNING_FAILED` is `not_sent`). A `pre_send` code without a row\n * is `invalid_input`: nothing was transmitted, so it cannot be `unknown`, and it is not a\n * condition a repeat clears. That keeps the 90-odd validation codes out of the table, which is\n * what keeps `classifyFailure` small enough for an app shell.\n */\ntype CodeTable = {\n readonly [C in Exclude<GSwapErrorCode, PreSendCode | AnswerDependentCode>]: FailureClassification;\n} & {\n readonly [C in Exclude<PreSendCode, AnswerDependentCode>]?: FailureClassification;\n};\n\n/**\n * A key that is not valid is the input's fault (`invalid_input`), except `no_key_provided`: the\n * provider had nothing to give right now (a locked wallet, an empty slot). That is the same\n * situation as a provider that throws (`KEY_PROVIDER_FAILED`), and it clears once a key exists, so\n * it is `not_sent` and Retry is safe.\n */\nfunction classifyInvalidPrivateKey(view: FailureView): FailureClassification {\n return stringParamOf(view, 'reason') === 'no_key_provided' ? NOT_SENT : INVALID_INPUT;\n}\n\n/** The status of an HTTP failure: the one it carries, else its `status` param. */\nfunction statusOfView(view: FailureView): number | undefined {\n return view.status ?? numberParamOf(view, 'status');\n}\n\n/**\n * An HTTP failure with no gateway or chain code: its meaning depends on the status and on read vs\n * write. A view that states no status at all cannot be judged: `unknown`.\n */\nfunction classifyHttpError(view: FailureView): FailureClassification {\n const status = statusOfView(view);\n if (status === undefined) return UNKNOWN;\n if (isWriteView(view)) {\n // A gateway that answered 5xx or 408 may still have queued the write; 429 and 4xx did not.\n if (status === 429) return NOT_QUEUED;\n if (status === 408 || status >= 500) return UNKNOWN;\n return NOT_QUEUED_FINAL;\n }\n return status === 408 || status === 429 || status >= 500 ? TRANSIENT : INVALID_INPUT;\n}\n\n/**\n * The version wall (HTTP 426). On a write the gateway refused it before queueing anything, and a\n * repeat of the same app version is refused the same way, so it is `not_queued` with no Retry. On a\n * read nothing changed either, and only an update helps: `invalid_input`, no Retry. Both say \"show\n * the update screen\", not \"try again\".\n */\nfunction classifyUpdateRequired(view: FailureView): FailureClassification {\n return isWriteView(view) ? NOT_QUEUED_FINAL : INVALID_INPUT;\n}\n\n/**\n * The chain dispatch failed. The gateway echoes the upstream status: 409 is \"this uniqueKey was\n * already consumed\" (the first attempt may be applied), 422 is the chain evaluating the write and\n * refusing it (definite), another 5xx is an upstream outage after which the write may have landed,\n * and a 4xx is a refusal before anything was queued.\n */\nfunction classifyChainDispatchFailed(view: FailureView): FailureClassification {\n const status = view.status;\n if (status === undefined || status === 409 || status >= 500) return UNKNOWN;\n return status === 422 ? REJECTED : NOT_QUEUED;\n}\n\n/**\n * True when the gateway answered that the write's `uniqueKey` was ALREADY CONSUMED: a\n * `CHAIN_DISPATCH_FAILED` with HTTP status 409. The first attempt under that key may be applied\n * (`classifyFailure` says `unknown`), so the app confirms the key it holds instead of submitting\n * again or showing the server's text. It replaces `error.code === 'CHAIN_DISPATCH_FAILED' &&\n * error.status === 409` and the regex on \"already saved\". Accepts an SDK error or a plain-object\n * view ({@link FailureView}: `code` and `status`); every other value is `false`.\n *\n * @example\n * ```ts\n * if (isUniqueKeyConsumed(error)) return confirmAndShowResult(error.uniqueKey);\n * ```\n */\nexport function isUniqueKeyConsumed(error: unknown): boolean {\n const view = toFailureView(error);\n return view?.code === 'CHAIN_DISPATCH_FAILED' && view.status === 409;\n}\n\nconst UNPROVEN_ACKNOWLEDGEMENT_CODES: readonly GSwapErrorCode[] = [\n 'GATEWAY_DATA_MISSING',\n 'GATEWAY_MODE_INVALID',\n 'GATEWAY_SYNC_REQUEST_TRADE',\n 'MISSING_UNIQUE_KEY',\n 'UNIQUE_KEY_MISMATCH',\n];\n\n/**\n * True when a write got a 2xx answer that does NOT prove it was queued under the signed key: no\n * `data`, an unreadable `mode`, a synchronous RequestTrade, or a missing or foreign `uniqueKey`.\n * The outcome is `unknown` (it may have been queued: confirm `error.uniqueKey`), and the copy an\n * app shows is one line for all five (\"the backend did not confirm it queued the request\"), so\n * the app asks this instead of keeping its own list of the codes. A gateway that says\n * `error: true` (`GATEWAY_REPORTED_ERROR`) is a refusal with its own message, not in this set.\n *\n * @example\n * ```ts\n * if (isUnprovenAcknowledgement(error)) keepPendingAndConfirm(error.uniqueKey);\n * ```\n */\nexport function isUnprovenAcknowledgement(error: unknown): boolean {\n return hasCodeIn(error, UNPROVEN_ACKNOWLEDGEMENT_CODES);\n}\n\n/** True when `code` is one of `codes` (a list of catalog codes), by exact spelling. */\nfunction isCodeIn(code: string, codes: readonly GSwapErrorCode[]): boolean {\n return codes.some((known) => known === code);\n}\n\n/**\n * True when `error` is an SDK error OR a plain-object view of one (see {@link FailureView}) whose\n * `code` is one of `codes`. The code is compared with the catalog's own spelling, so a wallet\n * error (`{ code: 'ACTION_REJECTED' }`, a numeric `4001`) is never a member.\n */\nfunction hasCodeIn(error: unknown, codes: readonly GSwapErrorCode[]): boolean {\n const view = toFailureView(error);\n // `codes` holds catalog codes only, so membership already means \"a catalog code\": no table needed.\n return view !== undefined && isCodeIn(view.code, codes);\n}\n\n/**\n * The codes whose `params.serverMessage` is the gateway's own answer to THIS write, which an app\n * may show as it is (and falls back to its own line when it is empty): a refusal the gateway made\n * (`GATEWAY_REPORTED_ERROR`, a 2xx with `error: true`) or a bounce it named (the gateway codes the\n * SDK knows, and `GATEWAY_REJECTED` for one it does not).\n */\nconst RELAYABLE_REFUSAL_CODES: readonly GSwapErrorCode[] = [\n 'GATEWAY_REPORTED_ERROR',\n 'METHOD_NOT_ALLOWED',\n 'SIGNATURE_INVALID',\n 'SIGNER_MISMATCH',\n 'DTO_INVALID',\n 'BOUNDS_VIOLATION',\n 'SYMBOL_CONFLICT',\n 'RATE_LIMITED',\n 'CHAIN_DISPATCH_FAILED',\n 'GATEWAY_REJECTED',\n];\n\n/**\n * True when the error is a gateway refusal whose `params.serverMessage` an app may relay to the\n * person as the gateway sent it: `GATEWAY_REPORTED_ERROR`, the gateway bounces (`METHOD_NOT_ALLOWED`,\n * `SIGNATURE_INVALID`, `SIGNER_MISMATCH`, `DTO_INVALID`, `BOUNDS_VIOLATION`, `SYMBOL_CONFLICT`,\n * `RATE_LIMITED`, `GATEWAY_REJECTED`) and `CHAIN_DISPATCH_FAILED`. The app shows `serverMessage` when\n * it is not empty and its own line when it is; the SDK's English `message` is never what to show.\n *\n * It accepts a plain-object view of an SDK error too ({@link FailureView}: an app's wrapper with a\n * `code`), and decides from the code in the same way. It narrows to `GSwapSDKError<...>` ONLY when\n * called with an SDK error; with an `unknown` or a view it answers `boolean` (narrow with\n * `isGSwapError` first, or read the view's own `params`).\n *\n * It is a test on the code alone, so it also holds for `CHAIN_DISPATCH_FAILED` with HTTP 409\n * (the `uniqueKey` was already consumed; `classifyFailure` says `unknown`): an app that words that\n * case itself (\"already submitted\", then confirm the key) checks `error.status === 409` BEFORE\n * relaying. It is false for `CHAIN_REJECTED` (the chain's verdict, worded by where it is shown: the\n * submit and confirm screens differ), for `HTTP_ERROR` (a proxy's page, not an answer to this\n * write) and for the five unproven acknowledgements (`isUnprovenAcknowledgement`: they carry no\n * server text).\n *\n * @example\n * ```ts\n * if (isRelayableRefusal(error)) show(error.params.serverMessage || t('swap.requestFailed'));\n * ```\n */\nexport function isRelayableRefusal(error: GSwapSDKError): error is GSwapSDKError<RelayableCode>;\nexport function isRelayableRefusal(error: unknown): boolean;\nexport function isRelayableRefusal(error: unknown): boolean {\n return hasCodeIn(error, RELAYABLE_REFUSAL_CODES);\n}\n\n/** The codes {@link isRelayableRefusal} is true for. */\nexport type RelayableCode =\n | 'GATEWAY_REPORTED_ERROR'\n | 'METHOD_NOT_ALLOWED'\n | 'SIGNATURE_INVALID'\n | 'SIGNER_MISMATCH'\n | 'DTO_INVALID'\n | 'BOUNDS_VIOLATION'\n | 'SYMBOL_CONFLICT'\n | 'RATE_LIMITED'\n | 'CHAIN_DISPATCH_FAILED'\n | 'GATEWAY_REJECTED';\n\n/**\n * True when a write got a 2xx answer the SDK refused to interpret, so the answer itself is in\n * `error.body` (or `error.bodyText`) for the app to read: the five unproven acknowledgements\n * (`isUnprovenAcknowledgement`) and `GATEWAY_REPORTED_ERROR` (a gateway that says `error: true`).\n * The outcome is `unknown` for all six. It replaces the app's own\n * `isUnprovenAcknowledgement(e) || isGSwapError(e, 'GATEWAY_REPORTED_ERROR')` pair.\n */\nexport function isUninterpretedAnswer(error: unknown): boolean {\n return isUnprovenAcknowledgement(error) || hasCodeIn(error, ['GATEWAY_REPORTED_ERROR']);\n}\n\n/**\n * Classifies any thrown value from its code (and, for two codes, its HTTP status), never from\n * message text, so an app does not have to re-derive \"may I offer Retry?\" and \"is the outcome\n * unknown?\" for every code. The decision for each code is in this module's table; a new catalog\n * code cannot be added without one.\n *\n * A failure BEFORE the request left (`error.phase === 'pre_send'`, `isPreSendError`) can never be\n * `unknown` or `rejected`: it is `invalid_input` (the input has to change) or `not_sent` (nothing\n * was transmitted; a repeat is safe). Every app-supplied callback the SDK calls before it sends\n * (the signer, the key provider, a token resolver) has its failure wrapped into such an error\n * (`SIGNING_FAILED`, `KEY_PROVIDER_FAILED`, `TOKEN_RESOLUTION_FAILED`, the original as `cause`),\n * so an app NEVER needs an error class of its own to say \"nothing was sent\": a locked wallet,\n * a declined signature and a failed lookup all arrive as `not_sent`, retryable.\n *\n * It accepts a `GSwapSDKError` or a plain-object VIEW of one ({@link FailureView}: `{ code,\n * params?, status?, uniqueKey?, ... }`, the shape of the wrapper an app builds from an SDK error;\n * `toFailureView` reads any value), and decides from the same fields, so an app does not need to\n * keep the original error to ask. `{ afterQueued: true }` says the write is already queued (the\n * step being classified is a confirmation): `retryable` is then always `false`.\n *\n * Anything that is not an SDK error or such a view, or a view whose `code` the catalog does not\n * define, is `{ outcome: 'unknown', retryable: false }`: the SDK\n * cannot say what happened, and refusing Retry is the safe answer. That is only what escapes AFTER\n * the request left (an app requestor that rejects with its own error, a callback of yours that\n * throws once bytes were sent). An error your own code raises BEFORE it calls the SDK says so\n * itself: give it a code of its own and `outcome` (`{ code: 'POSITION_STALE', outcome: 'not_sent'\n * }`), and the SDK answers with that outcome's classification and `mayHaveBeenQueued` is `false`.\n * The declared `outcome` is read only for a code the catalog does not define.\n *\n * A failure is a WRITE's unless it states `write: false` (`GSwapSDKError.write`, set for every\n * SDK read): see {@link FailureView}. A view that lost its `uniqueKey` is therefore still judged as\n * a write (a 5xx stays `unknown`), never as a retryable read.\n *\n * To learn that a failure came from the dark (shadow) path, read `error.shadow` (a typed field of\n * every `GSwapSDKError`) or call `isShadowError(error)`, which also covers a non-SDK object that\n * escaped `swapShadow` / `submitSwapShadow` / `ChainGateway.submitShadow`. The classification is\n * the same as on the public path; an app that withholds Retry or a public fallback after a dark\n * swap applies that policy on top of `retryable`.\n *\n * @example\n * ```ts\n * try {\n * await swaps.swap('GALA', 'GUSDC', 3000, { exactIn: '100' });\n * } catch (error) {\n * const { outcome, retryable } = classifyFailure(error);\n * if (outcome === 'unknown') keepPendingAndConfirm(error); // never a second swap\n * else if (retryable) offerRetry(); // a new request under a fresh key\n * }\n * ```\n */\nexport function classifyFailure(\n error: unknown,\n options: ClassifyFailureOptions = {},\n): FailureClassification {\n const result = classifyView(toFailureView(error));\n // A `rejected` outcome is the chain's definite no: the queued write was evaluated and NOT\n // applied, so a new request under a fresh key cannot double-apply it and Retry stays as it is.\n return options.afterQueued === true && result.outcome !== 'rejected'\n ? { ...result, retryable: false }\n : result;\n}\n\n/** Options of {@link classifyFailure}. */\nexport interface ClassifyFailureOptions {\n /**\n * The write is ALREADY queued (the gateway acknowledged it with this `uniqueKey`), and what is\n * being classified is a later step: confirming it, reading its apply result. Whatever the\n * outcome, `retryable` is then `false`: a NEW request would be a second trade, so the app\n * confirms the same key (or forgets it) and never offers \"Try again\". `outcome` is unchanged.\n *\n * The one exception is `rejected`: the chain evaluated the queued write and refused it (an\n * apply result of `Status: 0`, `CHAIN_REJECTED`; a 422 dispatch). That PROVES it was not\n * applied, so a new request under a fresh key cannot make a second trade, and `retryable`\n * keeps the answer it has without this option (`true`). Every outcome that does not prove\n * \"not applied\" (`unknown`, `transient`, `not_queued`, ...) is `false`.\n */\n afterQueued?: boolean | undefined;\n}\n\n/**\n * The classification of a failure view: the table decision for its code, the status-dependent\n * decision for the four codes whose meaning needs it, and for a code the catalog does not define\n * (a wallet error, an ethers `ACTION_REJECTED`, a typo, an app's own error): `unknown` unless the\n * view DECLARES its `outcome` (an app's error, `{ code: 'POSITION_STALE', outcome: 'not_sent' }`),\n * then the SDK's own classification of that outcome. A value with no view is `unknown`.\n */\nfunction classifyView(view: FailureView | undefined): FailureClassification {\n if (view === undefined) return { ...UNKNOWN };\n if (!isCatalogCode(view.code)) {\n if (view.outcome === undefined) return { ...UNKNOWN };\n // Withhold-only: `retryable: false` removes Retry from the declared outcome, never adds it.\n return view.retryable === false\n ? classification(view.outcome, { retryable: false })\n : classification(view.outcome);\n }\n const { code } = view;\n if (code === 'HTTP_ERROR') return { ...classifyHttpError(view) };\n if (code === 'UPDATE_REQUIRED') return { ...classifyUpdateRequired(view) };\n if (code === 'CHAIN_DISPATCH_FAILED') return { ...classifyChainDispatchFailed(view) };\n if (code === 'INVALID_PRIVATE_KEY') return { ...classifyInvalidPrivateKey(view) };\n const row: FailureClassification | undefined = (\n BY_CODE as Readonly<Partial<Record<GSwapErrorCode, FailureClassification>>>\n )[code];\n return { ...(row ?? INVALID_INPUT) };\n}\n\n/** Codes that mean \"there is no market for this\" however the app reached them. */\nconst NO_MARKET_CODES: readonly GSwapErrorCode[] = [\n 'NO_POOL_AVAILABLE',\n 'INSUFFICIENT_LIQUIDITY',\n 'UNKNOWN_TOKEN',\n];\n\n/**\n * True when a failure means \"there is no market to trade this pair\": `NO_POOL_AVAILABLE` (no pool\n * at any fee tier), `INSUFFICIENT_LIQUIDITY` (a pool too thin to quote), `UNKNOWN_TOKEN` (the DEX\n * has no trading symbol for the token), or a READ that was answered 404 (a quote the backend\n * cannot find, `error.notFound`). It replaces an app's own `code === ... || code === ... ||\n * notFound` list, so the empty-state (\"No market for this pair\") has one definition.\n *\n * A write that was answered 404 (it carries a `uniqueKey`) is a missing route, not a missing\n * market, and is not this. Accepts an SDK error or a plain-object view ({@link FailureView}); every\n * other value is `false`.\n *\n * @example\n * ```ts\n * if (isNoMarket(error)) showEmptyState(t('swap.noMarket')); else showError(error);\n * ```\n */\nexport function isNoMarket(error: unknown): boolean {\n const view = toFailureView(error);\n if (view === undefined) return false;\n if (isCodeIn(view.code, NO_MARKET_CODES)) return true;\n return !isWriteView(view) && (view.notFound === true || view.status === 404);\n}\n\n/**\n * True when the failed call was a WRITE that may nevertheless have been queued: its outcome is\n * `unknown` (`classifyFailure(error).outcome === 'unknown'`: a timeout or broken connection after\n * the request left, an acknowledgement that proves nothing, a gateway 409 or 5xx, a failed\n * confirmation). The app must keep `error.uniqueKey` and confirm it, and must NOT submit again\n * under a fresh key. Every other outcome (`rejected`, `not_queued`, `not_sent`, `invalid_input`,\n * `transient`) proves the write is not pending.\n *\n * A value the SDK cannot judge (an app requestor's own rejection after the request left) is\n * `unknown` too, so it is `true`; handle errors your own code raised BEFORE calling the SDK first.\n * Meant for the catch of a write; a read never has a pending key to keep.\n *\n * @example\n * ```ts\n * try {\n * await swaps.swap('GALA', 'GUSDC', 3000, { exactIn: '100' });\n * } catch (error) {\n * if (mayHaveBeenQueued(error)) keepPendingAndConfirm(error);\n * }\n * ```\n */\nexport function mayHaveBeenQueued(error: unknown): boolean {\n return classifyFailure(error).outcome === 'unknown';\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"failure_classification.js","names":[],"sources":["../../../src/utils/failure_classification.ts"],"sourcesContent":["import type { GSWAP_ERROR_CATALOG, GSwapErrorCode } from '../classes/error_catalog.js';\nimport { isCatalogCode } from '../classes/error_phase_table.js';\nimport { classification, NOT_QUEUED_FINAL, type FailureClassification } from './classification.js';\nimport { isWriteView, numberParamOf, stringParamOf, toFailureView } from './failure_view.js';\nimport type { FailureView } from './failure_view.js';\n\n/**\n * `classifyFailure` and `mayHaveBeenQueued`: the one module that holds the decision for every\n * catalog code (~135 names, a floor no minifier can remove). It needs the code table and nothing\n * else: the classification values live in `classification.ts`, the yes/no guards in\n * `failure_guards.ts`, and the error class and catalog are only named as types.\n */\n\nconst REJECTED: FailureClassification = classification('rejected');\nconst UNKNOWN: FailureClassification = classification('unknown');\nconst NOT_QUEUED: FailureClassification = classification('not_queued');\nconst NOT_SENT: FailureClassification = classification('not_sent');\nconst INVALID_INPUT: FailureClassification = classification('invalid_input');\nconst TRANSIENT: FailureClassification = classification('transient');\n\n/**\n * The classification of every code whose meaning does not depend on the failing answer. A code\n * missing here is a compile error (unless it is `pre_send`, see {@link CodeTable}), so adding a\n * catalog code forces a decision.\n */\nconst BY_CODE = {\n // ----------------------------------------------------------------------------------- transport\n REQUEST_TIMEOUT: TRANSIENT,\n TRANSPORT_ERROR: TRANSIENT,\n REQUEST_FAILED: TRANSIENT,\n\n // ------------------------------------------------------------------------------- write: unknown\n // No answer was read, or a 2xx answer that does not prove the write was queued under the signed\n // key: it may have been applied.\n SUBMISSION_OUTCOME_UNKNOWN: UNKNOWN,\n GATEWAY_REPORTED_ERROR: UNKNOWN,\n GATEWAY_DATA_MISSING: UNKNOWN,\n GATEWAY_MODE_INVALID: UNKNOWN,\n GATEWAY_SYNC_REQUEST_TRADE: UNKNOWN,\n MISSING_UNIQUE_KEY: UNKNOWN,\n UNIQUE_KEY_MISMATCH: UNKNOWN,\n\n // --------------------------------------------------------------------- write: refused, not queued\n SHADOW_NOT_QUEUED: NOT_QUEUED_FINAL,\n METHOD_NOT_ALLOWED: NOT_QUEUED,\n SIGNATURE_INVALID: NOT_QUEUED,\n SIGNER_MISMATCH: NOT_QUEUED,\n DTO_INVALID: NOT_QUEUED,\n BOUNDS_VIOLATION: NOT_QUEUED,\n SYMBOL_CONFLICT: NOT_QUEUED,\n RATE_LIMITED: NOT_QUEUED,\n GATEWAY_REJECTED: NOT_QUEUED,\n\n // ----------------------------------------------------------------------- the chain's own verdict\n CHAIN_REJECTED: REJECTED,\n\n // ----------------------------------------------------------------------- reads of backend data\n INVALID_CHAIN_RESPONSE: TRANSIENT,\n INVALID_POSITION_RESPONSE: TRANSIENT,\n\n // -------------------------------------------------------------------------------- confirmation\n CONFIRMATION_TIMEOUT: UNKNOWN,\n CONFIRMATION_ABORTED: UNKNOWN,\n CONFIRMATION_FAILED: UNKNOWN,\n CONFIRMATION_MALFORMED: UNKNOWN,\n CONFIRMATION_KEY_MISMATCH: UNKNOWN,\n\n // ------------------------------------------------------------------------------ the realtime stream\n STREAM_DISCONNECTED: TRANSIENT,\n STREAM_REQUEST_TIMEOUT: TRANSIENT,\n STREAM_CONNECT_FAILED: TRANSIENT,\n STREAM_CLOSED_BEFORE_CONNECT: TRANSIENT,\n STREAM_CONNECTION_CLOSED: TRANSIENT,\n\n // ----------------------------------------------- pre-send: an app-supplied callback failed\n // The signer, the key provider or the token resolver threw before anything was transmitted. The\n // original error is `cause`. A repeat cannot apply anything twice.\n SIGNING_FAILED: NOT_SENT,\n KEY_PROVIDER_FAILED: NOT_SENT,\n TOKEN_RESOLUTION_FAILED: NOT_SENT,\n\n // ------------------------------------------- the call cannot work as written, found AFTER an answer\n // (a pre_send code needs no row: it is `invalid_input` by default, see `CodeTable`)\n NO_WALLET_ADDRESS: INVALID_INPUT,\n NO_POOL_AVAILABLE: INVALID_INPUT,\n INSUFFICIENT_LIQUIDITY: INVALID_INPUT,\n} as const satisfies CodeTable;\n\n/** A code whose catalog phase is `pre_send`. */\ntype PreSendCode = {\n [C in GSwapErrorCode]: (typeof GSWAP_ERROR_CATALOG)[C]['phase'] extends 'pre_send' ? C : never;\n}[GSwapErrorCode];\n\n/** The codes whose classification depends on the failing answer, not on a table row. */\ntype AnswerDependentCode =\n 'HTTP_ERROR' | 'UPDATE_REQUIRED' | 'CHAIN_DISPATCH_FAILED' | 'INVALID_PRIVATE_KEY';\n\n/**\n * Every code that is NOT `pre_send` needs a row (a new one is a compile error until it has one);\n * a `pre_send` code may have one (`SIGNING_FAILED` is `not_sent`). A `pre_send` code without a row\n * is `invalid_input`: nothing was transmitted, so it cannot be `unknown`, and it is not a\n * condition a repeat clears. That keeps the 90-odd validation codes out of the table, which is\n * what keeps `classifyFailure` small enough for an app shell.\n */\ntype CodeTable = {\n readonly [C in Exclude<GSwapErrorCode, PreSendCode | AnswerDependentCode>]: FailureClassification;\n} & {\n readonly [C in Exclude<PreSendCode, AnswerDependentCode>]?: FailureClassification;\n};\n\n/**\n * A key that is not valid is the input's fault (`invalid_input`), except `no_key_provided`: the\n * provider had nothing to give right now (a locked wallet, an empty slot). That is the same\n * situation as a provider that throws (`KEY_PROVIDER_FAILED`), and it clears once a key exists, so\n * it is `not_sent` and Retry is safe.\n */\nfunction classifyInvalidPrivateKey(view: FailureView): FailureClassification {\n return stringParamOf(view, 'reason') === 'no_key_provided' ? NOT_SENT : INVALID_INPUT;\n}\n\n/** The status of an HTTP failure: the one it carries, else its `status` param. */\nfunction statusOfView(view: FailureView): number | undefined {\n return view.status ?? numberParamOf(view, 'status');\n}\n\n/**\n * An HTTP failure with no gateway or chain code: its meaning depends on the status and on read vs\n * write. A view that states no status at all cannot be judged: `unknown`.\n */\nfunction classifyHttpError(view: FailureView): FailureClassification {\n const status = statusOfView(view);\n if (status === undefined) return UNKNOWN;\n if (isWriteView(view)) {\n // A gateway that answered 5xx or 408 may still have queued the write; 429 and 4xx did not.\n if (status === 429) return NOT_QUEUED;\n if (status === 408 || status >= 500) return UNKNOWN;\n return NOT_QUEUED_FINAL;\n }\n return status === 408 || status === 429 || status >= 500 ? TRANSIENT : INVALID_INPUT;\n}\n\n/**\n * The version wall (HTTP 426). On a write the gateway refused it before queueing anything, and a\n * repeat of the same app version is refused the same way, so it is `not_queued` with no Retry. On a\n * read nothing changed either, and only an update helps: `invalid_input`, no Retry. Both say \"show\n * the update screen\", not \"try again\".\n */\nfunction classifyUpdateRequired(view: FailureView): FailureClassification {\n return isWriteView(view) ? NOT_QUEUED_FINAL : INVALID_INPUT;\n}\n\n/**\n * The chain dispatch failed. The gateway echoes the upstream status: 409 is \"this uniqueKey was\n * already consumed\" (the first attempt may be applied), 422 is the chain evaluating the write and\n * refusing it (definite), another 5xx is an upstream outage after which the write may have landed,\n * and a 4xx is a refusal before anything was queued.\n */\nfunction classifyChainDispatchFailed(view: FailureView): FailureClassification {\n const status = view.status;\n if (status === undefined || status === 409 || status >= 500) return UNKNOWN;\n return status === 422 ? REJECTED : NOT_QUEUED;\n}\n\n/**\n * Classifies any thrown value from its code (and, for two codes, its HTTP status), never from\n * message text, so an app does not have to re-derive \"may I offer Retry?\" and \"is the outcome\n * unknown?\" for every code. The decision for each code is in this module's table; a new catalog\n * code cannot be added without one.\n *\n * A failure BEFORE the request left (`error.phase === 'pre_send'`, `isPreSendError`) can never be\n * `unknown` or `rejected`: it is `invalid_input` (the input has to change) or `not_sent` (nothing\n * was transmitted; a repeat is safe). Every app-supplied callback the SDK calls before it sends\n * (the signer, the key provider, a token resolver) has its failure wrapped into such an error\n * (`SIGNING_FAILED`, `KEY_PROVIDER_FAILED`, `TOKEN_RESOLUTION_FAILED`, the original as `cause`),\n * so an app NEVER needs an error class of its own to say \"nothing was sent\": a locked wallet,\n * a declined signature and a failed lookup all arrive as `not_sent`, retryable.\n *\n * It accepts a `GSwapSDKError` or a plain-object VIEW of one ({@link FailureView}: `{ code,\n * params?, status?, uniqueKey?, ... }`, the shape of the wrapper an app builds from an SDK error;\n * `toFailureView` reads any value), and decides from the same fields, so an app does not need to\n * keep the original error to ask. `{ afterQueued: true }` says the write is already queued (the\n * step being classified is a confirmation): `retryable` is then always `false`.\n *\n * Anything that is not an SDK error or such a view, or a view whose `code` the catalog does not\n * define, is `{ outcome: 'unknown', retryable: false }`: the SDK\n * cannot say what happened, and refusing Retry is the safe answer. That is only what escapes AFTER\n * the request left (an app requestor that rejects with its own error, a callback of yours that\n * throws once bytes were sent). An error your own code raises BEFORE it calls the SDK says so\n * itself: give it a code of its own and `outcome` (`{ code: 'POSITION_STALE', outcome: 'not_sent'\n * }`), and the SDK answers with that outcome's classification and `mayHaveBeenQueued` is `false`.\n * The declared `outcome` is read only for a code the catalog does not define.\n *\n * A failure is a WRITE's unless it states `write: false` (`GSwapSDKError.write`, set for every\n * SDK read): see {@link FailureView}. A view that lost its `uniqueKey` is therefore still judged as\n * a write (a 5xx stays `unknown`), never as a retryable read.\n *\n * To learn that a failure came from the dark (shadow) path, read `error.shadow` (a typed field of\n * every `GSwapSDKError`) or call `isShadowError(error)`, which also covers a non-SDK object that\n * escaped `swapShadow` / `submitSwapShadow` / `ChainGateway.submitShadow`. The classification is\n * the same as on the public path; an app that withholds Retry or a public fallback after a dark\n * swap applies that policy on top of `retryable`.\n *\n * @example\n * ```ts\n * try {\n * await swaps.swap('GALA', 'GUSDC', 3000, { exactIn: '100' });\n * } catch (error) {\n * const { outcome, retryable } = classifyFailure(error);\n * if (outcome === 'unknown') keepPendingAndConfirm(error); // never a second swap\n * else if (retryable) offerRetry(); // a new request under a fresh key\n * }\n * ```\n */\nexport function classifyFailure(\n error: unknown,\n options: ClassifyFailureOptions = {},\n): FailureClassification {\n const result = classifyView(toFailureView(error));\n // A `rejected` outcome is the chain's definite no: the queued write was evaluated and NOT\n // applied, so a new request under a fresh key cannot double-apply it and Retry stays as it is.\n return options.afterQueued === true && result.outcome !== 'rejected'\n ? { ...result, retryable: false }\n : result;\n}\n\n/** Options of {@link classifyFailure}. */\nexport interface ClassifyFailureOptions {\n /**\n * The write is ALREADY queued (the gateway acknowledged it with this `uniqueKey`), and what is\n * being classified is a later step: confirming it, reading its apply result. Whatever the\n * outcome, `retryable` is then `false`: a NEW request would be a second trade, so the app\n * confirms the same key (or forgets it) and never offers \"Try again\". `outcome` is unchanged.\n *\n * The one exception is `rejected`: the chain evaluated the queued write and refused it (an\n * apply result of `Status: 0`, `CHAIN_REJECTED`; a 422 dispatch). That PROVES it was not\n * applied, so a new request under a fresh key cannot make a second trade, and `retryable`\n * keeps the answer it has without this option (`true`). Every outcome that does not prove\n * \"not applied\" (`unknown`, `transient`, `not_queued`, ...) is `false`.\n */\n afterQueued?: boolean | undefined;\n}\n\n/**\n * The classification of a failure view: the table decision for its code, the status-dependent\n * decision for the four codes whose meaning needs it, and for a code the catalog does not define\n * (a wallet error, an ethers `ACTION_REJECTED`, a typo, an app's own error): `unknown` unless the\n * view DECLARES its `outcome` (an app's error, `{ code: 'POSITION_STALE', outcome: 'not_sent' }`),\n * then the SDK's own classification of that outcome. A value with no view is `unknown`.\n */\nfunction classifyView(view: FailureView | undefined): FailureClassification {\n if (view === undefined) return { ...UNKNOWN };\n if (!isCatalogCode(view.code)) {\n if (view.outcome === undefined) return { ...UNKNOWN };\n // Withhold-only: `retryable: false` removes Retry from the declared outcome, never adds it.\n return view.retryable === false\n ? classification(view.outcome, { retryable: false })\n : classification(view.outcome);\n }\n const { code } = view;\n if (code === 'HTTP_ERROR') return { ...classifyHttpError(view) };\n if (code === 'UPDATE_REQUIRED') return { ...classifyUpdateRequired(view) };\n if (code === 'CHAIN_DISPATCH_FAILED') return { ...classifyChainDispatchFailed(view) };\n if (code === 'INVALID_PRIVATE_KEY') return { ...classifyInvalidPrivateKey(view) };\n const row: FailureClassification | undefined = (\n BY_CODE as Readonly<Partial<Record<GSwapErrorCode, FailureClassification>>>\n )[code];\n return { ...(row ?? INVALID_INPUT) };\n}\n\n/**\n * True when the failed call was a WRITE that may nevertheless have been queued: its outcome is\n * `unknown` (`classifyFailure(error).outcome === 'unknown'`: a timeout or broken connection after\n * the request left, an acknowledgement that proves nothing, a gateway 409 or 5xx, a failed\n * confirmation). The app must keep `error.uniqueKey` and confirm it, and must NOT submit again\n * under a fresh key. Every other outcome (`rejected`, `not_queued`, `not_sent`, `invalid_input`,\n * `transient`) proves the write is not pending.\n *\n * A value the SDK cannot judge (an app requestor's own rejection after the request left) is\n * `unknown` too, so it is `true`; handle errors your own code raised BEFORE calling the SDK first.\n * Meant for the catch of a write; a read never has a pending key to keep.\n *\n * @example\n * ```ts\n * try {\n * await swaps.swap('GALA', 'GUSDC', 3000, { exactIn: '100' });\n * } catch (error) {\n * if (mayHaveBeenQueued(error)) keepPendingAndConfirm(error);\n * }\n * ```\n */\nexport function mayHaveBeenQueued(error: unknown): boolean {\n return classifyFailure(error).outcome === 'unknown';\n}\n"],"mappings":"oPAaA,MAAM,EAAkC,EAAe,UAAU,EAC3D,EAAiC,EAAe,SAAS,EACzD,EAAoC,EAAe,YAAY,EAC/D,EAAkC,EAAe,UAAU,EAC3D,EAAuC,EAAe,eAAe,EACrE,EAAmC,EAAe,WAAW,EAO7D,EAAU,CAEd,gBAAiB,EACjB,gBAAiB,EACjB,eAAgB,EAKhB,2BAA4B,EAC5B,uBAAwB,EACxB,qBAAsB,EACtB,qBAAsB,EACtB,2BAA4B,EAC5B,mBAAoB,EACpB,oBAAqB,EAGrB,kBAAmB,EACnB,mBAAoB,EACpB,kBAAmB,EACnB,gBAAiB,EACjB,YAAa,EACb,iBAAkB,EAClB,gBAAiB,EACjB,aAAc,EACd,iBAAkB,EAGlB,eAAgB,EAGhB,uBAAwB,EACxB,0BAA2B,EAG3B,qBAAsB,EACtB,qBAAsB,EACtB,oBAAqB,EACrB,uBAAwB,EACxB,0BAA2B,EAG3B,oBAAqB,EACrB,uBAAwB,EACxB,sBAAuB,EACvB,6BAA8B,EAC9B,yBAA0B,EAK1B,eAAgB,EAChB,oBAAqB,EACrB,wBAAyB,EAIzB,kBAAmB,EACnB,kBAAmB,EACnB,uBAAwB,CAC1B,EA8BA,SAAS,EAA0B,EAA0C,CAC3E,OAAO,EAAc,EAAM,QAAQ,IAAM,kBAAoB,EAAW,CAC1E,CAGA,SAAS,EAAa,EAAuC,CAC3D,OAAO,EAAK,QAAU,EAAc,EAAM,QAAQ,CACpD,CAMA,SAAS,EAAkB,EAA0C,CACnE,IAAM,EAAS,EAAa,CAAI,EAQhC,OAPI,IAAW,IAAA,GAAkB,EAC7B,EAAY,CAAI,EAEd,IAAW,IAAY,EACvB,IAAW,KAAO,GAAU,IAAY,EACrC,EAEF,IAAW,KAAO,IAAW,KAAO,GAAU,IAAM,EAAY,CACzE,CAQA,SAAS,EAAuB,EAA0C,CACxE,OAAO,EAAY,CAAI,EAAI,EAAmB,CAChD,CAQA,SAAS,EAA4B,EAA0C,CAC7E,IAAM,EAAS,EAAK,OAEpB,OADI,IAAW,IAAA,IAAa,IAAW,KAAO,GAAU,IAAY,EAC7D,IAAW,IAAM,EAAW,CACrC,CAoDA,SAAgB,EACd,EACA,EAAkC,CAAC,EACZ,CACvB,IAAM,EAAS,EAAa,EAAc,CAAK,CAAC,EAGhD,OAAO,EAAQ,cAAgB,IAAQ,EAAO,UAAY,WACtD,CAAE,GAAG,EAAQ,UAAW,EAAM,EAC9B,CACN,CA0BA,SAAS,EAAa,EAAsD,CAC1E,GAAI,IAAS,IAAA,GAAW,MAAO,CAAE,GAAG,CAAQ,EAC5C,GAAI,CAAC,EAAc,EAAK,IAAI,EAG1B,OAFI,EAAK,UAAY,IAAA,GAAkB,CAAE,GAAG,CAAQ,EAE7C,EAAK,YAAc,GACtB,EAAe,EAAK,QAAS,CAAE,UAAW,EAAM,CAAC,EACjD,EAAe,EAAK,OAAO,EAEjC,GAAM,CAAE,QAAS,EAQjB,OAPI,IAAS,aAAqB,CAAE,GAAG,EAAkB,CAAI,CAAE,EAC3D,IAAS,kBAA0B,CAAE,GAAG,EAAuB,CAAI,CAAE,EACrE,IAAS,wBAAgC,CAAE,GAAG,EAA4B,CAAI,CAAE,EAChF,IAAS,sBAA8B,CAAE,GAAG,EAA0B,CAAI,CAAE,EAIzE,CAAE,GAFP,EACA,IACkB,CAAe,CACrC,CAuBA,SAAgB,EAAkB,EAAyB,CACzD,OAAO,EAAgB,CAAK,CAAC,CAAC,UAAY,SAC5C"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{isWriteView as e,stringParamOf as t,toFailureView as n}from"./failure_view.js";function r(e){let t=n(e);return t?.code===`CHAIN_DISPATCH_FAILED`&&t.status===409}const i=[`GATEWAY_DATA_MISSING`,`GATEWAY_MODE_INVALID`,`GATEWAY_SYNC_REQUEST_TRADE`,`MISSING_UNIQUE_KEY`,`UNIQUE_KEY_MISMATCH`];function a(e){return s(e,i)}function o(e,t){return t.some(t=>t===e)}function s(e,t){let r=n(e);return r!==void 0&&o(r.code,t)}const c=[`GATEWAY_REPORTED_ERROR`,`METHOD_NOT_ALLOWED`,`SIGNATURE_INVALID`,`SIGNER_MISMATCH`,`DTO_INVALID`,`BOUNDS_VIOLATION`,`SYMBOL_CONFLICT`,`RATE_LIMITED`,`CHAIN_DISPATCH_FAILED`,`GATEWAY_REJECTED`];function l(e){return s(e,c)}function u(e){return a(e)||s(e,[`GATEWAY_REPORTED_ERROR`])}function d(e){let r=n(e);if(r===void 0||r.code!==`CHAIN_REJECTED`&&r.code!==`CHAIN_DISPATCH_FAILED`)return;let i=t(r,`errorKey`);return i===void 0||i===``?void 0:i}const f=[`NO_POOL_AVAILABLE`,`INSUFFICIENT_LIQUIDITY`,`UNKNOWN_TOKEN`];function p(t){let r=n(t);return r===void 0?!1:o(r.code,f)?!0:!e(r)&&(r.notFound===!0||r.status===404)}export{d as chainErrorKeyOf,p as isNoMarket,l as isRelayableRefusal,u as isUninterpretedAnswer,r as isUniqueKeyConsumed,a as isUnprovenAcknowledgement};
|
|
2
|
+
//# sourceMappingURL=failure_guards.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"failure_guards.js","names":[],"sources":["../../../src/utils/failure_guards.ts"],"sourcesContent":["import type { GSwapErrorCode } from '../classes/error_catalog.js';\nimport type { GSwapSDKError } from '../classes/gswap_sdk_error.js';\nimport { isWriteView, stringParamOf, toFailureView } from './failure_view.js';\n\n/**\n * The yes/no questions an app asks about a failure from its CODE alone. They read only the failure\n * view and a short list of codes: no table of every code (that is `classifyFailure`'s, in\n * `failure_classification.ts`), no error class and no catalog (the imports above are types, erased\n * at build time). An app shell that imports one of them pays for this module and nothing else.\n */\n\n/**\n * True when the gateway answered that the write's `uniqueKey` was ALREADY CONSUMED: a\n * `CHAIN_DISPATCH_FAILED` with HTTP status 409. The first attempt under that key may be applied\n * (`classifyFailure` says `unknown`), so the app confirms the key it holds instead of submitting\n * again or showing the server's text. It replaces `error.code === 'CHAIN_DISPATCH_FAILED' &&\n * error.status === 409` and the regex on \"already saved\". Accepts an SDK error or a plain-object\n * view ({@link FailureView}: `code` and `status`); every other value is `false`.\n *\n * @example\n * ```ts\n * if (isUniqueKeyConsumed(error)) return confirmAndShowResult(error.uniqueKey);\n * ```\n */\nexport function isUniqueKeyConsumed(error: unknown): boolean {\n const view = toFailureView(error);\n return view?.code === 'CHAIN_DISPATCH_FAILED' && view.status === 409;\n}\n\nconst UNPROVEN_ACKNOWLEDGEMENT_CODES: readonly GSwapErrorCode[] = [\n 'GATEWAY_DATA_MISSING',\n 'GATEWAY_MODE_INVALID',\n 'GATEWAY_SYNC_REQUEST_TRADE',\n 'MISSING_UNIQUE_KEY',\n 'UNIQUE_KEY_MISMATCH',\n];\n\n/**\n * True when a write got a 2xx answer that does NOT prove it was queued under the signed key: no\n * `data`, an unreadable `mode`, a synchronous RequestTrade, or a missing or foreign `uniqueKey`.\n * The outcome is `unknown` (it may have been queued: confirm `error.uniqueKey`), and the copy an\n * app shows is one line for all five (\"the backend did not confirm it queued the request\"), so\n * the app asks this instead of keeping its own list of the codes. A gateway that says\n * `error: true` (`GATEWAY_REPORTED_ERROR`) is a refusal with its own message, not in this set.\n *\n * @example\n * ```ts\n * if (isUnprovenAcknowledgement(error)) keepPendingAndConfirm(error.uniqueKey);\n * ```\n */\nexport function isUnprovenAcknowledgement(error: unknown): boolean {\n return hasCodeIn(error, UNPROVEN_ACKNOWLEDGEMENT_CODES);\n}\n\n/** True when `code` is one of `codes` (a list of catalog codes), by exact spelling. */\nfunction isCodeIn(code: string, codes: readonly GSwapErrorCode[]): boolean {\n return codes.some((known) => known === code);\n}\n\n/**\n * True when `error` is an SDK error OR a plain-object view of one (see {@link FailureView}) whose\n * `code` is one of `codes`. The code is compared with the catalog's own spelling, so a wallet\n * error (`{ code: 'ACTION_REJECTED' }`, a numeric `4001`) is never a member.\n */\nfunction hasCodeIn(error: unknown, codes: readonly GSwapErrorCode[]): boolean {\n const view = toFailureView(error);\n // `codes` holds catalog codes only, so membership already means \"a catalog code\": no table needed.\n return view !== undefined && isCodeIn(view.code, codes);\n}\n\n/**\n * The codes whose `params.serverMessage` is the gateway's own answer to THIS write, which an app\n * may show as it is (and falls back to its own line when it is empty): a refusal the gateway made\n * (`GATEWAY_REPORTED_ERROR`, a 2xx with `error: true`) or a bounce it named (the gateway codes the\n * SDK knows, and `GATEWAY_REJECTED` for one it does not).\n */\nconst RELAYABLE_REFUSAL_CODES: readonly GSwapErrorCode[] = [\n 'GATEWAY_REPORTED_ERROR',\n 'METHOD_NOT_ALLOWED',\n 'SIGNATURE_INVALID',\n 'SIGNER_MISMATCH',\n 'DTO_INVALID',\n 'BOUNDS_VIOLATION',\n 'SYMBOL_CONFLICT',\n 'RATE_LIMITED',\n 'CHAIN_DISPATCH_FAILED',\n 'GATEWAY_REJECTED',\n];\n\n/**\n * True when the error is a gateway refusal whose `params.serverMessage` an app may relay to the\n * person as the gateway sent it: `GATEWAY_REPORTED_ERROR`, the gateway bounces (`METHOD_NOT_ALLOWED`,\n * `SIGNATURE_INVALID`, `SIGNER_MISMATCH`, `DTO_INVALID`, `BOUNDS_VIOLATION`, `SYMBOL_CONFLICT`,\n * `RATE_LIMITED`, `GATEWAY_REJECTED`) and `CHAIN_DISPATCH_FAILED`. The app shows `serverMessage` when\n * it is not empty and its own line when it is; the SDK's English `message` is never what to show.\n *\n * It accepts a plain-object view of an SDK error too ({@link FailureView}: an app's wrapper with a\n * `code`), and decides from the code in the same way. It narrows to `GSwapSDKError<...>` ONLY when\n * called with an SDK error; with an `unknown` or a view it answers `boolean` (narrow with\n * `isGSwapError` first, or read the view's own `params`).\n *\n * It is a test on the code alone, so it also holds for `CHAIN_DISPATCH_FAILED` with HTTP 409\n * (the `uniqueKey` was already consumed; `classifyFailure` says `unknown`): an app that words that\n * case itself (\"already submitted\", then confirm the key) checks `error.status === 409` BEFORE\n * relaying. It is false for `CHAIN_REJECTED` (the chain's verdict, worded by where it is shown: the\n * submit and confirm screens differ), for `HTTP_ERROR` (a proxy's page, not an answer to this\n * write) and for the five unproven acknowledgements (`isUnprovenAcknowledgement`: they carry no\n * server text).\n *\n * @example\n * ```ts\n * if (isRelayableRefusal(error)) show(error.params.serverMessage || t('swap.requestFailed'));\n * ```\n */\nexport function isRelayableRefusal(error: GSwapSDKError): error is GSwapSDKError<RelayableCode>;\nexport function isRelayableRefusal(error: unknown): boolean;\nexport function isRelayableRefusal(error: unknown): boolean {\n return hasCodeIn(error, RELAYABLE_REFUSAL_CODES);\n}\n\n/** The codes {@link isRelayableRefusal} is true for. */\nexport type RelayableCode =\n | 'GATEWAY_REPORTED_ERROR'\n | 'METHOD_NOT_ALLOWED'\n | 'SIGNATURE_INVALID'\n | 'SIGNER_MISMATCH'\n | 'DTO_INVALID'\n | 'BOUNDS_VIOLATION'\n | 'SYMBOL_CONFLICT'\n | 'RATE_LIMITED'\n | 'CHAIN_DISPATCH_FAILED'\n | 'GATEWAY_REJECTED';\n\n/**\n * True when a write got a 2xx answer the SDK refused to interpret, so the answer itself is in\n * `error.body` (or `error.bodyText`) for the app to read: the five unproven acknowledgements\n * (`isUnprovenAcknowledgement`) and `GATEWAY_REPORTED_ERROR` (a gateway that says `error: true`).\n * The outcome is `unknown` for all six. It replaces the app's own\n * `isUnprovenAcknowledgement(e) || isGSwapError(e, 'GATEWAY_REPORTED_ERROR')` pair.\n */\nexport function isUninterpretedAnswer(error: unknown): boolean {\n return isUnprovenAcknowledgement(error) || hasCodeIn(error, ['GATEWAY_REPORTED_ERROR']);\n}\n\n/**\n * The chain's own machine-readable `ErrorKey` of a failure (`'TOKEN_NOT_FOUND'`,\n * `'NOT_ENOUGH_LIQUIDITY'`, ...), for an app that maps a chain refusal to its own copy: the\n * `params.errorKey` of a `CHAIN_REJECTED` (an apply result, or a body that carries the chain's\n * error) and of a `CHAIN_DISPATCH_FAILED` (the gateway adds the chain's key to the body it\n * answered). `undefined` when the failure is neither, or when it carries no key (the gateway sent\n * none: the SDK writes `''`, which is not a key). Map the KEY, never the message text.\n *\n * Accepts an SDK error or a plain-object view ({@link FailureView}: `code` and `params`); every\n * other value is `undefined`. Other codes are `undefined` even when they have an `errorKey`-named\n * param, so a stray field cannot pass for a chain verdict.\n *\n * @example\n * ```ts\n * const key = chainErrorKeyOf(error);\n * if (key === 'NOT_ENOUGH_LIQUIDITY') showTooThin(); else showGeneric(error);\n * ```\n */\nexport function chainErrorKeyOf(error: unknown): string | undefined {\n const view = toFailureView(error);\n if (view === undefined) return undefined;\n if (view.code !== 'CHAIN_REJECTED' && view.code !== 'CHAIN_DISPATCH_FAILED') return undefined;\n const errorKey = stringParamOf(view, 'errorKey');\n return errorKey === undefined || errorKey === '' ? undefined : errorKey;\n}\n\n/** Codes that mean \"there is no market for this\" however the app reached them. */\nconst NO_MARKET_CODES: readonly GSwapErrorCode[] = [\n 'NO_POOL_AVAILABLE',\n 'INSUFFICIENT_LIQUIDITY',\n 'UNKNOWN_TOKEN',\n];\n\n/**\n * True when a failure means \"there is no market to trade this pair\": `NO_POOL_AVAILABLE` (no pool\n * at any fee tier), `INSUFFICIENT_LIQUIDITY` (a pool too thin to quote), `UNKNOWN_TOKEN` (the DEX\n * has no trading symbol for the token), or a READ that was answered 404 (a quote the backend\n * cannot find, `error.notFound`). It replaces an app's own `code === ... || code === ... ||\n * notFound` list, so the empty-state (\"No market for this pair\") has one definition.\n *\n * A write that was answered 404 (it carries a `uniqueKey`) is a missing route, not a missing\n * market, and is not this. Accepts an SDK error or a plain-object view ({@link FailureView}); every\n * other value is `false`.\n *\n * @example\n * ```ts\n * if (isNoMarket(error)) showEmptyState(t('swap.noMarket')); else showError(error);\n * ```\n */\nexport function isNoMarket(error: unknown): boolean {\n const view = toFailureView(error);\n if (view === undefined) return false;\n if (isCodeIn(view.code, NO_MARKET_CODES)) return true;\n return !isWriteView(view) && (view.notFound === true || view.status === 404);\n}\n"],"mappings":"sFAwBA,SAAgB,EAAoB,EAAyB,CAC3D,IAAM,EAAO,EAAc,CAAK,EAChC,OAAO,GAAM,OAAS,yBAA2B,EAAK,SAAW,GACnE,CAEA,MAAM,EAA4D,CAChE,uBACA,uBACA,6BACA,qBACA,qBACF,EAeA,SAAgB,EAA0B,EAAyB,CACjE,OAAO,EAAU,EAAO,CAA8B,CACxD,CAGA,SAAS,EAAS,EAAc,EAA2C,CACzE,OAAO,EAAM,KAAM,GAAU,IAAU,CAAI,CAC7C,CAOA,SAAS,EAAU,EAAgB,EAA2C,CAC5E,IAAM,EAAO,EAAc,CAAK,EAEhC,OAAO,IAAS,IAAA,IAAa,EAAS,EAAK,KAAM,CAAK,CACxD,CAQA,MAAM,EAAqD,CACzD,yBACA,qBACA,oBACA,kBACA,cACA,mBACA,kBACA,eACA,wBACA,kBACF,EA6BA,SAAgB,EAAmB,EAAyB,CAC1D,OAAO,EAAU,EAAO,CAAuB,CACjD,CAsBA,SAAgB,EAAsB,EAAyB,CAC7D,OAAO,EAA0B,CAAK,GAAK,EAAU,EAAO,CAAC,wBAAwB,CAAC,CACxF,CAoBA,SAAgB,EAAgB,EAAoC,CAClE,IAAM,EAAO,EAAc,CAAK,EAEhC,GADI,IAAS,IAAA,IACT,EAAK,OAAS,kBAAoB,EAAK,OAAS,wBAAyB,OAC7E,IAAM,EAAW,EAAc,EAAM,UAAU,EAC/C,OAAO,IAAa,IAAA,IAAa,IAAa,GAAK,IAAA,GAAY,CACjE,CAGA,MAAM,EAA6C,CACjD,oBACA,yBACA,eACF,EAkBA,SAAgB,EAAW,EAAyB,CAClD,IAAM,EAAO,EAAc,CAAK,EAGhC,OAFI,IAAS,IAAA,GAAkB,GAC3B,EAAS,EAAK,KAAM,CAAe,EAAU,GAC1C,CAAC,EAAY,CAAI,IAAM,EAAK,WAAa,IAAQ,EAAK,SAAW,IAC1E"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{GSwapSDKError as e}from"../classes/gswap_sdk_error.js";function
|
|
1
|
+
import{GSwapSDKError as e}from"../classes/gswap_sdk_error.js";import{splitClassKey as t}from"./class_key.js";function n(e,t){return e<t?[e,t]:[t,e]}function r(t){if(Array.isArray(t)&&t.length>=2){let e=t[0],n=t[1];if(typeof e==`string`&&typeof n==`string`)return{token0:e,token1:n}}if(typeof t==`object`&&t){let e=t,n=e.token0,r=e.token1;if(typeof n==`string`&&typeof r==`string`)return{token0:n,token1:r}}throw new e(`UNORDERABLE_SYMBOLS`,{received:Array.isArray(t)?`array`:t===null?`null`:typeof t})}function i(n){if(typeof n==`object`)return{collection:n.collection,category:n.category,type:n.type,additionalKey:n.additionalKey};let r=t(n,n.includes(`|`)?`|`:`$`);if(r===void 0)throw new e(`INVALID_TOKEN_CLASS_KEY`,{tokenClassKey:n});return r}export{n as orderSymbols,i as parseTokenClassKey,r as readOrderedSymbols};
|
|
2
2
|
//# sourceMappingURL=ordering.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ordering.js","names":[],"sources":["../../../src/utils/ordering.ts"],"sourcesContent":["import { GSwapSDKError } from '../classes/gswap_sdk_error.js';\nimport type { GalaChainTokenClassKey } from '../types/token.js';\n\n/** A symbol or a GalaChain token class key accepted by v2 operations. */\nexport type TokenRef = string | GalaChainTokenClassKey;\n\n/** Return two trading symbols in the contract's plain-string order. */\nexport function orderSymbols(a: string, b: string): [string, string] {\n return a < b ? [a, b] : [b, a];\n}\n\n/** Read an ordered symbol pair from a tuple or object returned by a boundary helper.\n *\n * @example\n * ```ts\n * const pair = readOrderedSymbols(['GALA', 'GUSDC']);\n * console.log(pair.token0);\n * ```\n */\nexport function readOrderedSymbols(value: unknown): { token0: string; token1: string } {\n if (Array.isArray(value) && value.length >= 2) {\n const token0 = (value as unknown[])[0];\n const token1 = (value as unknown[])[1];\n if (typeof token0 === 'string' && typeof token1 === 'string') return { token0, token1 };\n }\n if (typeof value === 'object' && value !== null) {\n const record = value as Record<string, unknown>;\n const token0 = record['token0'];\n const token1 = record['token1'];\n if (typeof token0 === 'string' && typeof token1 === 'string') return { token0, token1 };\n }\n throw new GSwapSDKError('UNORDERABLE_SYMBOLS', {\n received: Array.isArray(value) ? 'array' : value === null ? 'null' : typeof value,\n });\n}\n\n
|
|
1
|
+
{"version":3,"file":"ordering.js","names":[],"sources":["../../../src/utils/ordering.ts"],"sourcesContent":["import { GSwapSDKError } from '../classes/gswap_sdk_error.js';\nimport type { GalaChainTokenClassKey } from '../types/token.js';\nimport { splitClassKey } from './class_key.js';\n\n/** A symbol or a GalaChain token class key accepted by v2 operations. */\nexport type TokenRef = string | GalaChainTokenClassKey;\n\n/** Return two trading symbols in the contract's plain-string order. */\nexport function orderSymbols(a: string, b: string): [string, string] {\n return a < b ? [a, b] : [b, a];\n}\n\n/** Read an ordered symbol pair from a tuple or object returned by a boundary helper.\n *\n * @example\n * ```ts\n * const pair = readOrderedSymbols(['GALA', 'GUSDC']);\n * console.log(pair.token0);\n * ```\n */\nexport function readOrderedSymbols(value: unknown): { token0: string; token1: string } {\n if (Array.isArray(value) && value.length >= 2) {\n const token0 = (value as unknown[])[0];\n const token1 = (value as unknown[])[1];\n if (typeof token0 === 'string' && typeof token1 === 'string') return { token0, token1 };\n }\n if (typeof value === 'object' && value !== null) {\n const record = value as Record<string, unknown>;\n const token0 = record['token0'];\n const token1 = record['token1'];\n if (typeof token0 === 'string' && typeof token1 === 'string') return { token0, token1 };\n }\n throw new GSwapSDKError('UNORDERABLE_SYMBOLS', {\n received: Array.isArray(value) ? 'array' : value === null ? 'null' : typeof value,\n });\n}\n\n/**\n * Parse a pipe- or dollar-separated token class key.\n *\n * The LAST three segments are always `category`, `type` and `additionalKey`; everything before\n * them is the `collection`. Parsing from the right is required because real collections contain\n * the `$` delimiter themselves (for example `$GMUSIC`), so a plain left-to-right `split('$')`\n * shifts every field. Nothing here says anything about the token's symbol: a class key carries\n * no symbol, which must come from the token class or trading-symbol registry.\n *\n * @example\n * ```ts\n * parseTokenClassKey('$GMUSIC$Unit$none$none').collection; // '$GMUSIC'\n * ```\n */\nexport function parseTokenClassKey(tokenClassKey: TokenRef): GalaChainTokenClassKey {\n if (typeof tokenClassKey === 'object') {\n return {\n collection: tokenClassKey.collection,\n category: tokenClassKey.category,\n type: tokenClassKey.type,\n additionalKey: tokenClassKey.additionalKey,\n };\n }\n\n const parsed = splitClassKey(tokenClassKey, tokenClassKey.includes('|') ? '|' : '$');\n if (parsed === undefined) throw new GSwapSDKError('INVALID_TOKEN_CLASS_KEY', { tokenClassKey });\n return parsed;\n}\n"],"mappings":"6GAQA,SAAgB,EAAa,EAAW,EAA6B,CACnE,OAAO,EAAI,EAAI,CAAC,EAAG,CAAC,EAAI,CAAC,EAAG,CAAC,CAC/B,CAUA,SAAgB,EAAmB,EAAoD,CACrF,GAAI,MAAM,QAAQ,CAAK,GAAK,EAAM,QAAU,EAAG,CAC7C,IAAM,EAAU,EAAoB,GAC9B,EAAU,EAAoB,GACpC,GAAI,OAAO,GAAW,UAAY,OAAO,GAAW,SAAU,MAAO,CAAE,SAAQ,QAAO,CACxF,CACA,GAAI,OAAO,GAAU,UAAY,EAAgB,CAC/C,IAAM,EAAS,EACT,EAAS,EAAO,OAChB,EAAS,EAAO,OACtB,GAAI,OAAO,GAAW,UAAY,OAAO,GAAW,SAAU,MAAO,CAAE,SAAQ,QAAO,CACxF,CACA,MAAM,IAAI,EAAc,sBAAuB,CAC7C,SAAU,MAAM,QAAQ,CAAK,EAAI,QAAU,IAAU,KAAO,OAAS,OAAO,CAC9E,CAAC,CACH,CAgBA,SAAgB,EAAmB,EAAiD,CAClF,GAAI,OAAO,GAAkB,SAC3B,MAAO,CACL,WAAY,EAAc,WAC1B,SAAU,EAAc,SACxB,KAAM,EAAc,KACpB,cAAe,EAAc,aAC/B,EAGF,IAAM,EAAS,EAAc,EAAe,EAAc,SAAS,GAAG,EAAI,IAAM,GAAG,EACnF,GAAI,IAAW,IAAA,GAAW,MAAM,IAAI,EAAc,0BAA2B,CAAE,eAAc,CAAC,EAC9F,OAAO,CACT"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tick_align.js","names":[],"sources":["../../../src/utils/tick_align.ts"],"sourcesContent":["/**\n * Aligning a tick to a pool's tick spacing without throwing. This module imports nothing, so an\n * app shell (a price input that snaps to a tick) pays for a few dozen bytes, not for the error\n * class and catalog that `alignTickDown` and `alignTickUp` (in `ticks.ts`) carry because they\n * throw `INVALID_TICK_SPACING` for a spacing that is not a positive integer.\n */\n\n/** True for a tick spacing the contract can use: a positive integer. The one definition. */\nexport function hasValidSpacing(spacing: number): boolean {\n return Number.isInteger(spacing) && spacing > 0;\n}\n\n/**\n * {@link alignTickDown} that answers `NaN` instead of throwing: the greatest multiple of `spacing`\n * that is no greater than `tick`, or `NaN` when `spacing` is not a positive integer (a pool whose\n * spacing has not loaded yet) or `tick` is not a number. For an app shell and for a drag handler,\n * which must not throw while the spacing is missing. `alignTickDown` (the SDK's own call sites use\n * it) gives the same value and throws for the bad spacing.\n *\n * @example\n * ```ts\n * alignTickDownOrNaN(-61, 60); // -120: it floors, it does not truncate toward zero\n * alignTickDownOrNaN(100, 0); // NaN\n * ```\n */\nexport function alignTickDownOrNaN(tick: number, spacing: number): number {\n return hasValidSpacing(spacing) ? Math.floor(tick / spacing) * spacing : Number.NaN;\n}\n\n/**\n * {@link alignTickUp} that answers `NaN` instead of throwing: the least multiple of `spacing` that\n * is no less than `tick`, or `NaN` for a spacing that is not a positive integer. See\n * {@link alignTickDownOrNaN}.\n */\nexport function alignTickUpOrNaN(tick: number, spacing: number): number {\n return hasValidSpacing(spacing) ? Math.ceil(tick / spacing) * spacing : Number.NaN;\n}\n"],"mappings":"AAQA,SAAgB,EAAgB,EAA0B,CACxD,OAAO,OAAO,UAAU,CAAO,GAAK,EAAU,CAChD,CAeA,SAAgB,EAAmB,EAAc,EAAyB,CACxE,OAAO,EAAgB,CAAO,EAAI,KAAK,MAAM,EAAO,CAAO,EAAI,EAAU,GAC3E,CAOA,SAAgB,EAAiB,EAAc,EAAyB,CACtE,OAAO,EAAgB,CAAO,EAAI,KAAK,KAAK,EAAO,CAAO,EAAI,EAAU,GAC1E"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{isTickInRange as e}from"./tick_range.js";import t from"bignumber.js";import{Decimal as n}from"decimal.js";const r=-887272,i=887272,a=t.clone({DECIMAL_PLACES:60,ROUNDING_MODE:t.ROUND_DOWN}),o=n.clone({precision:60});function s(e){return new t(new o(1.0001).pow(e/2).toString())}function c(e){let t;try{t=new a(e)}catch{return NaN}if(!t.isFinite()||t.isLessThanOrEqualTo(0))return NaN;let n=Math.log(t.toNumber()**2)/Math.log(1.0001);if(!Number.isFinite(n))return NaN;let o=Math.min(i,Math.max(r,Math.floor(n)));for(;o<887272&&s(o+1).isLessThanOrEqualTo(t);)o+=1;for(;o>-887272&&s(o).isGreaterThan(t);)--o;return o}function l(e){return new t(new o(1.0001).pow(e).toString())}function u(t,n,r){return e(c(t),n,r)}function d(e,n={}){let r=typeof e==`string`?e:typeof e==`number`||t.isBigNumber(e)?e.toString():`NaN`,i=new o(r);if(!i.isFinite()||i.lessThanOrEqualTo(0)||!Number.isFinite(i.toNumber()))return NaN;let a=i.ln().dividedBy(new o(1.0001).ln()),s=(n.rounding===`nearest`?a.round():a.floor()).toNumber();return Number.isFinite(s)?s:NaN}export{o as DexDecimal,i as GC_MAX_TICK,r as GC_MIN_TICK,u as isSqrtPriceInRange,c as sqrtPriceToTick,d as tickFromPrice,l as tickToPrice,s as tickToSqrtPrice};
|
|
2
|
+
//# sourceMappingURL=tick_math.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tick_math.js","names":[],"sources":["../../../src/utils/tick_math.ts"],"sourcesContent":["import BigNumber from 'bignumber.js';\nimport { Decimal } from 'decimal.js';\nimport { isTickInRange, type TickInput } from './tick_range.js';\n\n/**\n * Tick and price arithmetic that never throws an SDK error: the contract's tick bounds, the\n * conversions between ticks and prices, and nothing that validates a range. It imports only the\n * two number libraries it computes with, never the `GSwapSDKError` class or the error catalog\n * (functions that must throw, `alignTickDown` and `assertTickRange`, are in `ticks.ts`), so an\n * app shell that prices a tick pays for the libraries and these few functions, not for ~25 kB of\n * error templates. A value that cannot be computed is `NaN`, never an exception.\n */\n\n/** Minimum tick supported by GalaChainDex. */\nexport const GC_MIN_TICK = -887272;\n/** Maximum tick supported by GalaChainDex. */\nexport const GC_MAX_TICK = 887272;\n\nconst DexMath = BigNumber.clone({\n DECIMAL_PLACES: 60,\n ROUNDING_MODE: BigNumber.ROUND_DOWN,\n});\n/** `decimal.js` at the 60 significant digits the contract's price math is exact to. */\nexport const DexDecimal = Decimal.clone({ precision: 60 });\n\n/** Convert a tick to the corresponding square-root price. */\nexport function tickToSqrtPrice(tick: number): BigNumber {\n return new BigNumber(new DexDecimal(1.0001).pow(tick / 2).toString());\n}\n\n/** Return the greatest tick whose square-root price is no greater than the input. */\nexport function sqrtPriceToTick(sqrtPrice: BigNumber.Value): number {\n let value: BigNumber;\n try {\n // bignumber.js 11+ throws on an unparseable value instead of constructing a NaN\n // instance — this function's contract is to signal an invalid input via NaN, not\n // to throw, so an unparseable value is just another case for the guard below.\n value = new DexMath(sqrtPrice);\n } catch {\n return Number.NaN;\n }\n if (!value.isFinite() || value.isLessThanOrEqualTo(0)) return Number.NaN;\n\n const approximate = Math.log(value.toNumber() ** 2) / Math.log(1.0001);\n if (!Number.isFinite(approximate)) return Number.NaN;\n // Clamp the seed before the correction loops below — an unclamped seed derived from an\n // out-of-range sqrtPrice (extreme but finite, e.g. 1e20) leaves the loops running past\n // GC_MIN_TICK/GC_MAX_TICK for a long time; an infinite (overflowed) seed would never\n // enter the increasing loop and never terminate the decreasing one.\n let tick = Math.min(GC_MAX_TICK, Math.max(GC_MIN_TICK, Math.floor(approximate)));\n while (tick < GC_MAX_TICK && tickToSqrtPrice(tick + 1).isLessThanOrEqualTo(value)) tick += 1;\n while (tick > GC_MIN_TICK && tickToSqrtPrice(tick).isGreaterThan(value)) tick -= 1;\n return tick;\n}\n\n/**\n * The token1/token0 price at `tick`: `1.0001 ** tick`, exact to 60 significant digits (the lower\n * edge of the tick's price range; `tickToSqrtPrice(tick) ** 2`). A tick outside the contract's\n * bounds is still computed; ask `assertTickRange` first when that matters. A non-integer tick is\n * computed as given.\n *\n * Use `.toNumber()` for a display value (it differs from `Math.exp(tick * Math.log(1.0001))` in\n * the last digits of a double: that expression multiplies in floating point first).\n */\nexport function tickToPrice(tick: number): BigNumber {\n return new BigNumber(new DexDecimal(1.0001).pow(tick).toString());\n}\n\n/**\n * {@link isTickInRange} for a pool's square-root price instead of its tick (a position row that\n * carries `sqrtPrice`, `tickLower` and `tickUpper`). The tick is derived from the price exactly as\n * the contract does (`sqrtPriceToTick`: the greatest tick whose square-root price is no greater\n * than the price, exact to 60 digits), then the contract's rule applies: lower tick inclusive,\n * upper tick exclusive. So a pool sitting exactly on `tickLower`'s price is in range, and exactly\n * on `tickUpper`'s price is not, and no float rounding can flip either.\n *\n * `false` for a price that is not positive and finite (not loaded, zero, `NaN`): it says nothing\n * about the position, so an app that must show a neutral state checks for the missing price\n * itself first. `tickLower` and `tickUpper` follow {@link isTickInRange}.\n *\n * @example\n * ```ts\n * const inRange = isSqrtPriceInRange(position.sqrtPrice, position.tickLower, position.tickUpper);\n * ```\n */\nexport function isSqrtPriceInRange(\n sqrtPrice: BigNumber.Value,\n tickLower: TickInput,\n tickUpper: TickInput,\n): boolean {\n return isTickInRange(sqrtPriceToTick(sqrtPrice), tickLower, tickUpper);\n}\n\n/** How {@link tickFromPrice} turns the exact logarithm into an integer tick. */\nexport interface TickFromPriceOptions {\n /**\n * `'floor'` (default): the greatest tick whose price is no greater than `price`, which is how\n * the contract places a price. `'nearest'`: the closest tick, a half rounding away from zero.\n * That is what a price INPUT wants (a person types a price, the field snaps to the nearest\n * tick), and it differs from `'floor'` for any price in the upper half of a tick's range.\n */\n rounding?: 'floor' | 'nearest' | undefined;\n}\n\n/**\n * Return the integer tick for a token1/token0 `price`: by default the greatest tick whose price\n * is no greater than `price`; `{ rounding: 'nearest' }` the closest tick. `NaN` for a price that\n * is not positive and finite.\n */\nexport function tickFromPrice(price: BigNumber.Value, options: TickFromPriceOptions = {}): number {\n const priceText =\n typeof price === 'string'\n ? price\n : typeof price === 'number'\n ? price.toString()\n : BigNumber.isBigNumber(price)\n ? price.toString()\n : 'NaN';\n const value = new DexDecimal(priceText);\n if (!value.isFinite() || value.lessThanOrEqualTo(0)) return Number.NaN;\n if (!Number.isFinite(value.toNumber())) return Number.NaN;\n const exact = value.ln().dividedBy(new DexDecimal(1.0001).ln());\n const tick = (options.rounding === 'nearest' ? exact.round() : exact.floor()).toNumber();\n return Number.isFinite(tick) ? tick : Number.NaN;\n}\n"],"mappings":"iHAcA,MAAa,EAAc,QAEd,EAAc,OAErB,EAAU,EAAU,MAAM,CAC9B,eAAgB,GAChB,cAAe,EAAU,UAC3B,CAAC,EAEY,EAAa,EAAQ,MAAM,CAAE,UAAW,EAAG,CAAC,EAGzD,SAAgB,EAAgB,EAAyB,CACvD,OAAO,IAAI,EAAU,IAAI,EAAW,MAAM,CAAC,CAAC,IAAI,EAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CACtE,CAGA,SAAgB,EAAgB,EAAoC,CAClE,IAAI,EACJ,GAAI,CAIF,EAAQ,IAAI,EAAQ,CAAS,CAC/B,MAAQ,CACN,MAAO,IACT,CACA,GAAI,CAAC,EAAM,SAAS,GAAK,EAAM,oBAAoB,CAAC,EAAG,MAAO,KAE9D,IAAM,EAAc,KAAK,IAAI,EAAM,SAAS,GAAK,CAAC,EAAI,KAAK,IAAI,MAAM,EACrE,GAAI,CAAC,OAAO,SAAS,CAAW,EAAG,MAAO,KAK1C,IAAI,EAAO,KAAK,IAAI,EAAa,KAAK,IAAI,EAAa,KAAK,MAAM,CAAW,CAAC,CAAC,EAC/E,KAAO,EAAA,QAAsB,EAAgB,EAAO,CAAC,CAAC,CAAC,oBAAoB,CAAK,GAAG,GAAQ,EAC3F,KAAO,EAAA,SAAsB,EAAgB,CAAI,CAAC,CAAC,cAAc,CAAK,GAAG,IACzE,OAAO,CACT,CAWA,SAAgB,EAAY,EAAyB,CACnD,OAAO,IAAI,EAAU,IAAI,EAAW,MAAM,CAAC,CAAC,IAAI,CAAI,CAAC,CAAC,SAAS,CAAC,CAClE,CAmBA,SAAgB,EACd,EACA,EACA,EACS,CACT,OAAO,EAAc,EAAgB,CAAS,EAAG,EAAW,CAAS,CACvE,CAkBA,SAAgB,EAAc,EAAwB,EAAgC,CAAC,EAAW,CAChG,IAAM,EACJ,OAAO,GAAU,SACb,EACA,OAAO,GAAU,UAEf,EAAU,YAAY,CAAK,EAD3B,EAAM,SAAS,EAGb,MACJ,EAAQ,IAAI,EAAW,CAAS,EAEtC,GADI,CAAC,EAAM,SAAS,GAAK,EAAM,kBAAkB,CAAC,GAC9C,CAAC,OAAO,SAAS,EAAM,SAAS,CAAC,EAAG,MAAO,KAC/C,IAAM,EAAQ,EAAM,GAAG,CAAC,CAAC,UAAU,IAAI,EAAW,MAAM,CAAC,CAAC,GAAG,CAAC,EACxD,GAAQ,EAAQ,WAAa,UAAY,EAAM,MAAM,EAAI,EAAM,MAAM,EAAA,CAAG,SAAS,EACvF,OAAO,OAAO,SAAS,CAAI,EAAI,EAAO,GACxC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tick_range.js","names":[],"sources":["../../../src/utils/tick_range.ts"],"sourcesContent":["/**\n * Whether a pool's current tick lies inside a position's range, by the contract's own rule. This\n * module imports nothing, so an app shell (a position badge, a filter) pays for a few dozen bytes.\n */\n\n/** A tick as the chain and the backends write it: a number, or the decimal string of one. */\nexport type TickInput = number | string;\n\nfunction integerTickOf(tick: TickInput): number {\n if (typeof tick === 'string') return tick.trim() === '' ? Number.NaN : Number(tick);\n return tick;\n}\n\n/**\n * True when a position over `[tickLower, tickUpper)` is IN RANGE at `currentTick`: the lower tick\n * is INCLUSIVE and the upper tick is EXCLUSIVE. That is the GalaChainDex contract's rule\n * (`DexV3Pool`: `tickCurrent >= tickLower && tickCurrent < tickUpper` is the case in which a\n * position holds both tokens and earns fees). A pool whose tick equals `tickUpper` is already out\n * of range: the position is entirely token1 there, and a swap through the tick has crossed it.\n *\n * `currentTick` is the pool's tick (`slot0.tick`, the greatest tick whose price is no greater than\n * the pool's price), not a rounded display value. A tick may be a number or the decimal string a\n * backend returns. Anything that is not an integer (`NaN`, `undefined`-like, an empty string, a\n * fraction) makes the answer `false`: an app that must tell \"not loaded yet\" from \"out of range\"\n * checks for the missing value itself first.\n *\n * Do NOT decide range by comparing a price with a tick's price (`tickToPrice`) using `<=` on both\n * ends, or by mixing a square-root price with a price. A price exactly on a tick's boundary is\n * then in range at the upper edge (the contract says it is not), and two representations of the\n * same boundary can round to opposite sides. For a pool price use {@link isSqrtPriceInRange}, which\n * derives the tick the way the contract does and then applies this rule.\n *\n * @example\n * ```ts\n * isTickInRange(-60, -60, 60); // true: the lower tick is inclusive\n * isTickInRange(60, -60, 60); // false: the upper tick is exclusive\n * ```\n */\nexport function isTickInRange(\n currentTick: TickInput,\n tickLower: TickInput,\n tickUpper: TickInput,\n): boolean {\n const current = integerTickOf(currentTick);\n const lower = integerTickOf(tickLower);\n const upper = integerTickOf(tickUpper);\n if (!Number.isInteger(current) || !Number.isInteger(lower) || !Number.isInteger(upper)) {\n return false;\n }\n return current >= lower && current < upper;\n}\n"],"mappings":"AAQA,SAAS,EAAc,EAAyB,CAE9C,OADI,OAAO,GAAS,SAAiB,EAAK,KAAK,IAAM,GAAK,IAAa,OAAO,CAAI,EAC3E,CACT,CA2BA,SAAgB,EACd,EACA,EACA,EACS,CACT,IAAM,EAAU,EAAc,CAAW,EACnC,EAAQ,EAAc,CAAS,EAC/B,EAAQ,EAAc,CAAS,EAIrC,MAHI,CAAC,OAAO,UAAU,CAAO,GAAK,CAAC,OAAO,UAAU,CAAK,GAAK,CAAC,OAAO,UAAU,CAAK,EAC5E,GAEF,GAAW,GAAS,EAAU,CACvC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{ALLOWED_FEE_TIERS_TEXT as e}from"../classes/error_catalog.js";import{GSwapSDKError as t}from"../classes/gswap_sdk_error.js";import{TICK_SPACING_BY_FEE as n}from"../types/fees.js";import r
|
|
1
|
+
import{ALLOWED_FEE_TIERS_TEXT as e}from"../classes/error_catalog.js";import{GSwapSDKError as t}from"../classes/gswap_sdk_error.js";import{TICK_SPACING_BY_FEE as n}from"../types/fees.js";import{DexDecimal as r,GC_MAX_TICK as i,GC_MIN_TICK as a,sqrtPriceToTick as o}from"./tick_math.js";import{alignTickDownOrNaN as s,alignTickUpOrNaN as c,hasValidSpacing as l}from"./tick_align.js";function u(e,t){return p(t),s(e,t)}function d(e,t){return p(t),c(e,t)}function f(r,o,s){let c=n[s];if(c===void 0)throw new t(`INVALID_FEE`,{field:`fee`,value:s,allowed:e});if(!Number.isInteger(r)||!Number.isInteger(o))throw new t(`TICKS_NOT_INTEGERS`,{tickLower:r,tickUpper:o});if(r>=o)throw new t(`INVALID_TICK_RANGE`,{tickLower:r,tickUpper:o});if(r<-887272||o>887272)throw new t(`TICKS_OUT_OF_BOUNDS`,{tickLower:r,tickUpper:o,minTick:a,maxTick:i});if(r%c!==0||o%c!==0)throw new t(`TICKS_NOT_ALIGNED`,{tickLower:r,tickUpper:o,spacing:c})}function p(e){if(!l(e))throw new t(`INVALID_TICK_SPACING`,{value:e})}function m(e){if(e.flippedFromRequest!==!0)return{sqrtPrice:e.sqrtPrice,tick:e.tick};let n=new r(1).dividedBy(new r(e.sqrtPrice));if(!n.isFinite()||n.lessThanOrEqualTo(0))throw new t(`INVALID_SQRT_PRICE`,{sqrtPrice:e.sqrtPrice});let i=n.toString();return{sqrtPrice:i,tick:o(i)}}export{u as alignTickDown,d as alignTickUp,f as assertTickRange,m as frameSlot0};
|
|
2
2
|
//# sourceMappingURL=ticks.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ticks.js","names":[],"sources":["../../../src/utils/ticks.ts"],"sourcesContent":["import BigNumber from 'bignumber.js';\nimport { Decimal } from 'decimal.js';\nimport { ALLOWED_FEE_TIERS_TEXT } from '../classes/error_catalog.js';\nimport { GSwapSDKError } from '../classes/gswap_sdk_error.js';\nimport type { FEE_TIER } from '../types/fees.js';\nimport { TICK_SPACING_BY_FEE } from '../types/fees.js';\n\n/** Minimum tick supported by GalaChainDex. */\nexport const GC_MIN_TICK = -887272;\n/** Maximum tick supported by GalaChainDex. */\nexport const GC_MAX_TICK = 887272;\n\nconst DexMath = BigNumber.clone({\n DECIMAL_PLACES: 60,\n ROUNDING_MODE: BigNumber.ROUND_DOWN,\n});\nconst DexDecimal = Decimal.clone({ precision: 60 });\n\n/** Convert a tick to the corresponding square-root price. */\nexport function tickToSqrtPrice(tick: number): BigNumber {\n return new BigNumber(new DexDecimal(1.0001).pow(tick / 2).toString());\n}\n\n/** Return the greatest tick whose square-root price is no greater than the input. */\nexport function sqrtPriceToTick(sqrtPrice: BigNumber.Value): number {\n let value: BigNumber;\n try {\n // bignumber.js 11+ throws on an unparseable value instead of constructing a NaN\n // instance — this function's contract is to signal an invalid input via NaN, not\n // to throw, so an unparseable value is just another case for the guard below.\n value = new DexMath(sqrtPrice);\n } catch {\n return Number.NaN;\n }\n if (!value.isFinite() || value.isLessThanOrEqualTo(0)) return Number.NaN;\n\n const approximate = Math.log(value.toNumber() ** 2) / Math.log(1.0001);\n if (!Number.isFinite(approximate)) return Number.NaN;\n // Clamp the seed before the correction loops below — an unclamped seed derived from an\n // out-of-range sqrtPrice (extreme but finite, e.g. 1e20) leaves the loops running past\n // GC_MIN_TICK/GC_MAX_TICK for a long time; an infinite (overflowed) seed would never\n // enter the increasing loop and never terminate the decreasing one.\n let tick = Math.min(GC_MAX_TICK, Math.max(GC_MIN_TICK, Math.floor(approximate)));\n while (tick < GC_MAX_TICK && tickToSqrtPrice(tick + 1).isLessThanOrEqualTo(value)) tick += 1;\n while (tick > GC_MIN_TICK && tickToSqrtPrice(tick).isGreaterThan(value)) tick -= 1;\n return tick;\n}\n\n/**\n * The token1/token0 price at `tick`: `1.0001 ** tick`, exact to 60 significant digits (the lower\n * edge of the tick's price range; `tickToSqrtPrice(tick) ** 2`). A tick outside the contract's\n * bounds is still computed; ask `assertTickRange` first when that matters. A non-integer tick is\n * computed as given.\n *\n * Use `.toNumber()` for a display value (it differs from `Math.exp(tick * Math.log(1.0001))` in\n * the last digits of a double: that expression multiplies in floating point first).\n */\nexport function tickToPrice(tick: number): BigNumber {\n return new BigNumber(new DexDecimal(1.0001).pow(tick).toString());\n}\n\n/** How {@link tickFromPrice} turns the exact logarithm into an integer tick. */\nexport interface TickFromPriceOptions {\n /**\n * `'floor'` (default): the greatest tick whose price is no greater than `price`, which is how\n * the contract places a price. `'nearest'`: the closest tick, a half rounding away from zero.\n * That is what a price INPUT wants (a person types a price, the field snaps to the nearest\n * tick), and it differs from `'floor'` for any price in the upper half of a tick's range.\n */\n rounding?: 'floor' | 'nearest' | undefined;\n}\n\n/**\n * Return the integer tick for a token1/token0 `price`: by default the greatest tick whose price\n * is no greater than `price`; `{ rounding: 'nearest' }` the closest tick. `NaN` for a price that\n * is not positive and finite.\n */\nexport function tickFromPrice(price: BigNumber.Value, options: TickFromPriceOptions = {}): number {\n const priceText =\n typeof price === 'string'\n ? price\n : typeof price === 'number'\n ? price.toString()\n : BigNumber.isBigNumber(price)\n ? price.toString()\n : 'NaN';\n const value = new DexDecimal(priceText);\n if (!value.isFinite() || value.lessThanOrEqualTo(0)) return Number.NaN;\n if (!Number.isFinite(value.toNumber())) return Number.NaN;\n const exact = value.ln().dividedBy(new DexDecimal(1.0001).ln());\n const tick = (options.rounding === 'nearest' ? exact.round() : exact.floor()).toNumber();\n return Number.isFinite(tick) ? tick : Number.NaN;\n}\n\n/** Align a tick down to the nearest multiple of a positive spacing. */\nexport function alignTickDown(tick: number, spacing: number): number {\n assertSpacing(spacing);\n return Math.floor(tick / spacing) * spacing;\n}\n\n/** Align a tick up to the nearest multiple of a positive spacing. */\nexport function alignTickUp(tick: number, spacing: number): number {\n assertSpacing(spacing);\n return Math.ceil(tick / spacing) * spacing;\n}\n\n/** Validate a position range against the global tick bounds and fee-tier spacing. */\nexport function assertTickRange(lower: number, upper: number, fee: FEE_TIER): void {\n const spacing = TICK_SPACING_BY_FEE[fee];\n if (spacing === undefined) {\n throw new GSwapSDKError('INVALID_FEE', {\n field: 'fee',\n value: fee,\n allowed: ALLOWED_FEE_TIERS_TEXT,\n });\n }\n if (!Number.isInteger(lower) || !Number.isInteger(upper)) {\n throw new GSwapSDKError('TICKS_NOT_INTEGERS', { tickLower: lower, tickUpper: upper });\n }\n if (lower >= upper) {\n throw new GSwapSDKError('INVALID_TICK_RANGE', { tickLower: lower, tickUpper: upper });\n }\n if (lower < GC_MIN_TICK || upper > GC_MAX_TICK) {\n throw new GSwapSDKError('TICKS_OUT_OF_BOUNDS', {\n tickLower: lower,\n tickUpper: upper,\n minTick: GC_MIN_TICK,\n maxTick: GC_MAX_TICK,\n });\n }\n if (lower % spacing !== 0 || upper % spacing !== 0) {\n throw new GSwapSDKError('TICKS_NOT_ALIGNED', {\n tickLower: lower,\n tickUpper: upper,\n spacing,\n });\n }\n}\n\nfunction assertSpacing(spacing: number): void {\n if (!Number.isInteger(spacing) || spacing <= 0) {\n throw new GSwapSDKError('INVALID_TICK_SPACING', { value: spacing });\n }\n}\n\n/** A pool's current price in the frame of the request that asked for it. */\nexport interface FramedSlot0 {\n /** The square-root price, token1 per token0 in the REQUESTED order. */\n sqrtPrice: string;\n /** The greatest tick whose square-root price is no greater than `sqrtPrice`. */\n tick: number;\n}\n\n/**\n * Puts a `slot0` answer into the frame of the request. The backend always answers in the pool's\n * canonical order and says so with `flippedFromRequest`; when the caller asked for the reverse\n * order the price is inverted here (at 60 significant digits, so a very large or very small\n * reciprocal keeps its precision) and the tick is re-derived from the inverted price with the\n * contract's greatest-tick rule. Negating the tick instead is wrong for a price between ticks\n * (and `-t - 1` is wrong for a price exactly on a tick: sqrtPrice 1 / tick 0 stays 1 / 0).\n *\n * @example\n * ```ts\n * const slot0 = await gSwap.pools.getSlot0('A', 'B', 500);\n * const { sqrtPrice, tick } = frameSlot0(slot0);\n * ```\n */\nexport function frameSlot0(slot0: {\n sqrtPrice: string;\n tick: number;\n flippedFromRequest?: boolean | undefined;\n}): FramedSlot0 {\n if (slot0.flippedFromRequest !== true) return { sqrtPrice: slot0.sqrtPrice, tick: slot0.tick };\n const inverted = new DexDecimal(1).dividedBy(new DexDecimal(slot0.sqrtPrice));\n if (!inverted.isFinite() || inverted.lessThanOrEqualTo(0)) {\n throw new GSwapSDKError('INVALID_SQRT_PRICE', { sqrtPrice: slot0.sqrtPrice });\n }\n const sqrtPrice = inverted.toString();\n return { sqrtPrice, tick: sqrtPriceToTick(sqrtPrice) };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ticks.js","names":[],"sources":["../../../src/utils/ticks.ts"],"sourcesContent":["import { ALLOWED_FEE_TIERS_TEXT } from '../classes/error_catalog.js';\nimport { GSwapSDKError } from '../classes/gswap_sdk_error.js';\nimport type { FEE_TIER } from '../types/fees.js';\nimport { TICK_SPACING_BY_FEE } from '../types/fees.js';\nimport { alignTickDownOrNaN, alignTickUpOrNaN, hasValidSpacing } from './tick_align.js';\nimport { DexDecimal, GC_MAX_TICK, GC_MIN_TICK, sqrtPriceToTick } from './tick_math.js';\n\n/**\n * The tick functions that THROW an SDK error on a bad input (`alignTickDown`, `alignTickUp`,\n * `assertTickRange`, `frameSlot0`). The arithmetic that never throws (`tickToPrice`,\n * `tickFromPrice`, `tickToSqrtPrice`, `sqrtPriceToTick`, the bounds) is in `tick_math.ts`, which\n * does not import the error class or the catalog; import it from the package entry as usual.\n */\n\n/** Align a tick down to the nearest multiple of a positive spacing. */\nexport function alignTickDown(tick: number, spacing: number): number {\n assertSpacing(spacing);\n return alignTickDownOrNaN(tick, spacing);\n}\n\n/** Align a tick up to the nearest multiple of a positive spacing. */\nexport function alignTickUp(tick: number, spacing: number): number {\n assertSpacing(spacing);\n return alignTickUpOrNaN(tick, spacing);\n}\n\n/** Validate a position range against the global tick bounds and fee-tier spacing. */\nexport function assertTickRange(lower: number, upper: number, fee: FEE_TIER): void {\n const spacing = TICK_SPACING_BY_FEE[fee];\n if (spacing === undefined) {\n throw new GSwapSDKError('INVALID_FEE', {\n field: 'fee',\n value: fee,\n allowed: ALLOWED_FEE_TIERS_TEXT,\n });\n }\n if (!Number.isInteger(lower) || !Number.isInteger(upper)) {\n throw new GSwapSDKError('TICKS_NOT_INTEGERS', { tickLower: lower, tickUpper: upper });\n }\n if (lower >= upper) {\n throw new GSwapSDKError('INVALID_TICK_RANGE', { tickLower: lower, tickUpper: upper });\n }\n if (lower < GC_MIN_TICK || upper > GC_MAX_TICK) {\n throw new GSwapSDKError('TICKS_OUT_OF_BOUNDS', {\n tickLower: lower,\n tickUpper: upper,\n minTick: GC_MIN_TICK,\n maxTick: GC_MAX_TICK,\n });\n }\n if (lower % spacing !== 0 || upper % spacing !== 0) {\n throw new GSwapSDKError('TICKS_NOT_ALIGNED', {\n tickLower: lower,\n tickUpper: upper,\n spacing,\n });\n }\n}\n\nfunction assertSpacing(spacing: number): void {\n if (!hasValidSpacing(spacing)) {\n throw new GSwapSDKError('INVALID_TICK_SPACING', { value: spacing });\n }\n}\n\n/** A pool's current price in the frame of the request that asked for it. */\nexport interface FramedSlot0 {\n /** The square-root price, token1 per token0 in the REQUESTED order. */\n sqrtPrice: string;\n /** The greatest tick whose square-root price is no greater than `sqrtPrice`. */\n tick: number;\n}\n\n/**\n * Puts a `slot0` answer into the frame of the request. The backend always answers in the pool's\n * canonical order and says so with `flippedFromRequest`; when the caller asked for the reverse\n * order the price is inverted here (at 60 significant digits, so a very large or very small\n * reciprocal keeps its precision) and the tick is re-derived from the inverted price with the\n * contract's greatest-tick rule. Negating the tick instead is wrong for a price between ticks\n * (and `-t - 1` is wrong for a price exactly on a tick: sqrtPrice 1 / tick 0 stays 1 / 0).\n *\n * @example\n * ```ts\n * const slot0 = await gSwap.pools.getSlot0('A', 'B', 500);\n * const { sqrtPrice, tick } = frameSlot0(slot0);\n * ```\n */\nexport function frameSlot0(slot0: {\n sqrtPrice: string;\n tick: number;\n flippedFromRequest?: boolean | undefined;\n}): FramedSlot0 {\n if (slot0.flippedFromRequest !== true) return { sqrtPrice: slot0.sqrtPrice, tick: slot0.tick };\n const inverted = new DexDecimal(1).dividedBy(new DexDecimal(slot0.sqrtPrice));\n if (!inverted.isFinite() || inverted.lessThanOrEqualTo(0)) {\n throw new GSwapSDKError('INVALID_SQRT_PRICE', { sqrtPrice: slot0.sqrtPrice });\n }\n const sqrtPrice = inverted.toString();\n return { sqrtPrice, tick: sqrtPriceToTick(sqrtPrice) };\n}\n"],"mappings":"6XAeA,SAAgB,EAAc,EAAc,EAAyB,CAEnE,OADA,EAAc,CAAO,EACd,EAAmB,EAAM,CAAO,CACzC,CAGA,SAAgB,EAAY,EAAc,EAAyB,CAEjE,OADA,EAAc,CAAO,EACd,EAAiB,EAAM,CAAO,CACvC,CAGA,SAAgB,EAAgB,EAAe,EAAe,EAAqB,CACjF,IAAM,EAAU,EAAoB,GACpC,GAAI,IAAY,IAAA,GACd,MAAM,IAAI,EAAc,cAAe,CACrC,MAAO,MACP,MAAO,EACP,QAAS,CACX,CAAC,EAEH,GAAI,CAAC,OAAO,UAAU,CAAK,GAAK,CAAC,OAAO,UAAU,CAAK,EACrD,MAAM,IAAI,EAAc,qBAAsB,CAAE,UAAW,EAAO,UAAW,CAAM,CAAC,EAEtF,GAAI,GAAS,EACX,MAAM,IAAI,EAAc,qBAAsB,CAAE,UAAW,EAAO,UAAW,CAAM,CAAC,EAEtF,GAAI,EAAA,SAAuB,EAAA,OACzB,MAAM,IAAI,EAAc,sBAAuB,CAC7C,UAAW,EACX,UAAW,EACX,QAAS,EACT,QAAS,CACX,CAAC,EAEH,GAAI,EAAQ,IAAY,GAAK,EAAQ,IAAY,EAC/C,MAAM,IAAI,EAAc,oBAAqB,CAC3C,UAAW,EACX,UAAW,EACX,SACF,CAAC,CAEL,CAEA,SAAS,EAAc,EAAuB,CAC5C,GAAI,CAAC,EAAgB,CAAO,EAC1B,MAAM,IAAI,EAAc,uBAAwB,CAAE,MAAO,CAAQ,CAAC,CAEtE,CAwBA,SAAgB,EAAW,EAIX,CACd,GAAI,EAAM,qBAAuB,GAAM,MAAO,CAAE,UAAW,EAAM,UAAW,KAAM,EAAM,IAAK,EAC7F,IAAM,EAAW,IAAI,EAAW,CAAC,CAAC,CAAC,UAAU,IAAI,EAAW,EAAM,SAAS,CAAC,EAC5E,GAAI,CAAC,EAAS,SAAS,GAAK,EAAS,kBAAkB,CAAC,EACtD,MAAM,IAAI,EAAc,qBAAsB,CAAE,UAAW,EAAM,SAAU,CAAC,EAE9E,IAAM,EAAY,EAAS,SAAS,EACpC,MAAO,CAAE,YAAW,KAAM,EAAgB,CAAS,CAAE,CACvD"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{GSwapSDKError as e,parseJson as t}from"../classes/gswap_sdk_error.js";import"../version.js";function n(e,t){if(t===1/0)return()=>void 0;let n=setTimeout(()=>e.abort(),t);return()=>clearTimeout(n)}function r(t,n){if(t===1/0||Number.isFinite(t)&&t>0)return t;throw new e(`INVALID_TIMEOUT`,{field:n,value:t})}const i=/[A-Za-z0-9\-._~!'()*$|:,]/u;function a(e){let t=``;for(let n of e)n===` `?t+=`+`:i.test(n)?t+=n:t+=o(n);return t}function o(e){try{return encodeURIComponent(e)}catch{return`%EF%BF%BD`}}function s(e,t=`standard`){return t===`readable`?Object.entries(e).map(([e,t])=>`${a(e)}=${a(t)}`).join(`&`):new URLSearchParams(e).toString()}function c(e,t={}){let n=/* @__PURE__ */ new Map,r=(e,t=!1)=>{for(let[r,i]of Object.entries(e??{}))t&&n.delete(r.toLowerCase()),n.set(r.toLowerCase(),[r,i])};return e?.userAgent!==!1&&r({"User-Agent":e?.userAgent??`GalaChain-SDK/1.2.0
|
|
1
|
+
import{GSwapSDKError as e,parseJson as t}from"../classes/gswap_sdk_error.js";import"../version.js";function n(e,t){if(t===1/0)return()=>void 0;let n=setTimeout(()=>e.abort(),t);return()=>clearTimeout(n)}function r(t,n){if(t===1/0||Number.isFinite(t)&&t>0)return t;throw new e(`INVALID_TIMEOUT`,{field:n,value:t})}const i=/[A-Za-z0-9\-._~!'()*$|:,]/u;function a(e){let t=``;for(let n of e)n===` `?t+=`+`:i.test(n)?t+=n:t+=o(n);return t}function o(e){try{return encodeURIComponent(e)}catch{return`%EF%BF%BD`}}function s(e,t=`standard`){return t===`readable`?Object.entries(e).map(([e,t])=>`${a(e)}=${a(t)}`).join(`&`):new URLSearchParams(e).toString()}function c(e,t={}){let n=/* @__PURE__ */ new Map,r=(e,t=!1)=>{for(let[r,i]of Object.entries(e??{}))t&&n.delete(r.toLowerCase()),n.set(r.toLowerCase(),[r,i])};return e?.userAgent!==!1&&r({"User-Agent":e?.userAgent??`GalaChain-SDK/1.2.0`}),r(t.defaults),r(e?.headers),t.json===!0&&!n.has(`content-type`)&&r({"Content-Type":`application/json`}),r(t.fixed,!0),Object.fromEntries(n.values())}const l=async(t,n)=>{try{return await fetch(t,n)}catch(r){throw n?.signal?.aborted===!0?r:new e(`TRANSPORT_ERROR`,{url:t},{url:t,cause:r})}};async function u(t,r,i,a){let o=new AbortController,s=n(o,a),c=i.signal,l=()=>o.abort();c?.addEventListener(`abort`,l,{once:!0});try{return await t(r,{...i,signal:o.signal})}catch(t){throw c?.aborted===!0?t:o.signal.aborted?new e(`REQUEST_TIMEOUT`,{url:r,timeoutMs:a},{url:r,cause:t}):t}finally{s(),c?.removeEventListener(`abort`,l)}}async function d(e){let n=await e.text();return t(n)??n}function f(e,t){let n=e.headers;if(n===void 0)return;if(h(n)){let e=n.get(t);return e===null||e===``?void 0:e}let r=Object.entries(n).find(([e])=>e.toLowerCase()===t.toLowerCase())?.[1];return r===void 0||r===``?void 0:r}function p(e){let t=f(e,`retry-after`);if(t===void 0)return;let n=Number(t);if(Number.isFinite(n)&&n>=0)return n*1e3;let r=Date.parse(t);return Number.isNaN(r)?void 0:Math.max(0,r-Date.now())}function m(e,t=`x-block-number`){let n=f(e,t);if(n===void 0)return;let r=Number(n);return Number.isInteger(r)&&r>=0?r:void 0}function h(e){return`get`in e&&typeof e.get==`function`}export{n as armTimeout,s as buildQueryString,c as buildRequestHeaders,l as fetchRequestor,m as readBlockNumberHeader,f as readHeader,d as readResponseBody,p as readRetryAfter,u as requestWithTimeout,r as validateTimeoutMs};
|
|
2
2
|
//# sourceMappingURL=transport.js.map
|