@gala-chain/gswap-sdk 2.0.0 → 2.1.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +8 -2
  2. package/dist/browser/classes/apply_results.js.map +1 -1
  3. package/dist/browser/classes/assets.js.map +1 -1
  4. package/dist/browser/classes/dex_fees.js +1 -1
  5. package/dist/browser/classes/dex_fees.js.map +1 -1
  6. package/dist/browser/classes/gateway.js +1 -1
  7. package/dist/browser/classes/gateway.js.map +1 -1
  8. package/dist/browser/classes/gdex_stream.js +1 -1
  9. package/dist/browser/classes/gdex_stream.js.map +1 -1
  10. package/dist/browser/classes/gswap.js +1 -1
  11. package/dist/browser/classes/gswap.js.map +1 -1
  12. package/dist/browser/classes/gswap_error_catalog.js +1 -1
  13. package/dist/browser/classes/gswap_error_catalog.js.map +1 -1
  14. package/dist/browser/classes/gswap_error_phase_table.js +1 -1
  15. package/dist/browser/classes/gswap_error_phase_table.js.map +1 -1
  16. package/dist/browser/classes/gswap_sdk_error.js +1 -1
  17. package/dist/browser/classes/gswap_sdk_error.js.map +1 -1
  18. package/dist/browser/classes/http_client.js.map +1 -1
  19. package/dist/browser/classes/leaderboard.js.map +1 -1
  20. package/dist/browser/classes/market.js.map +1 -1
  21. package/dist/browser/classes/onramper.js.map +1 -1
  22. package/dist/browser/classes/pending_swap_fate.js.map +1 -1
  23. package/dist/browser/classes/pending_swaps.js +1 -1
  24. package/dist/browser/classes/pending_swaps.js.map +1 -1
  25. package/dist/browser/classes/pools.js +1 -1
  26. package/dist/browser/classes/pools.js.map +1 -1
  27. package/dist/browser/classes/positions.js +1 -1
  28. package/dist/browser/classes/positions.js.map +1 -1
  29. package/dist/browser/classes/quoting.js +1 -1
  30. package/dist/browser/classes/quoting.js.map +1 -1
  31. package/dist/browser/classes/shadow_submit.js.map +1 -1
  32. package/dist/browser/classes/storage_adapters.js.map +1 -1
  33. package/dist/browser/classes/submitted_transaction.js +1 -1
  34. package/dist/browser/classes/submitted_transaction.js.map +1 -1
  35. package/dist/browser/classes/swaps.js +1 -1
  36. package/dist/browser/classes/swaps.js.map +1 -1
  37. package/dist/browser/classes/symbols.js +1 -1
  38. package/dist/browser/classes/symbols.js.map +1 -1
  39. package/dist/browser/types/env.js.map +1 -1
  40. package/dist/browser/types/fees.js.map +1 -1
  41. package/dist/browser/types/market.js.map +1 -1
  42. package/dist/browser/utils/chain_contract.js +2 -0
  43. package/dist/browser/utils/chain_contract.js.map +1 -0
  44. package/dist/browser/utils/classification.js.map +1 -1
  45. package/dist/browser/utils/confirmation_schedule.js.map +1 -1
  46. package/dist/browser/utils/confirmation_state.js.map +1 -1
  47. package/dist/browser/utils/debug.js.map +1 -1
  48. package/dist/browser/utils/dto_builders.js.map +1 -1
  49. package/dist/browser/utils/failure_classification.js +1 -1
  50. package/dist/browser/utils/failure_classification.js.map +1 -1
  51. package/dist/browser/utils/failure_guards.js +1 -1
  52. package/dist/browser/utils/failure_guards.js.map +1 -1
  53. package/dist/browser/utils/lenient.js.map +1 -1
  54. package/dist/browser/utils/ordering.js.map +1 -1
  55. package/dist/browser/utils/reconnection.js +1 -1
  56. package/dist/browser/utils/reconnection.js.map +1 -1
  57. package/dist/browser/utils/resolve_pre_send.js.map +1 -1
  58. package/dist/browser/utils/server_message.js.map +1 -1
  59. package/dist/browser/utils/shadow_errors.js.map +1 -1
  60. package/dist/browser/utils/submit_state.js.map +1 -1
  61. package/dist/browser/utils/tick_align.js.map +1 -1
  62. package/dist/browser/utils/tick_math.js.map +1 -1
  63. package/dist/browser/utils/tick_range.js.map +1 -1
  64. package/dist/browser/utils/ticks.js.map +1 -1
  65. package/dist/browser/utils/validation.js.map +1 -1
  66. package/dist/index.cjs +23 -6
  67. package/dist/index.cjs.map +1 -1
  68. package/dist/index.d.cts +13 -5
  69. package/dist/index.d.ts +13 -5
  70. package/dist/index.mjs +23 -6
  71. package/dist/index.mjs.map +1 -1
  72. package/package.json +3 -3
  73. /package/dist/browser/_virtual/{_@oxc-project_runtime@0.153.0 → _@oxc-project_runtime@0.154.0}/helpers/esm/defineProperty.js +0 -0
  74. /package/dist/browser/_virtual/{_@oxc-project_runtime@0.153.0 → _@oxc-project_runtime@0.154.0}/helpers/esm/toPrimitive.js +0 -0
  75. /package/dist/browser/_virtual/{_@oxc-project_runtime@0.153.0 → _@oxc-project_runtime@0.154.0}/helpers/esm/toPropertyKey.js +0 -0
  76. /package/dist/browser/_virtual/{_@oxc-project_runtime@0.153.0 → _@oxc-project_runtime@0.154.0}/helpers/esm/typeof.js +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"gswap_sdk_error.js","names":["_defineProperty","UPDATE_REQUIRED_STATUS","GSwapSDKError","GalaSDKError","code","params","context","tokenText","tokenIn","tokenOut","feeText","fee","sourceContext","source","gatewayCode","getStringProperty","body","token","options","isWrite","write","uniqueKey","status","url","serverMessage","bodyText","response","bodyJson","parseJson","errorObject","getObjectProperty","errorKey","message","serverMessageOfBody","GSWAP_SDK_ERROR_ENTRIES","isGSwapError","value","isPreSendError","view","toFailureView","phaseOfView","property","propertyValue"],"sources":["../../../src/classes/gswap_sdk_error.ts"],"sourcesContent":["import {\n GalaSDKError,\n parseJson,\n toFailureView,\n type GalaChainTokenClassKey,\n type GalaErrorContext,\n type HTTPResponse,\n} from '@gala-chain/gala-core-sdk';\nimport { serverMessageOfBody } from '../utils/server_message.js';\nimport {\n GSWAP_SDK_ERROR_ENTRIES,\n type GSwapErrorCode,\n type GSwapErrorParams,\n} from './gswap_error_catalog.js';\nimport { phaseOfView } from './gswap_error_phase_table.js';\n\n/**\n * The HTTP status of a version wall: the backend refuses an app version that is too old (HTTP 426\n * Upgrade Required). It is `UPDATE_REQUIRED` on every request the SDK sends, whatever else the body\n * says; a confirmation read keeps `CONFIRMATION_FAILED` because the write it confirms may be applied.\n */\nexport const UPDATE_REQUIRED_STATUS = 426;\n\n/** What {@link GSwapSDKError.fromStatus} may be told about the HTTP answer. */\nexport interface GSwapStatusErrorOptions {\n /** The URL the request went to. */\n url?: string | undefined;\n /** The signed `uniqueKey` of the WRITE the answer is for; its presence makes this a write. */\n uniqueKey?: string | undefined;\n /** Say it outright: `true` for a write (needs `uniqueKey`), `false` for a read. */\n write?: boolean | undefined;\n /** The server's own text, for `HTTP_ERROR.params.serverMessage` (`''` when none). */\n serverMessage?: string | undefined;\n /** The response body, kept as `error.body` (or `bodyText` for a string). */\n body?: unknown;\n /** How long the server asked to wait (`Retry-After`), in milliseconds. */\n retryAfterMs?: number | undefined;\n}\n\n/**\n * Error raised by gswap-sdk for validation, gateway, chain, or transaction failures. It is a\n * `GalaSDKError` (`@gala-chain/gala-core-sdk`), and can carry any code of the core catalog or of\n * gswap-sdk's own DEX catalog: the signing, transport and wallet failures of a gswap flow are core\n * codes, thrown either as a `GSwapSDKError` by gswap-sdk or as a plain `GalaSDKError` by core\n * itself (a signer, the fetch requestor). Catch both with `isGSwapError` (or core's `isGalaError`);\n * `instanceof GSwapSDKError` alone misses the second kind.\n *\n * Branch on {@link code} (stable) and render {@link params} through your own localization.\n * {@link message} is English generated from the catalog template for logs and developers: it\n * is not stable and must never be shown to a user or matched on.\n */\nexport class GSwapSDKError<C extends GSwapErrorCode = GSwapErrorCode> extends GalaSDKError<\n C,\n GSwapErrorParams<C>\n> {\n /** Core's catalog entries and the DEX entries: a `GSwapSDKError` can carry a code of either. */\n public static override readonly catalog = GSWAP_SDK_ERROR_ENTRIES;\n\n constructor(code: C, params: GSwapErrorParams<C>, context: GalaErrorContext = {}) {\n super(code, params, context);\n this.name = 'GSwapSDKError';\n }\n\n /**\n * True when the failure means \"that market, pool or position does not exist\" rather than\n * \"something went wrong\": an HTTP 404, a pair with no pool at any fee tier\n * (`NO_POOL_AVAILABLE`), or a token the DEX contract has no trading symbol for\n * (`UNKNOWN_TOKEN`). A pool that exists but is too thin (`INSUFFICIENT_LIQUIDITY`) is NOT\n * not-found.\n */\n public override get notFound(): boolean {\n return super.notFound || this.code === 'NO_POOL_AVAILABLE' || this.code === 'UNKNOWN_TOKEN';\n }\n\n /** Create the standard missing-signer error. */\n public static noSignerError(): GSwapSDKError<'NO_SIGNER'> {\n return new GSwapSDKError('NO_SIGNER', {});\n }\n\n /** Create the error used when confirming a position write without a configured wallet address. */\n public static noWalletAddressError(): GSwapSDKError<'NO_WALLET_ADDRESS'> {\n return new GSwapSDKError('NO_WALLET_ADDRESS', {});\n }\n\n /**\n * Create the error used when no pool can quote the requested pair. `source` is the backend\n * answer that said so; its status and body are kept.\n */\n public static noPoolAvailableError(\n tokenIn: GalaChainTokenClassKey | string,\n tokenOut: GalaChainTokenClassKey | string,\n fee?: number,\n source?: GSwapSDKError,\n ): GSwapSDKError<'NO_POOL_AVAILABLE'> {\n return new GSwapSDKError(\n 'NO_POOL_AVAILABLE',\n { tokenIn: tokenText(tokenIn), tokenOut: tokenText(tokenOut), fee: feeText(fee) },\n sourceContext(source),\n );\n }\n\n /** Create the error used when a pool lacks liquidity for a quote. `source` as for {@link noPoolAvailableError}. */\n public static insufficientLiquidityError(\n tokenIn: GalaChainTokenClassKey | string,\n tokenOut: GalaChainTokenClassKey | string,\n fee?: number,\n source?: GSwapSDKError,\n ): GSwapSDKError<'INSUFFICIENT_LIQUIDITY'> {\n return new GSwapSDKError(\n 'INSUFFICIENT_LIQUIDITY',\n { tokenIn: tokenText(tokenIn), tokenOut: tokenText(tokenOut), fee: feeText(fee) },\n sourceContext(source),\n );\n }\n\n /**\n * Convert a chain-gateway bounce into a typed SDK error: the gateway's own code when the SDK\n * knows it, `GATEWAY_REJECTED` (with the code as `params.gatewayCode`) when it does not.\n * `serverMessage` is the gateway's English text: for logs, not for a user interface. For\n * `CHAIN_DISPATCH_FAILED`, `params.errorKey` is the chain's `ErrorKey` from the body's\n * top-level `errorKey` (`''` when the gateway sent none): map it, not the text.\n */\n public static fromGatewayBounce(\n gatewayCode: string,\n serverMessage: string,\n status: number,\n body: unknown,\n url: string,\n retryAfterMs?: number,\n uniqueKey?: string,\n ): GSwapSDKError {\n const context: GalaErrorContext = { status, body, url, retryAfterMs, uniqueKey };\n const params = { serverMessage };\n switch (gatewayCode) {\n case 'METHOD_NOT_ALLOWED':\n return new GSwapSDKError('METHOD_NOT_ALLOWED', params, context);\n case 'SIGNATURE_INVALID':\n return new GSwapSDKError('SIGNATURE_INVALID', params, context);\n case 'SIGNER_MISMATCH':\n return new GSwapSDKError('SIGNER_MISMATCH', params, context);\n case 'DTO_INVALID':\n return new GSwapSDKError('DTO_INVALID', params, context);\n case 'BOUNDS_VIOLATION':\n return new GSwapSDKError('BOUNDS_VIOLATION', params, context);\n case 'SYMBOL_CONFLICT':\n return new GSwapSDKError('SYMBOL_CONFLICT', params, context);\n case 'RATE_LIMITED':\n return new GSwapSDKError('RATE_LIMITED', params, context);\n case 'CHAIN_DISPATCH_FAILED':\n // The gateway adds the chain's own machine-readable key as a top-level `errorKey`, only\n // when the chain supplied one; an older gateway sends none, which is the empty string.\n return new GSwapSDKError(\n 'CHAIN_DISPATCH_FAILED',\n { ...params, errorKey: getStringProperty(body, 'errorKey') ?? '' },\n context,\n );\n default:\n return new GSwapSDKError('GATEWAY_REJECTED', { gatewayCode, serverMessage }, context);\n }\n }\n\n /** Convert a GalaChain `Status: 0` response into a typed SDK error. */\n public static fromChainError(\n errorKey: string,\n serverMessage: string,\n context?: GalaErrorContext,\n ): GSwapSDKError<'CHAIN_REJECTED'> {\n return new GSwapSDKError('CHAIN_REJECTED', { errorKey, serverMessage }, context);\n }\n\n /**\n * Create the error used when a token is not registered by the current DEX contract. `source` is\n * the backend answer that said so (a quote); its status, body and url are kept.\n */\n public static unknownTokenError(\n token: GalaChainTokenClassKey | string,\n source?: GSwapSDKError,\n ): GSwapSDKError<'UNKNOWN_TOKEN'> {\n return new GSwapSDKError('UNKNOWN_TOKEN', { token: tokenText(token) }, sourceContext(source));\n }\n\n /** Create the error used when apply-results confirmation does not arrive in time. */\n public static confirmationTimeoutError(\n uniqueKey: string,\n cause?: unknown,\n ): GSwapSDKError<'CONFIRMATION_TIMEOUT'> {\n return new GSwapSDKError('CONFIRMATION_TIMEOUT', {}, { uniqueKey, cause });\n }\n\n /**\n * Builds, synchronously, the error the SDK itself throws for an HTTP answer of this `status`\n * that has no gateway or chain code: `UPDATE_REQUIRED` for the version wall (426, whatever\n * else is true), `HTTP_ERROR` for every other status. It is for an app that holds a resolved\n * HTTP answer the SDK did not read (a v1 endpoint, an interceptor's rejection) and wants the\n * SDK's classification of it (`classifyFailure`) instead of building the error by hand.\n *\n * Reads and writes differ exactly as they do in the gateway (`classifyFailure`): a WRITE\n * carries the signed `uniqueKey` (the handle to confirm it), a READ carries none. Pass\n * `uniqueKey` for a write, or `write: true` with it; `write: false` builds a read and drops any\n * `uniqueKey`. `write: true` without a non-empty `uniqueKey` throws `INVALID_UNIQUE_KEY`: a\n * write error with no key would be classified as a read, which is a different answer. Either\n * way an answer was read, so the phase is `post_send`.\n *\n * @example\n * ```ts\n * const read = GSwapSDKError.fromStatus(503, { url });\n * classifyFailure(read); // { outcome: 'transient', retryable: true }\n * const write = GSwapSDKError.fromStatus(503, { url, uniqueKey });\n * classifyFailure(write); // { outcome: 'unknown', retryable: false }: it may have been queued\n * classifyFailure(GSwapSDKError.fromStatus(426)); // invalid_input: only an update helps\n * ```\n */\n public static fromStatus(\n status: number,\n options: GSwapStatusErrorOptions = {},\n ): GSwapSDKError<'UPDATE_REQUIRED' | 'HTTP_ERROR'> {\n const { url, uniqueKey, write, serverMessage, body, retryAfterMs } = options;\n const isWrite = write ?? (uniqueKey !== undefined && uniqueKey !== '');\n if (isWrite && (uniqueKey === undefined || uniqueKey === '')) {\n throw new GSwapSDKError('INVALID_UNIQUE_KEY', { uniqueKey: uniqueKey ?? '' });\n }\n const context: GalaErrorContext = {\n status,\n body,\n url,\n retryAfterMs,\n uniqueKey: isWrite ? uniqueKey : undefined,\n write: isWrite,\n phase: 'post_send',\n };\n return status === UPDATE_REQUIRED_STATUS\n ? new GSwapSDKError('UPDATE_REQUIRED', { status }, context)\n : new GSwapSDKError(\n 'HTTP_ERROR',\n { status, url: url ?? '', serverMessage: serverMessage ?? '' },\n context,\n );\n }\n\n /** Convert an unexpected HTTP response into an SDK error. */\n public static async fromErrorResponse(\n url: string,\n response: HTTPResponse,\n ): Promise<GSwapSDKError> {\n const bodyText = await response.text();\n const bodyJson: unknown = parseJson(bodyText);\n const errorObject = getObjectProperty(bodyJson, 'error');\n const errorKey = getStringProperty(errorObject, 'ErrorKey');\n const message = getStringProperty(errorObject, 'Message');\n // Every request that goes through `HttpClient` is a READ (a quote, a list, an apply-results\n // GET); writes go through the gateway, which builds its own errors with the signed key.\n const context: GalaErrorContext = {\n status: response.status,\n body: bodyJson ?? bodyText,\n url,\n write: false,\n };\n\n if (response.status === UPDATE_REQUIRED_STATUS) {\n return new GSwapSDKError('UPDATE_REQUIRED', { status: response.status }, context);\n }\n\n if (errorKey !== undefined && message !== undefined) {\n return new GSwapSDKError('CHAIN_REJECTED', { errorKey, serverMessage: message }, context);\n }\n\n return new GSwapSDKError(\n 'HTTP_ERROR',\n { status: response.status, url, serverMessage: serverMessageOfBody(bodyJson) },\n context,\n );\n }\n}\n\n/** A token as the text a param carries: a string as is, a class key as its `$` composite. */\nfunction tokenText(token: GalaChainTokenClassKey | string): string {\n return typeof token === 'string'\n ? token\n : [token.collection, token.category, token.type, token.additionalKey].join('$');\n}\n\nfunction feeText(fee: number | undefined): string {\n return fee === undefined ? 'any' : String(fee);\n}\n\n/**\n * The HTTP facts of the error a classified error was derived from. An error derived from a\n * backend answer is `post_send`: the answer was read.\n */\nfunction sourceContext(source: GSwapSDKError | undefined): GalaErrorContext {\n if (source === undefined) return {};\n return {\n status: source.status,\n body: source.body ?? source.bodyText,\n url: source.url,\n phase: 'post_send',\n write: source.write,\n };\n}\n\n/** Any thrown value that is an SDK error of a gswap flow, narrowed to the union of every code. */\nexport type AnyGSwapError = { [C in GSwapErrorCode]: GSwapSDKError<C> }[GSwapErrorCode];\n\n/**\n * True when `value` is an SDK error a gswap flow can throw and, when `code` is given, carries\n * exactly that code. With a code, `error.params` narrows to that code's params.\n *\n * It recognises every `GalaSDKError` (`instanceof`), not only a `GSwapSDKError`: the signing,\n * transport and wallet failures of a gswap flow come from `@gala-chain/gala-core-sdk` (a signer,\n * the fetch requestor), carry a core code, and are core `GalaSDKError`s. For such an error the\n * fields are exactly a `GSwapSDKError`'s (`notFound` differs only for the DEX not-found codes,\n * which core never throws), so the narrowing holds for everything a gswap flow throws.\n *\n * @example\n * ```ts\n * if (isGSwapError(error, 'NON_CANONICAL_PAIR')) {\n * t('errors.NON_CANONICAL_PAIR', error.params); // { token0, token1 }\n * }\n * ```\n */\nexport function isGSwapError(value: unknown): value is AnyGSwapError;\nexport function isGSwapError<C extends GSwapErrorCode>(\n value: unknown,\n code: C,\n): value is GSwapSDKError<C>;\nexport function isGSwapError(value: unknown, code?: GSwapErrorCode): boolean {\n return value instanceof GalaSDKError && (code === undefined || value.code === code);\n}\n\n/**\n * True when `value` is an SDK error that was thrown BEFORE anything was transmitted\n * (`error.phase === 'pre_send'`): validation, symbol resolution, the signer, the wallet, a key\n * provider. A write that failed this way cannot have been queued, so the failure never calls for\n * \"confirm before you retry\". Every failure of an app-supplied callback that runs BEFORE the\n * request leaves reaches you as an SDK error of this kind (`SIGNING_FAILED`,\n * `KEY_PROVIDER_FAILED`, `TOKEN_RESOLUTION_FAILED`, the original error as `cause`), so an app\n * never needs an error class of its own to tell \"nothing was sent\" apart.\n *\n * Whether to offer Retry is `classifyFailure(error).retryable`, not this: a pre-send validation\n * error is pre-send too, and repeating the same call fails the same way.\n *\n * It also answers for a plain-object view of an SDK error (`{ code, phase? }`, see\n * `FailureView`): the view's own `phase`, else the catalog's phase for its `code` (a core code or\n * a DEX code). It is a plain `boolean`, not a type guard (a view is not a `GSwapSDKError`); use\n * `isGSwapError` to narrow.\n *\n * @example\n * ```ts\n * if (isPreSendError(error)) markNothingSent(); // no pending swap to confirm\n * ```\n */\nexport function isPreSendError(value: unknown): boolean {\n const view = toFailureView(value);\n return view !== undefined && phaseOfView(view) === 'pre_send';\n}\n\n/** Narrow an object property without trusting a cast at a network boundary. */\nexport function getObjectProperty(\n value: unknown,\n property: string,\n): Record<string, unknown> | undefined {\n if (typeof value !== 'object' || value === null || !(property in value)) return undefined;\n const propertyValue = (value as Record<string, unknown>)[property];\n return typeof propertyValue === 'object' && propertyValue !== null\n ? (propertyValue as Record<string, unknown>)\n : undefined;\n}\n\n/** Read a string property from an unknown object. */\nexport function getStringProperty(value: unknown, property: string): string | undefined {\n if (typeof value !== 'object' || value === null || !(property in value)) return undefined;\n const propertyValue = (value as Record<string, unknown>)[property];\n return typeof propertyValue === 'string' ? propertyValue : undefined;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"gswap_sdk_error.js","names":["_defineProperty","UPDATE_REQUIRED_STATUS","GSwapSDKError","GalaSDKError","code","params","context","tokenText","tokenIn","tokenOut","feeText","fee","sourceContext","source","gatewayCode","getStringProperty","body","token","options","isWrite","write","uniqueKey","status","url","serverMessage","bodyText","response","bodyJson","parseJson","errorObject","getObjectProperty","errorKey","message","serverMessageOfBody","GSWAP_SDK_ERROR_ENTRIES","isGSwapError","value","isPreSendError","view","toFailureView","phaseOfView","property","propertyValue"],"sources":["../../../src/classes/gswap_sdk_error.ts"],"sourcesContent":["import {\n GalaSDKError,\n parseJson,\n toFailureView,\n type GalaChainTokenClassKey,\n type GalaErrorContext,\n type HTTPResponse,\n} from '@gala-chain/gala-core-sdk';\nimport { serverMessageOfBody } from '../utils/server_message.js';\nimport {\n GSWAP_SDK_ERROR_ENTRIES,\n type GSwapErrorCode,\n type GSwapErrorParams,\n} from './gswap_error_catalog.js';\nimport { phaseOfView } from './gswap_error_phase_table.js';\n\n/**\n * The HTTP status of a version wall: the backend refuses an app version that is too old (HTTP 426\n * Upgrade Required). It is `UPDATE_REQUIRED` on every request the SDK sends, whatever else the body\n * says; a confirmation read keeps `CONFIRMATION_FAILED` because the write it confirms may be applied.\n */\nexport const UPDATE_REQUIRED_STATUS = 426;\n\n/** What {@link GSwapSDKError.fromStatus} may be told about the HTTP answer. */\nexport interface GSwapStatusErrorOptions {\n /** The URL the request went to. */\n url?: string | undefined;\n /** The signed `uniqueKey` of the WRITE the answer is for; its presence makes this a write. */\n uniqueKey?: string | undefined;\n /** Say it outright: `true` for a write (needs `uniqueKey`), `false` for a read. */\n write?: boolean | undefined;\n /** The server's own text, for `HTTP_ERROR.params.serverMessage` (`''` when none). */\n serverMessage?: string | undefined;\n /** The response body, kept as `error.body` (or `bodyText` for a string). */\n body?: unknown;\n /** How long the server asked to wait (`Retry-After`), in milliseconds. */\n retryAfterMs?: number | undefined;\n}\n\n/**\n * Error raised by gswap-sdk for validation, gateway, chain, or transaction failures. It is a\n * `GalaSDKError` (`@gala-chain/gala-core-sdk`), and can carry any code of the core catalog or of\n * gswap-sdk's own DEX catalog: the signing, transport and wallet failures of a gswap flow are core\n * codes, thrown either as a `GSwapSDKError` by gswap-sdk or as a plain `GalaSDKError` by core\n * itself (a signer, the fetch requestor). Catch both with `isGSwapError` (or core's `isGalaError`);\n * `instanceof GSwapSDKError` alone misses the second kind.\n *\n * Branch on {@link code} (stable) and render {@link params} through your own localization.\n * {@link message} is English generated from the catalog template for logs and developers: it\n * is not stable and must never be shown to a user or matched on.\n */\nexport class GSwapSDKError<C extends GSwapErrorCode = GSwapErrorCode> extends GalaSDKError<\n C,\n GSwapErrorParams<C>\n> {\n /** Core's catalog entries and the DEX entries: a `GSwapSDKError` can carry a code of either. */\n public static override readonly catalog = GSWAP_SDK_ERROR_ENTRIES;\n\n constructor(code: C, params: GSwapErrorParams<C>, context: GalaErrorContext = {}) {\n super(code, params, context);\n this.name = 'GSwapSDKError';\n }\n\n /**\n * True when the failure means \"that market, pool or position does not exist\" rather than\n * \"something went wrong\": an HTTP 404, a pair with no pool at any fee tier\n * (`NO_POOL_AVAILABLE`), or a token the DEX contract has no trading symbol for\n * (`UNKNOWN_TOKEN`). A pool that exists but is too thin (`INSUFFICIENT_LIQUIDITY`) is NOT\n * not-found.\n */\n public override get notFound(): boolean {\n return super.notFound || this.code === 'NO_POOL_AVAILABLE' || this.code === 'UNKNOWN_TOKEN';\n }\n\n /** Create the standard missing-signer error. */\n public static noSignerError(): GSwapSDKError<'NO_SIGNER'> {\n return new GSwapSDKError('NO_SIGNER', {});\n }\n\n /** Create the error used when confirming a position write without a configured wallet address. */\n public static noWalletAddressError(): GSwapSDKError<'NO_WALLET_ADDRESS'> {\n return new GSwapSDKError('NO_WALLET_ADDRESS', {});\n }\n\n /**\n * Create the error used when no pool can quote the requested pair. `source` is the backend\n * answer that said so; its status and body are kept.\n */\n public static noPoolAvailableError(\n tokenIn: GalaChainTokenClassKey | string,\n tokenOut: GalaChainTokenClassKey | string,\n fee?: number,\n source?: GSwapSDKError,\n ): GSwapSDKError<'NO_POOL_AVAILABLE'> {\n return new GSwapSDKError(\n 'NO_POOL_AVAILABLE',\n { tokenIn: tokenText(tokenIn), tokenOut: tokenText(tokenOut), fee: feeText(fee) },\n sourceContext(source),\n );\n }\n\n /** Create the error used when a pool lacks liquidity for a quote. `source` as for {@link noPoolAvailableError}. */\n public static insufficientLiquidityError(\n tokenIn: GalaChainTokenClassKey | string,\n tokenOut: GalaChainTokenClassKey | string,\n fee?: number,\n source?: GSwapSDKError,\n ): GSwapSDKError<'INSUFFICIENT_LIQUIDITY'> {\n return new GSwapSDKError(\n 'INSUFFICIENT_LIQUIDITY',\n { tokenIn: tokenText(tokenIn), tokenOut: tokenText(tokenOut), fee: feeText(fee) },\n sourceContext(source),\n );\n }\n\n /**\n * Convert a chain-gateway bounce into a typed SDK error: the gateway's own code when the SDK\n * knows it, `GATEWAY_REJECTED` (with the code as `params.gatewayCode`) when it does not.\n * `serverMessage` is the gateway's English text: for logs, not for a user interface. For\n * `CHAIN_DISPATCH_FAILED`, `params.errorKey` is the chain's `ErrorKey` from the body's\n * top-level `errorKey` (`''` when the gateway sent none): map it, not the text.\n */\n public static fromGatewayBounce(\n gatewayCode: string,\n serverMessage: string,\n status: number,\n body: unknown,\n url: string,\n retryAfterMs?: number,\n uniqueKey?: string,\n ): GSwapSDKError {\n const context: GalaErrorContext = { status, body, url, retryAfterMs, uniqueKey };\n const params = { serverMessage };\n switch (gatewayCode) {\n case 'METHOD_NOT_ALLOWED':\n return new GSwapSDKError('METHOD_NOT_ALLOWED', params, context);\n case 'SIGNATURE_INVALID':\n return new GSwapSDKError('SIGNATURE_INVALID', params, context);\n case 'SIGNER_MISMATCH':\n return new GSwapSDKError('SIGNER_MISMATCH', params, context);\n case 'DTO_INVALID':\n return new GSwapSDKError('DTO_INVALID', params, context);\n case 'DTO_OPERATION_MISMATCH':\n return new GSwapSDKError('DTO_OPERATION_MISMATCH', params, context);\n case 'BOUNDS_VIOLATION':\n return new GSwapSDKError('BOUNDS_VIOLATION', params, context);\n case 'SYMBOL_CONFLICT':\n return new GSwapSDKError('SYMBOL_CONFLICT', params, context);\n case 'RATE_LIMITED':\n return new GSwapSDKError('RATE_LIMITED', params, context);\n case 'CHAIN_DISPATCH_FAILED':\n // The gateway adds the chain's own machine-readable key as a top-level `errorKey`, only\n // when the chain supplied one; an older gateway sends none, which is the empty string.\n return new GSwapSDKError(\n 'CHAIN_DISPATCH_FAILED',\n { ...params, errorKey: getStringProperty(body, 'errorKey') ?? '' },\n context,\n );\n default:\n return new GSwapSDKError('GATEWAY_REJECTED', { gatewayCode, serverMessage }, context);\n }\n }\n\n /** Convert a GalaChain `Status: 0` response into a typed SDK error. */\n public static fromChainError(\n errorKey: string,\n serverMessage: string,\n context?: GalaErrorContext,\n ): GSwapSDKError<'CHAIN_REJECTED'> {\n return new GSwapSDKError('CHAIN_REJECTED', { errorKey, serverMessage }, context);\n }\n\n /**\n * Create the error used when a token is not registered by the current DEX contract. `source` is\n * the backend answer that said so (a quote); its status, body and url are kept.\n */\n public static unknownTokenError(\n token: GalaChainTokenClassKey | string,\n source?: GSwapSDKError,\n ): GSwapSDKError<'UNKNOWN_TOKEN'> {\n return new GSwapSDKError('UNKNOWN_TOKEN', { token: tokenText(token) }, sourceContext(source));\n }\n\n /** Create the error used when apply-results confirmation does not arrive in time. */\n public static confirmationTimeoutError(\n uniqueKey: string,\n cause?: unknown,\n ): GSwapSDKError<'CONFIRMATION_TIMEOUT'> {\n return new GSwapSDKError('CONFIRMATION_TIMEOUT', {}, { uniqueKey, cause });\n }\n\n /**\n * Builds, synchronously, the error the SDK itself throws for an HTTP answer of this `status`\n * that has no gateway or chain code: `UPDATE_REQUIRED` for the version wall (426, whatever\n * else is true), `HTTP_ERROR` for every other status. It is for an app that holds a resolved\n * HTTP answer the SDK did not read (a v1 endpoint, an interceptor's rejection) and wants the\n * SDK's classification of it (`classifyFailure`) instead of building the error by hand.\n *\n * Reads and writes differ exactly as they do in the gateway (`classifyFailure`): a WRITE\n * carries the signed `uniqueKey` (the handle to confirm it), a READ carries none. Pass\n * `uniqueKey` for a write, or `write: true` with it; `write: false` builds a read and drops any\n * `uniqueKey`. `write: true` without a non-empty `uniqueKey` throws `INVALID_UNIQUE_KEY`: a\n * write error with no key would be classified as a read, which is a different answer. Either\n * way an answer was read, so the phase is `post_send`.\n *\n * @example\n * ```ts\n * const read = GSwapSDKError.fromStatus(503, { url });\n * classifyFailure(read); // { outcome: 'transient', retryable: true }\n * const write = GSwapSDKError.fromStatus(503, { url, uniqueKey });\n * classifyFailure(write); // { outcome: 'unknown', retryable: false }: it may have been queued\n * classifyFailure(GSwapSDKError.fromStatus(426)); // invalid_input: only an update helps\n * ```\n */\n public static fromStatus(\n status: number,\n options: GSwapStatusErrorOptions = {},\n ): GSwapSDKError<'UPDATE_REQUIRED' | 'HTTP_ERROR'> {\n const { url, uniqueKey, write, serverMessage, body, retryAfterMs } = options;\n const isWrite = write ?? (uniqueKey !== undefined && uniqueKey !== '');\n if (isWrite && (uniqueKey === undefined || uniqueKey === '')) {\n throw new GSwapSDKError('INVALID_UNIQUE_KEY', { uniqueKey: uniqueKey ?? '' });\n }\n const context: GalaErrorContext = {\n status,\n body,\n url,\n retryAfterMs,\n uniqueKey: isWrite ? uniqueKey : undefined,\n write: isWrite,\n phase: 'post_send',\n };\n return status === UPDATE_REQUIRED_STATUS\n ? new GSwapSDKError('UPDATE_REQUIRED', { status }, context)\n : new GSwapSDKError(\n 'HTTP_ERROR',\n { status, url: url ?? '', serverMessage: serverMessage ?? '' },\n context,\n );\n }\n\n /** Convert an unexpected HTTP response into an SDK error. */\n public static async fromErrorResponse(\n url: string,\n response: HTTPResponse,\n ): Promise<GSwapSDKError> {\n const bodyText = await response.text();\n const bodyJson: unknown = parseJson(bodyText);\n const errorObject = getObjectProperty(bodyJson, 'error');\n const errorKey = getStringProperty(errorObject, 'ErrorKey');\n const message = getStringProperty(errorObject, 'Message');\n // Every request that goes through `HttpClient` is a READ (a quote, a list, an apply-results\n // GET); writes go through the gateway, which builds its own errors with the signed key.\n const context: GalaErrorContext = {\n status: response.status,\n body: bodyJson ?? bodyText,\n url,\n write: false,\n };\n\n if (response.status === UPDATE_REQUIRED_STATUS) {\n return new GSwapSDKError('UPDATE_REQUIRED', { status: response.status }, context);\n }\n\n if (errorKey !== undefined && message !== undefined) {\n return new GSwapSDKError('CHAIN_REJECTED', { errorKey, serverMessage: message }, context);\n }\n\n return new GSwapSDKError(\n 'HTTP_ERROR',\n { status: response.status, url, serverMessage: serverMessageOfBody(bodyJson) },\n context,\n );\n }\n}\n\n/** A token as the text a param carries: a string as is, a class key as its `$` composite. */\nfunction tokenText(token: GalaChainTokenClassKey | string): string {\n return typeof token === 'string'\n ? token\n : [token.collection, token.category, token.type, token.additionalKey].join('$');\n}\n\nfunction feeText(fee: number | undefined): string {\n return fee === undefined ? 'any' : String(fee);\n}\n\n/**\n * The HTTP facts of the error a classified error was derived from. An error derived from a\n * backend answer is `post_send`: the answer was read.\n */\nfunction sourceContext(source: GSwapSDKError | undefined): GalaErrorContext {\n if (source === undefined) return {};\n return {\n status: source.status,\n body: source.body ?? source.bodyText,\n url: source.url,\n phase: 'post_send',\n write: source.write,\n };\n}\n\n/** Any thrown value that is an SDK error of a gswap flow, narrowed to the union of every code. */\nexport type AnyGSwapError = { [C in GSwapErrorCode]: GSwapSDKError<C> }[GSwapErrorCode];\n\n/**\n * True when `value` is an SDK error a gswap flow can throw and, when `code` is given, carries\n * exactly that code. With a code, `error.params` narrows to that code's params.\n *\n * It recognises every `GalaSDKError` (`instanceof`), not only a `GSwapSDKError`: the signing,\n * transport and wallet failures of a gswap flow come from `@gala-chain/gala-core-sdk` (a signer,\n * the fetch requestor), carry a core code, and are core `GalaSDKError`s. For such an error the\n * fields are exactly a `GSwapSDKError`'s (`notFound` differs only for the DEX not-found codes,\n * which core never throws), so the narrowing holds for everything a gswap flow throws.\n *\n * @example\n * ```ts\n * if (isGSwapError(error, 'NON_CANONICAL_PAIR')) {\n * t('errors.NON_CANONICAL_PAIR', error.params); // { token0, token1 }\n * }\n * ```\n */\nexport function isGSwapError(value: unknown): value is AnyGSwapError;\nexport function isGSwapError<C extends GSwapErrorCode>(\n value: unknown,\n code: C,\n): value is GSwapSDKError<C>;\nexport function isGSwapError(value: unknown, code?: GSwapErrorCode): boolean {\n return value instanceof GalaSDKError && (code === undefined || value.code === code);\n}\n\n/**\n * True when `value` is an SDK error that was thrown BEFORE anything was transmitted\n * (`error.phase === 'pre_send'`): validation, symbol resolution, the signer, the wallet, a key\n * provider. A write that failed this way cannot have been queued, so the failure never calls for\n * \"confirm before you retry\". Every failure of an app-supplied callback that runs BEFORE the\n * request leaves reaches you as an SDK error of this kind (`SIGNING_FAILED`,\n * `KEY_PROVIDER_FAILED`, `TOKEN_RESOLUTION_FAILED`, the original error as `cause`), so an app\n * never needs an error class of its own to tell \"nothing was sent\" apart.\n *\n * Whether to offer Retry is `classifyFailure(error).retryable`, not this: a pre-send validation\n * error is pre-send too, and repeating the same call fails the same way.\n *\n * It also answers for a plain-object view of an SDK error (`{ code, phase? }`, see\n * `FailureView`): the view's own `phase`, else the catalog's phase for its `code` (a core code or\n * a DEX code). It is a plain `boolean`, not a type guard (a view is not a `GSwapSDKError`); use\n * `isGSwapError` to narrow.\n *\n * @example\n * ```ts\n * if (isPreSendError(error)) markNothingSent(); // no pending swap to confirm\n * ```\n */\nexport function isPreSendError(value: unknown): boolean {\n const view = toFailureView(value);\n return view !== undefined && phaseOfView(view) === 'pre_send';\n}\n\n/** Narrow an object property without trusting a cast at a network boundary. */\nexport function getObjectProperty(\n value: unknown,\n property: string,\n): Record<string, unknown> | undefined {\n if (typeof value !== 'object' || value === null || !(property in value)) return undefined;\n const propertyValue = (value as Record<string, unknown>)[property];\n return typeof propertyValue === 'object' && propertyValue !== null\n ? (propertyValue as Record<string, unknown>)\n : undefined;\n}\n\n/** Read a string property from an unknown object. */\nexport function getStringProperty(value: unknown, property: string): string | undefined {\n if (typeof value !== 'object' || value === null || !(property in value)) return undefined;\n const propertyValue = (value as Record<string, unknown>)[property];\n return typeof propertyValue === 'string' ? propertyValue : undefined;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"http_client.js","names":["HttpClient","fetchRequestor","httpRequestor","timeoutMs","wire","url","baseUrl","basePath","endpoint","debugLog","body","hasBody","response","requestWithTimeout","buildRequestHeaders","options","GSwapSDKError","json","readResponseBody","endpointWithParams","params","buildQueryString"],"sources":["../../../src/classes/http_client.ts"],"sourcesContent":["import {\n buildQueryString,\n buildRequestHeaders,\n fetchRequestor,\n readResponseBody,\n requestWithTimeout,\n type HttpRequestor,\n type WireOptions,\n} from '@gala-chain/gala-core-sdk';\nimport { debugLog } from '../utils/debug.js';\nimport { GSwapSDKError } from './gswap_sdk_error.js';\n\nexport class HttpClient {\n /**\n * Create an HTTP client around fetch or an injected requestor. `wire` selects the query\n * encoding and the `User-Agent` (see {@link WireOptions}).\n */\n constructor(\n private readonly httpRequestor: HttpRequestor = fetchRequestor,\n private readonly timeoutMs = 30_000,\n private readonly wire: WireOptions = {},\n ) {}\n\n private async sendRequest<TReturnType>(\n method: 'POST' | 'GET',\n baseUrl: string,\n basePath: string,\n endpoint: string,\n body?: unknown,\n options?: Pick<RequestInit, 'signal'>,\n ): Promise<TReturnType> {\n const url = `${baseUrl}${basePath}${endpoint}`;\n debugLog(`Sending request to ${url} with body:`, body);\n\n const hasBody = Boolean(body);\n const response = await requestWithTimeout(\n this.httpRequestor,\n url,\n {\n method,\n // Content-Type describes a body, so a GET carries none: it also keeps a browser's\n // cross-origin GET a simple request instead of forcing a CORS preflight.\n headers: buildRequestHeaders(this.wire, { json: hasBody }),\n ...(hasBody ? { body: JSON.stringify(body) } : {}),\n ...(options?.signal === undefined ? {} : { signal: options.signal }),\n },\n this.timeoutMs,\n // Every request of this client is a read (writes go through ChainGateway): a timeout or a\n // transport failure is a read's, as its HTTP errors are (`fromErrorResponse`).\n { write: false },\n );\n\n if (!response.ok) {\n throw await GSwapSDKError.fromErrorResponse(url, response);\n }\n\n const json = await readResponseBody(response);\n debugLog(`Response from ${baseUrl}${basePath}${endpoint}:`, json);\n\n return json as TReturnType;\n }\n\n /** Send a JSON POST request and decode its response. */\n async sendPostRequest<TReturnType>(\n baseUrl: string,\n basePath: string,\n endpoint: string,\n body: unknown,\n ): Promise<TReturnType> {\n return this.sendRequest('POST', baseUrl, basePath, endpoint, body);\n }\n\n /** Send a GET request with URL-encoded query parameters and decode its response. */\n async sendGetRequest<TReturnType>(\n baseUrl: string,\n basePath: string,\n endpoint: string,\n params?: Record<string, string>,\n options?: Pick<RequestInit, 'signal'>,\n ): Promise<TReturnType> {\n const endpointWithParams = params\n ? `${endpoint}?${buildQueryString(params, this.wire.queryEncoding)}`\n : endpoint;\n\n return this.sendRequest('GET', baseUrl, basePath, endpointWithParams, undefined, options);\n }\n}\n"],"mappings":"0PAYA,IAAaA,EAAb,KAAwB,CAKtB,YACE,EAAgDC,EAChD,EAA6B,IAC7B,EAAqC,CAAC,EACtC,CAHiB,KAAA,cAAAC,EACA,KAAA,UAAAC,EACA,KAAA,KAAAC,CAChB,CAEH,MAAc,YACZ,EACA,EACA,EACA,EACA,EACA,EACsB,CACtB,IAAMC,EAAM,GAAGC,IAAUC,IAAWC,IACpCC,EAAS,sBAAsBJ,EAAI,aAAcK,CAAI,EAErD,IAAMC,EAAU,EAAQD,EAClBE,EAAW,MAAMC,EACrB,KAAK,cACLR,EACA,CACE,SAGA,QAASS,EAAoB,KAAK,KAAM,CAAE,KAAMH,CAAQ,CAAC,EACzD,GAAIA,EAAU,CAAE,KAAM,KAAK,UAAUD,CAAI,CAAE,EAAI,CAAC,EAChD,GAAIK,GAAS,SAAW,IAAA,GAAY,CAAC,EAAI,CAAE,OAAQA,EAAQ,MAAO,CACpE,EACA,KAAK,UAGL,CAAE,MAAO,EAAM,CACjB,EAEA,GAAI,CAACH,EAAS,GACZ,MAAM,MAAMI,EAAc,kBAAkBX,EAAKO,CAAQ,EAG3D,IAAMK,EAAO,MAAMC,EAAiBN,CAAQ,EAG5C,OAFAH,EAAS,iBAAiBH,IAAUC,IAAWC,EAAS,GAAIS,CAAI,EAEzDA,CACT,CAGA,MAAM,gBACJ,EACA,EACA,EACA,EACsB,CACtB,OAAO,KAAK,YAAY,OAAQX,EAASC,EAAUC,EAAUE,CAAI,CACnE,CAGA,MAAM,eACJ,EACA,EACA,EACA,EACA,EACsB,CACtB,IAAMS,EAAqBC,EACvB,GAAGZ,EAAS,GAAGa,EAAiBD,EAAQ,KAAK,KAAK,aAAa,IAC/DZ,EAEJ,OAAO,KAAK,YAAY,MAAOF,EAASC,EAAUY,EAAoB,IAAA,GAAWJ,CAAO,CAC1F,CACF"}
1
+ {"version":3,"file":"http_client.js","names":["HttpClient","fetchRequestor","httpRequestor","timeoutMs","wire","url","baseUrl","basePath","endpoint","debugLog","body","hasBody","response","requestWithTimeout","buildRequestHeaders","options","GSwapSDKError","json","readResponseBody","endpointWithParams","params","buildQueryString"],"sources":["../../../src/classes/http_client.ts"],"sourcesContent":["import {\n buildQueryString,\n buildRequestHeaders,\n fetchRequestor,\n readResponseBody,\n requestWithTimeout,\n type HttpRequestor,\n type WireOptions,\n} from '@gala-chain/gala-core-sdk';\nimport { debugLog } from '../utils/debug.js';\nimport { GSwapSDKError } from './gswap_sdk_error.js';\n\nexport class HttpClient {\n /**\n * Create an HTTP client around fetch or an injected requestor. `wire` selects the query\n * encoding and the `User-Agent` (see {@link WireOptions}).\n */\n constructor(\n private readonly httpRequestor: HttpRequestor = fetchRequestor,\n private readonly timeoutMs = 30_000,\n private readonly wire: WireOptions = {},\n ) {}\n\n private async sendRequest<TReturnType>(\n method: 'POST' | 'GET',\n baseUrl: string,\n basePath: string,\n endpoint: string,\n body?: unknown,\n options?: Pick<RequestInit, 'signal'>,\n ): Promise<TReturnType> {\n const url = `${baseUrl}${basePath}${endpoint}`;\n debugLog(`Sending request to ${url} with body:`, body);\n\n const hasBody = Boolean(body);\n const response = await requestWithTimeout(\n this.httpRequestor,\n url,\n {\n method,\n // Content-Type describes a body, so a GET carries none: it also keeps a browser's\n // cross-origin GET a simple request instead of forcing a CORS preflight.\n headers: buildRequestHeaders(this.wire, { json: hasBody }),\n ...(hasBody ? { body: JSON.stringify(body) } : {}),\n ...(options?.signal === undefined ? {} : { signal: options.signal }),\n },\n this.timeoutMs,\n // Every request of this client is a read (writes go through ChainGateway): a timeout or a\n // transport failure is a read's, as its HTTP errors are (`fromErrorResponse`).\n { write: false },\n );\n\n if (!response.ok) {\n throw await GSwapSDKError.fromErrorResponse(url, response);\n }\n\n const json = await readResponseBody(response);\n debugLog(`Response from ${baseUrl}${basePath}${endpoint}:`, json);\n\n return json as TReturnType;\n }\n\n /** Send a JSON POST request and decode its response. */\n async sendPostRequest<TReturnType>(\n baseUrl: string,\n basePath: string,\n endpoint: string,\n body: unknown,\n ): Promise<TReturnType> {\n return this.sendRequest('POST', baseUrl, basePath, endpoint, body);\n }\n\n /** Send a GET request with URL-encoded query parameters and decode its response. */\n async sendGetRequest<TReturnType>(\n baseUrl: string,\n basePath: string,\n endpoint: string,\n params?: Record<string, string>,\n options?: Pick<RequestInit, 'signal'>,\n ): Promise<TReturnType> {\n const endpointWithParams = params\n ? `${endpoint}?${buildQueryString(params, this.wire.queryEncoding)}`\n : endpoint;\n\n return this.sendRequest('GET', baseUrl, basePath, endpointWithParams, undefined, options);\n }\n}\n"],"mappings":"A,0PAYA,IAAaA,EAAb,KAAwB,CAKtB,YACE,EAAgDC,EAChD,EAA6B,IAC7B,EAAqC,CAAC,EACtC,CAHiB,KAAA,cAAAC,EACA,KAAA,UAAAC,EACA,KAAA,KAAAC,CAChB,CAEH,MAAc,YACZ,EACA,EACA,EACA,EACA,EACA,EACsB,CACtB,IAAMC,EAAM,GAAGC,IAAUC,IAAWC,IACpCC,EAAS,sBAAsBJ,EAAI,aAAcK,CAAI,EAErD,IAAMC,EAAU,EAAQD,EAClBE,EAAW,MAAMC,EACrB,KAAK,cACLR,EACA,CACE,SAGA,QAASS,EAAoB,KAAK,KAAM,CAAE,KAAMH,CAAQ,CAAC,EACzD,GAAIA,EAAU,CAAE,KAAM,KAAK,UAAUD,CAAI,CAAE,EAAI,CAAC,EAChD,GAAIK,GAAS,SAAW,IAAA,GAAY,CAAC,EAAI,CAAE,OAAQA,EAAQ,MAAO,CACpE,EACA,KAAK,UAGL,CAAE,MAAO,EAAM,CACjB,EAEA,GAAI,CAACH,EAAS,GACZ,MAAM,MAAMI,EAAc,kBAAkBX,EAAKO,CAAQ,EAG3D,IAAMK,EAAO,MAAMC,EAAiBN,CAAQ,EAG5C,OAFAH,EAAS,iBAAiBH,IAAUC,IAAWC,EAAS,GAAIS,CAAI,EAEzDA,CACT,CAGA,MAAM,gBACJ,EACA,EACA,EACA,EACsB,CACtB,OAAO,KAAK,YAAY,OAAQX,EAASC,EAAUC,EAAUE,CAAI,CACnE,CAGA,MAAM,eACJ,EACA,EACA,EACA,EACA,EACsB,CACtB,IAAMS,EAAqBC,EACvB,GAAGZ,EAAS,GAAGa,EAAiBD,EAAQ,KAAK,KAAK,aAAa,IAC/DZ,EAEJ,OAAO,KAAK,YAAY,MAAOF,EAASC,EAAUY,EAAoB,IAAA,GAAWJ,CAAO,CAC1F,CACF,E"}
@@ -1 +1 @@
1
- {"version":3,"file":"leaderboard.js","names":["Leaderboard","dexBackendBaseUrl","httpClient","options","response","signalOption","readEntries","parseSeason","now","season","seasonId","GSwapSDKError","rows","asEntryRecord","\"leaderboard\"","parseEntry","positiveInteger","value","isoDate","date","record","entry","entryRejected","id","\"id\"","name","\"name\"","start","\"start\"","end","\"end\"","rulesId","\"rules_id\"","entryOk","DECIMAL","decimalOf","text","parseTitle","type","order","wallet","\"wallet\"","rank","\"rank\"","rawTotal","\"total_xp\"","totalXp","distributionPercent","\"distribution_percent\"","liquidityXp","\"liquidity_xp\"","tradingXp","\"trading_xp\"","rawTitles","\"mastery_titles\"","masteryTitles","raw","title"],"sources":["../../../src/classes/leaderboard.ts"],"sourcesContent":["import type {\n LeaderboardEntry,\n LeaderboardSeason,\n MasteryTitle,\n SeasonLeaderboard,\n} from '../types/market.js';\nimport {\n asEntryRecord,\n entryOk,\n entryRejected,\n readEntries,\n type EntryParse,\n type InvalidEntryHandler,\n} from '../utils/lenient.js';\nimport { GSwapSDKError } from './gswap_sdk_error.js';\nimport type { HttpClient } from './http_client.js';\n\n/** Options of every leaderboard read. */\nexport interface LeaderboardReadOptions {\n signal?: AbortSignal | undefined;\n /** Overrides the service-level handler for this call. */\n onInvalidEntry?: InvalidEntryHandler | undefined;\n}\n\n/** Options of the reads that pick the season running now. */\nexport interface CurrentSeasonOptions extends LeaderboardReadOptions {\n /** The instant \"now\" is (default: the current time). */\n now?: Date | undefined;\n}\n\n/**\n * The swap backend's trading-competition reads: seasons (`GET /leaderboard/seasons`) and a\n * season's rankings (`GET /leaderboard?seasonId=`). Public reads: no signer, no wallet. A row that\n * is not usable is skipped and reported to `onInvalidEntry`; an answer that is not the expected\n * list is `INVALID_CHAIN_RESPONSE`.\n */\nexport class Leaderboard {\n /**\n * Creates a leaderboard service. `options.onInvalidEntry` is told about every season or entry\n * row that was skipped as unusable.\n *\n * @example\n * ```ts\n * const leaderboard = new Leaderboard('https://dex-backend-prod1.defi.gala.com', httpClient);\n * ```\n */\n constructor(\n private readonly dexBackendBaseUrl: string,\n private readonly httpClient: HttpClient,\n private readonly options: { onInvalidEntry?: InvalidEntryHandler | undefined } = {},\n ) {}\n\n /**\n * Every active season, in the backend's order (newest first).\n *\n * @example\n * ```ts\n * const seasons = await gSwap.leaderboard.getSeasons();\n * console.log(seasons.map((season) => season.name));\n * ```\n */\n public async getSeasons(options?: LeaderboardReadOptions): Promise<LeaderboardSeason[]> {\n const response = await this.httpClient.sendGetRequest<unknown>(\n this.dexBackendBaseUrl,\n '/leaderboard',\n '/seasons',\n undefined,\n signalOption(options),\n );\n return readEntries(\n 'seasons',\n response,\n parseSeason,\n options?.onInvalidEntry ?? this.options.onInvalidEntry,\n );\n }\n\n /**\n * The season running at `options.now` (default: now; `start` and `end` both count as inside),\n * or `null` between seasons. When seasons overlap, the first in the backend's order.\n *\n * @example\n * ```ts\n * const season = await gSwap.leaderboard.getCurrentSeason();\n * console.log(season === null ? 'no season running' : season.name);\n * ```\n */\n public async getCurrentSeason(options?: CurrentSeasonOptions): Promise<LeaderboardSeason | null> {\n const now = (options?.now ?? new Date()).getTime();\n const seasons = await this.getSeasons(options);\n return (\n seasons.find((season) => season.start.getTime() <= now && now <= season.end.getTime()) ?? null\n );\n }\n\n /**\n * A season's rankings (the backend's top 100 wallets). `seasonId` must be an integer of at\n * least 1 (`INVALID_SEASON_ID` before anything is sent); a season with no rankings, or none at\n * all, is an empty list.\n *\n * @example\n * ```ts\n * const { entries } = await gSwap.leaderboard.getLeaderboard(1);\n * console.log(entries[0]?.wallet, entries[0]?.totalXp);\n * ```\n */\n public async getLeaderboard(\n seasonId: number,\n options?: LeaderboardReadOptions,\n ): Promise<SeasonLeaderboard> {\n if (typeof seasonId !== 'number' || !Number.isSafeInteger(seasonId) || seasonId < 1) {\n throw new GSwapSDKError('INVALID_SEASON_ID', { value: String(seasonId) });\n }\n const response = await this.httpClient.sendGetRequest<unknown>(\n this.dexBackendBaseUrl,\n '/leaderboard',\n '',\n { seasonId: String(seasonId) },\n signalOption(options),\n );\n const rows = asEntryRecord(response)?.['leaderboard'];\n if (!Array.isArray(rows)) {\n throw new GSwapSDKError(\n 'INVALID_CHAIN_RESPONSE',\n { resource: 'leaderboard' },\n { body: response, write: false },\n );\n }\n const entries = readEntries(\n 'leaderboard',\n rows,\n parseEntry,\n options?.onInvalidEntry ?? this.options.onInvalidEntry,\n );\n return { seasonId, entries };\n }\n\n /**\n * The rankings of the season running at `options.now` (see\n * {@link Leaderboard.getCurrentSeason}), or `null` between seasons.\n *\n * @example\n * ```ts\n * const current = await gSwap.leaderboard.getCurrentLeaderboard();\n * if (current !== null) console.log(current.seasonId, current.entries.length);\n * ```\n */\n public async getCurrentLeaderboard(\n options?: CurrentSeasonOptions,\n ): Promise<SeasonLeaderboard | null> {\n const season = await this.getCurrentSeason(options);\n return season === null ? null : this.getLeaderboard(season.id, options);\n }\n}\n\nfunction signalOption(\n options: LeaderboardReadOptions | undefined,\n): { signal: AbortSignal } | undefined {\n return options?.signal === undefined ? undefined : { signal: options.signal };\n}\n\nfunction positiveInteger(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isSafeInteger(value) && value >= 1 ? value : undefined;\n}\n\nfunction isoDate(value: unknown): Date | undefined {\n if (typeof value !== 'string') return undefined;\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? undefined : date;\n}\n\nfunction parseSeason(entry: unknown): EntryParse<LeaderboardSeason> {\n const record = asEntryRecord(entry);\n if (record === undefined) return entryRejected('not an object');\n const id = positiveInteger(record['id']);\n if (id === undefined) return entryRejected('id is not an integer of at least 1');\n const name = record['name'];\n if (typeof name !== 'string') return entryRejected('name is not a string');\n const start = isoDate(record['start']);\n const end = isoDate(record['end']);\n if (start === undefined || end === undefined) return entryRejected('start or end is not a date');\n if (start.getTime() > end.getTime()) return entryRejected('start is after end');\n const rulesId = record['rules_id'];\n if (typeof rulesId !== 'number' || !Number.isSafeInteger(rulesId) || rulesId < 0) {\n return entryRejected('rules_id is not an integer');\n }\n return entryOk({ id, name, start, end, rulesId });\n}\n\n/** A non-negative decimal, as the backend's DECIMAL columns are serialized (or a finite number). */\nconst DECIMAL = /^\\d+(?:\\.\\d+)?$/u;\n\nfunction decimalOf(value: unknown): string | undefined {\n // A number is accepted only when it prints as a plain decimal (never `1e+21`).\n const text = typeof value === 'number' ? String(value) : value;\n return typeof text === 'string' && DECIMAL.test(text) ? text : undefined;\n}\n\nfunction parseTitle(value: unknown): MasteryTitle | undefined {\n const record = asEntryRecord(value);\n if (record === undefined) return undefined;\n const { name, type, order } = record;\n if (typeof name !== 'string' || typeof type !== 'string') return undefined;\n if (typeof order !== 'number' || !Number.isFinite(order)) return undefined;\n return { name, type, order };\n}\n\nfunction parseEntry(entry: unknown): EntryParse<LeaderboardEntry> {\n const record = asEntryRecord(entry);\n if (record === undefined) return entryRejected('not an object');\n const wallet = record['wallet'];\n if (typeof wallet !== 'string' || wallet === '') return entryRejected('wallet is not a string');\n const rank = positiveInteger(record['rank']);\n if (rank === undefined) return entryRejected('rank is not an integer of at least 1');\n // The backend leaves total_xp out for a ranked wallet it has no XP breakdown for.\n const rawTotal = record['total_xp'];\n const totalXp = rawTotal === undefined || rawTotal === null ? null : decimalOf(rawTotal);\n if (totalXp === undefined) return entryRejected('total_xp is not a decimal');\n const distributionPercent = decimalOf(record['distribution_percent']);\n const liquidityXp = decimalOf(record['liquidity_xp']);\n const tradingXp = decimalOf(record['trading_xp']);\n if (distributionPercent === undefined || liquidityXp === undefined || tradingXp === undefined) {\n return entryRejected('distribution_percent, liquidity_xp or trading_xp is not a decimal');\n }\n const rawTitles = record['mastery_titles'];\n if (!Array.isArray(rawTitles)) return entryRejected('mastery_titles is not a list');\n const masteryTitles: MasteryTitle[] = [];\n for (const raw of rawTitles as unknown[]) {\n const title = parseTitle(raw);\n if (title === undefined) return entryRejected('a mastery title is not { name, type, order }');\n masteryTitles.push(title);\n }\n return entryOk({\n wallet,\n rank,\n totalXp,\n distributionPercent,\n liquidityXp,\n tradingXp,\n masteryTitles,\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"leaderboard.js","names":["Leaderboard","dexBackendBaseUrl","httpClient","options","response","signalOption","readEntries","parseSeason","now","season","seasonId","GSwapSDKError","rows","asEntryRecord","\"leaderboard\"","parseEntry","positiveInteger","value","isoDate","date","record","entry","entryRejected","id","\"id\"","name","\"name\"","start","\"start\"","end","\"end\"","rulesId","\"rules_id\"","entryOk","DECIMAL","decimalOf","text","parseTitle","type","order","wallet","\"wallet\"","rank","\"rank\"","rawTotal","\"total_xp\"","totalXp","distributionPercent","\"distribution_percent\"","liquidityXp","\"liquidity_xp\"","tradingXp","\"trading_xp\"","rawTitles","\"mastery_titles\"","masteryTitles","raw","title"],"sources":["../../../src/classes/leaderboard.ts"],"sourcesContent":["import type {\n LeaderboardEntry,\n LeaderboardSeason,\n MasteryTitle,\n SeasonLeaderboard,\n} from '../types/market.js';\nimport {\n asEntryRecord,\n entryOk,\n entryRejected,\n readEntries,\n type EntryParse,\n type InvalidEntryHandler,\n} from '../utils/lenient.js';\nimport { GSwapSDKError } from './gswap_sdk_error.js';\nimport type { HttpClient } from './http_client.js';\n\n/** Options of every leaderboard read. */\nexport interface LeaderboardReadOptions {\n signal?: AbortSignal | undefined;\n /** Overrides the service-level handler for this call. */\n onInvalidEntry?: InvalidEntryHandler | undefined;\n}\n\n/** Options of the reads that pick the season running now. */\nexport interface CurrentSeasonOptions extends LeaderboardReadOptions {\n /** The instant \"now\" is (default: the current time). */\n now?: Date | undefined;\n}\n\n/**\n * The swap backend's trading-competition reads: seasons (`GET /leaderboard/seasons`) and a\n * season's rankings (`GET /leaderboard?seasonId=`). Public reads: no signer, no wallet. A row that\n * is not usable is skipped and reported to `onInvalidEntry`; an answer that is not the expected\n * list is `INVALID_CHAIN_RESPONSE`.\n */\nexport class Leaderboard {\n /**\n * Creates a leaderboard service. `options.onInvalidEntry` is told about every season or entry\n * row that was skipped as unusable.\n *\n * @example\n * ```ts\n * const leaderboard = new Leaderboard('https://dex-backend-prod1.defi.gala.com', httpClient);\n * ```\n */\n constructor(\n private readonly dexBackendBaseUrl: string,\n private readonly httpClient: HttpClient,\n private readonly options: { onInvalidEntry?: InvalidEntryHandler | undefined } = {},\n ) {}\n\n /**\n * Every active season, in the backend's order (newest first).\n *\n * @example\n * ```ts\n * const seasons = await gSwap.leaderboard.getSeasons();\n * console.log(seasons.map((season) => season.name));\n * ```\n */\n public async getSeasons(options?: LeaderboardReadOptions): Promise<LeaderboardSeason[]> {\n const response = await this.httpClient.sendGetRequest<unknown>(\n this.dexBackendBaseUrl,\n '/leaderboard',\n '/seasons',\n undefined,\n signalOption(options),\n );\n return readEntries(\n 'seasons',\n response,\n parseSeason,\n options?.onInvalidEntry ?? this.options.onInvalidEntry,\n );\n }\n\n /**\n * The season running at `options.now` (default: now; `start` and `end` both count as inside),\n * or `null` between seasons. When seasons overlap, the first in the backend's order.\n *\n * @example\n * ```ts\n * const season = await gSwap.leaderboard.getCurrentSeason();\n * console.log(season === null ? 'no season running' : season.name);\n * ```\n */\n public async getCurrentSeason(options?: CurrentSeasonOptions): Promise<LeaderboardSeason | null> {\n const now = (options?.now ?? new Date()).getTime();\n const seasons = await this.getSeasons(options);\n return (\n seasons.find((season) => season.start.getTime() <= now && now <= season.end.getTime()) ?? null\n );\n }\n\n /**\n * A season's rankings (the backend's top 100 wallets). `seasonId` must be an integer of at\n * least 1 (`INVALID_SEASON_ID` before anything is sent); a season with no rankings, or none at\n * all, is an empty list.\n *\n * @example\n * ```ts\n * const { entries } = await gSwap.leaderboard.getLeaderboard(1);\n * console.log(entries[0]?.wallet, entries[0]?.totalXp);\n * ```\n */\n public async getLeaderboard(\n seasonId: number,\n options?: LeaderboardReadOptions,\n ): Promise<SeasonLeaderboard> {\n if (typeof seasonId !== 'number' || !Number.isSafeInteger(seasonId) || seasonId < 1) {\n throw new GSwapSDKError('INVALID_SEASON_ID', { value: String(seasonId) });\n }\n const response = await this.httpClient.sendGetRequest<unknown>(\n this.dexBackendBaseUrl,\n '/leaderboard',\n '',\n { seasonId: String(seasonId) },\n signalOption(options),\n );\n const rows = asEntryRecord(response)?.['leaderboard'];\n if (!Array.isArray(rows)) {\n throw new GSwapSDKError(\n 'INVALID_CHAIN_RESPONSE',\n { resource: 'leaderboard' },\n { body: response, write: false },\n );\n }\n const entries = readEntries(\n 'leaderboard',\n rows,\n parseEntry,\n options?.onInvalidEntry ?? this.options.onInvalidEntry,\n );\n return { seasonId, entries };\n }\n\n /**\n * The rankings of the season running at `options.now` (see\n * {@link Leaderboard.getCurrentSeason}), or `null` between seasons.\n *\n * @example\n * ```ts\n * const current = await gSwap.leaderboard.getCurrentLeaderboard();\n * if (current !== null) console.log(current.seasonId, current.entries.length);\n * ```\n */\n public async getCurrentLeaderboard(\n options?: CurrentSeasonOptions,\n ): Promise<SeasonLeaderboard | null> {\n const season = await this.getCurrentSeason(options);\n return season === null ? null : this.getLeaderboard(season.id, options);\n }\n}\n\nfunction signalOption(\n options: LeaderboardReadOptions | undefined,\n): { signal: AbortSignal } | undefined {\n return options?.signal === undefined ? undefined : { signal: options.signal };\n}\n\nfunction positiveInteger(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isSafeInteger(value) && value >= 1 ? value : undefined;\n}\n\nfunction isoDate(value: unknown): Date | undefined {\n if (typeof value !== 'string') return undefined;\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? undefined : date;\n}\n\nfunction parseSeason(entry: unknown): EntryParse<LeaderboardSeason> {\n const record = asEntryRecord(entry);\n if (record === undefined) return entryRejected('not an object');\n const id = positiveInteger(record['id']);\n if (id === undefined) return entryRejected('id is not an integer of at least 1');\n const name = record['name'];\n if (typeof name !== 'string') return entryRejected('name is not a string');\n const start = isoDate(record['start']);\n const end = isoDate(record['end']);\n if (start === undefined || end === undefined) return entryRejected('start or end is not a date');\n if (start.getTime() > end.getTime()) return entryRejected('start is after end');\n const rulesId = record['rules_id'];\n if (typeof rulesId !== 'number' || !Number.isSafeInteger(rulesId) || rulesId < 0) {\n return entryRejected('rules_id is not an integer');\n }\n return entryOk({ id, name, start, end, rulesId });\n}\n\n/** A non-negative decimal, as the backend's DECIMAL columns are serialized (or a finite number). */\nconst DECIMAL = /^\\d+(?:\\.\\d+)?$/u;\n\nfunction decimalOf(value: unknown): string | undefined {\n // A number is accepted only when it prints as a plain decimal (never `1e+21`).\n const text = typeof value === 'number' ? String(value) : value;\n return typeof text === 'string' && DECIMAL.test(text) ? text : undefined;\n}\n\nfunction parseTitle(value: unknown): MasteryTitle | undefined {\n const record = asEntryRecord(value);\n if (record === undefined) return undefined;\n const { name, type, order } = record;\n if (typeof name !== 'string' || typeof type !== 'string') return undefined;\n if (typeof order !== 'number' || !Number.isFinite(order)) return undefined;\n return { name, type, order };\n}\n\nfunction parseEntry(entry: unknown): EntryParse<LeaderboardEntry> {\n const record = asEntryRecord(entry);\n if (record === undefined) return entryRejected('not an object');\n const wallet = record['wallet'];\n if (typeof wallet !== 'string' || wallet === '') return entryRejected('wallet is not a string');\n const rank = positiveInteger(record['rank']);\n if (rank === undefined) return entryRejected('rank is not an integer of at least 1');\n // The backend leaves total_xp out for a ranked wallet it has no XP breakdown for.\n const rawTotal = record['total_xp'];\n const totalXp = rawTotal === undefined || rawTotal === null ? null : decimalOf(rawTotal);\n if (totalXp === undefined) return entryRejected('total_xp is not a decimal');\n const distributionPercent = decimalOf(record['distribution_percent']);\n const liquidityXp = decimalOf(record['liquidity_xp']);\n const tradingXp = decimalOf(record['trading_xp']);\n if (distributionPercent === undefined || liquidityXp === undefined || tradingXp === undefined) {\n return entryRejected('distribution_percent, liquidity_xp or trading_xp is not a decimal');\n }\n const rawTitles = record['mastery_titles'];\n if (!Array.isArray(rawTitles)) return entryRejected('mastery_titles is not a list');\n const masteryTitles: MasteryTitle[] = [];\n for (const raw of rawTitles as unknown[]) {\n const title = parseTitle(raw);\n if (title === undefined) return entryRejected('a mastery title is not { name, type, order }');\n masteryTitles.push(title);\n }\n return entryOk({\n wallet,\n rank,\n totalXp,\n distributionPercent,\n liquidityXp,\n tradingXp,\n masteryTitles,\n });\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"market.js","names":["MARKET_TOKEN_PATTERN","Market","dexBackendBaseUrl","httpClient","options","tokens","GSwapSDKError","ids","marketTokenOf","answered","batch","priceBatches","response","signalOption","answer","asEntryRecord","id","onInvalidEntry","entry","parsed","parseMarketPrice","price","token","PRICE_SERIES_TIMEFRAMES","timeframe","currentPrice","nullableFinite","\"currentPrice\"","change24h","\"change24h\"","\"prices\"","readEntries","parsePricePoint","summary","parseVolumeSummary","\"data\"","MARKET_PRICE_QUERY_BYTES","batches","bytes","size","isTokenClassKey","describeValue","checkedMarketToken","compositeKeyOf","value","finite","record","entryRejected","\"price\"","listed","\"listed\"","entryOk","time","\"time\"","VOLUME_FIELDS","data","field","trades"],"sources":["../../../src/classes/market.ts"],"sourcesContent":["import { compositeKeyOf, isTokenClassKey } from '@gala-chain/gala-core-sdk';\nimport {\n MARKET_PRICE_BATCH_LIMIT,\n PRICE_SERIES_TIMEFRAMES,\n type MarketPrice,\n type PriceSeries,\n type PriceSeriesPoint,\n type PriceSeriesTimeframe,\n type VolumeSummary,\n} from '../types/market.js';\nimport {\n asEntryRecord,\n entryOk,\n entryRejected,\n readEntries,\n type EntryParse,\n type InvalidEntryHandler,\n} from '../utils/lenient.js';\nimport type { TokenRef } from '../utils/ordering.js';\nimport { GSwapSDKError } from './gswap_sdk_error.js';\nimport type { HttpClient } from './http_client.js';\n\n/**\n * The ids `GET /market/price` and `GET /market/price-series` accept (the backend's own allowlist):\n * a symbol, or a class key in the `$` or `|` form. A comma would split the price request.\n */\nconst MARKET_TOKEN_PATTERN = /^[A-Za-z0-9._$+/:|-]{1,120}$/u;\n\n/** Options of every market read. */\nexport interface MarketReadOptions {\n signal?: AbortSignal | undefined;\n}\n\n/** Options of {@link Market.getPrices} and {@link Market.getPriceSeries}. */\nexport interface MarketListOptions extends MarketReadOptions {\n /** Overrides the service-level handler for this call. */\n onInvalidEntry?: InvalidEntryHandler | undefined;\n}\n\n/**\n * Market data from the swap backend: USD prices (`GET /market/price`), price series\n * (`GET /market/price-series`) and the platform volume totals (`GET /v2/trade/volume`). Public\n * reads: no signer, no wallet.\n *\n * A token is named by a symbol (`GALA`) or a class key (`GALA$Unit$none$none`,\n * `GALA|Unit|none|none` or the object). The backend answers each token under the exact id it was\n * asked for, so the answer is read back under that id: a token the backend has no entry for is\n * `undefined`, never another token's price.\n */\nexport class Market {\n /**\n * Creates a market service. `options.onInvalidEntry` is told about every price or price point\n * that was skipped as unusable.\n *\n * @example\n * ```ts\n * const market = new Market('https://dex-backend-prod1.defi.gala.com', httpClient);\n * ```\n */\n constructor(\n private readonly dexBackendBaseUrl: string,\n private readonly httpClient: HttpClient,\n private readonly options: { onInvalidEntry?: InvalidEntryHandler | undefined } = {},\n ) {}\n\n /**\n * USD prices of 1 to 500 tokens, in the order asked (a token named twice is asked for once).\n * The ids go in as many requests as their length needs (see {@link MARKET_PRICE_QUERY_BYTES}),\n * one after another; any request failing fails the call. A token the backend did not answer\n * for is `undefined`; one it answered for unusably is `undefined` too, and reported to\n * `onInvalidEntry`. An answer that is not an object is `INVALID_CHAIN_RESPONSE`.\n *\n * @example\n * ```ts\n * const [gala, gusdc] = await gSwap.market.getPrices(['GALA$Unit$none$none', 'GUSDC$Unit$none$none']);\n * console.log(gala?.price, gusdc?.price);\n * ```\n */\n public async getPrices(\n tokens: readonly TokenRef[],\n options?: MarketListOptions,\n ): Promise<Array<MarketPrice | undefined>> {\n if (!Array.isArray(tokens) || tokens.length < 1 || tokens.length > MARKET_PRICE_BATCH_LIMIT) {\n throw new GSwapSDKError('INVALID_TOKEN_BATCH', {\n method: 'getPrices',\n count: Array.isArray(tokens) ? tokens.length : 0,\n max: MARKET_PRICE_BATCH_LIMIT,\n });\n }\n const ids = tokens.map(marketTokenOf);\n // One request per batch, one after another (the endpoint allows 60 a minute): each batch's\n // answer is read under the ids that batch asked for, never another batch's.\n const answered = new Map<string, unknown>();\n for (const batch of priceBatches([...new Set(ids)])) {\n const response = await this.httpClient.sendGetRequest<unknown>(\n this.dexBackendBaseUrl,\n '/market',\n '/price',\n { assetIds: batch.join(',') },\n signalOption(options),\n );\n const answer = asEntryRecord(response);\n if (answer === undefined) {\n throw new GSwapSDKError(\n 'INVALID_CHAIN_RESPONSE',\n { resource: 'market prices' },\n { body: response, write: false },\n );\n }\n for (const id of batch) {\n if (Object.hasOwn(answer, id)) answered.set(id, answer[id]);\n }\n }\n const onInvalidEntry = options?.onInvalidEntry ?? this.options.onInvalidEntry;\n return ids.map((id, index) => {\n if (!answered.has(id)) return undefined;\n const entry = answered.get(id);\n const parsed = parseMarketPrice(id, entry);\n if (parsed.ok) return parsed.value;\n try {\n onInvalidEntry?.({ source: 'prices', index, entry, reason: parsed.reason });\n } catch {\n // A diagnostics hook must not turn a lenient read into a failed one.\n }\n return undefined;\n });\n }\n\n /**\n * The USD price of one token, or `undefined` when the backend has no entry for it (see\n * {@link Market.getPrices}).\n *\n * @example\n * ```ts\n * const gala = await gSwap.market.getPrice('GALA$Unit$none$none');\n * if (gala?.price != null) console.log(`GALA: $${gala.price}`);\n * ```\n */\n public async getPrice(\n token: TokenRef,\n options?: MarketListOptions,\n ): Promise<MarketPrice | undefined> {\n const [price] = await this.getPrices([token], options);\n return price;\n }\n\n /**\n * A token's USD price line for one window (1D, 1W, 1M, 1Y or ALL; any other is\n * `INVALID_TIMEFRAME` before anything is sent). The whole series in one answer, oldest first; a\n * token the backend has no prices for is an empty series. An unusable point is skipped and\n * reported to `onInvalidEntry`; an answer that is not a series is `INVALID_CHAIN_RESPONSE`.\n *\n * @example\n * ```ts\n * const series = await gSwap.market.getPriceSeries('GALA$Unit$none$none', '1W');\n * console.log(series.prices.length, series.currentPrice);\n * ```\n */\n public async getPriceSeries(\n token: TokenRef,\n timeframe: PriceSeriesTimeframe,\n options?: MarketListOptions,\n ): Promise<PriceSeries> {\n if (!(PRICE_SERIES_TIMEFRAMES as readonly unknown[]).includes(timeframe)) {\n throw new GSwapSDKError('INVALID_TIMEFRAME', {\n value: String(timeframe),\n allowed: PRICE_SERIES_TIMEFRAMES.join(', '),\n });\n }\n const id = marketTokenOf(token);\n const response = await this.httpClient.sendGetRequest<unknown>(\n this.dexBackendBaseUrl,\n '/market',\n '/price-series',\n { token: id, timeframe },\n signalOption(options),\n );\n const answer = asEntryRecord(response);\n const currentPrice = answer === undefined ? undefined : nullableFinite(answer['currentPrice']);\n const change24h = answer === undefined ? undefined : nullableFinite(answer['change24h']);\n if (\n answer === undefined ||\n !Array.isArray(answer['prices']) ||\n currentPrice === undefined ||\n change24h === undefined\n ) {\n throw new GSwapSDKError(\n 'INVALID_CHAIN_RESPONSE',\n { resource: 'price series' },\n { body: response, write: false },\n );\n }\n const prices = readEntries(\n 'priceSeries',\n answer['prices'],\n parsePricePoint,\n options?.onInvalidEntry ?? this.options.onInvalidEntry,\n );\n return { token: id, timeframe, prices, currentPrice, change24h };\n }\n\n /**\n * TVL, volume and trade totals across every current-contract pool. Every field must be a finite\n * number, or the answer is `INVALID_CHAIN_RESPONSE`.\n *\n * @example\n * ```ts\n * const volume = await gSwap.market.getVolumeSummary();\n * console.log(`24h: $${volume.volume1d.toFixed(0)} (${(volume.volume1dDelta * 100).toFixed(1)} %)`);\n * ```\n */\n public async getVolumeSummary(options?: MarketReadOptions): Promise<VolumeSummary> {\n const response = await this.httpClient.sendGetRequest<unknown>(\n this.dexBackendBaseUrl,\n '/v2/trade',\n '/volume',\n undefined,\n signalOption(options),\n );\n const summary = parseVolumeSummary(asEntryRecord(asEntryRecord(response)?.['data']));\n if (summary === undefined) {\n throw new GSwapSDKError(\n 'INVALID_CHAIN_RESPONSE',\n { resource: 'volume summary' },\n { body: response, write: false },\n );\n }\n return summary;\n }\n}\n\n/**\n * The most query bytes one `GET /market/price` carries. The backend refuses a request line over\n * 16 KB (Node's default header limit: 300 launchpad keys, 17.7 KB encoded, answer HTTP 431,\n * recorded on prod 2026-10-13), so a long list is split well under it (about 135 launchpad keys\n * per request, 270 short ones).\n */\nexport const MARKET_PRICE_QUERY_BYTES = 8_000;\n\n/**\n * The ids in request-sized batches: at most {@link MARKET_PRICE_BATCH_LIMIT} ids and\n * {@link MARKET_PRICE_QUERY_BYTES} of encoded `assetIds` each (the standard encoding's size, the\n * larger of the two, with a `%2C` per id).\n */\nexport function priceBatches(ids: readonly string[]): string[][] {\n const batches: string[][] = [];\n let batch: string[] = [];\n let bytes = 0;\n for (const id of ids) {\n const size = encodeURIComponent(id).length + 3;\n if (\n batch.length > 0 &&\n (bytes + size > MARKET_PRICE_QUERY_BYTES || batch.length === MARKET_PRICE_BATCH_LIMIT)\n ) {\n batches.push(batch);\n batch = [];\n bytes = 0;\n }\n batch.push(id);\n bytes += size;\n }\n if (batch.length > 0) batches.push(batch);\n return batches;\n}\n\n/**\n * The id a market read sends for a token: a string as given (a symbol or a class key in either\n * form), an object key in its `$` form. Anything the backend's allowlist refuses is refused here.\n */\nfunction marketTokenOf(token: TokenRef): string {\n if (typeof token !== 'string') {\n if (!isTokenClassKey(token)) {\n throw new GSwapSDKError('INVALID_TOKEN_CLASS_KEY', {\n tokenClassKey: describeValue(token),\n });\n }\n return checkedMarketToken(compositeKeyOf(token));\n }\n return checkedMarketToken(token);\n}\n\nfunction checkedMarketToken(id: string): string {\n if (!MARKET_TOKEN_PATTERN.test(id)) {\n throw new GSwapSDKError('INVALID_MARKET_TOKEN', { token: id });\n }\n return id;\n}\n\nfunction describeValue(value: unknown): string {\n try {\n return JSON.stringify(value) ?? String(value);\n } catch {\n return Object.prototype.toString.call(value);\n }\n}\n\nfunction signalOption(options: MarketReadOptions | undefined): { signal: AbortSignal } | undefined {\n return options?.signal === undefined ? undefined : { signal: options.signal };\n}\n\n/** A finite number, `null` for `null`, `undefined` for anything else (an unusable value). */\nfunction nullableFinite(value: unknown): number | null | undefined {\n if (value === null) return null;\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\nfunction finite(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\nfunction parseMarketPrice(token: string, entry: unknown): EntryParse<MarketPrice> {\n const record = asEntryRecord(entry);\n if (record === undefined) return entryRejected('not an object');\n const price = nullableFinite(record['price']);\n if (price === undefined || (price !== null && price < 0)) {\n return entryRejected('price is not a non-negative number or null');\n }\n const change24h = nullableFinite(record['change24h']);\n if (change24h === undefined) return entryRejected('change24h is not a number or null');\n const listed = record['listed'];\n if (typeof listed !== 'boolean') return entryRejected('listed is not a boolean');\n return entryOk({ token, price, change24h, listed });\n}\n\nfunction parsePricePoint(entry: unknown): EntryParse<PriceSeriesPoint> {\n const record = asEntryRecord(entry);\n const time = finite(record?.['time']);\n const price = finite(record?.['price']);\n if (time === undefined || !Number.isInteger(time) || time < 0) {\n return entryRejected('time is not unix seconds');\n }\n if (price === undefined || price < 0) return entryRejected('price is not a non-negative number');\n return entryOk({ time, price });\n}\n\nconst VOLUME_FIELDS = [\n 'tvl',\n 'volume1d',\n 'volume7d',\n 'volume30d',\n 'trades1d',\n 'volume1dDelta',\n 'volume7dDelta',\n 'volume30dDelta',\n] as const satisfies ReadonlyArray<keyof VolumeSummary>;\n\nfunction parseVolumeSummary(data: Record<string, unknown> | undefined): VolumeSummary | undefined {\n if (data === undefined) return undefined;\n const summary: Partial<Record<keyof VolumeSummary, number>> = {};\n for (const field of VOLUME_FIELDS) {\n const value = finite(data[field]);\n if (value === undefined) return undefined;\n summary[field] = value;\n }\n const trades = summary.trades1d;\n if (trades === undefined || !Number.isInteger(trades) || trades < 0) return undefined;\n return summary as VolumeSummary;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"market.js","names":["MARKET_TOKEN_PATTERN","Market","dexBackendBaseUrl","httpClient","options","tokens","GSwapSDKError","ids","marketTokenOf","answered","batch","priceBatches","response","signalOption","answer","asEntryRecord","id","onInvalidEntry","entry","parsed","parseMarketPrice","price","token","PRICE_SERIES_TIMEFRAMES","timeframe","currentPrice","nullableFinite","\"currentPrice\"","change24h","\"change24h\"","\"prices\"","readEntries","parsePricePoint","summary","parseVolumeSummary","\"data\"","MARKET_PRICE_QUERY_BYTES","batches","bytes","size","isTokenClassKey","describeValue","checkedMarketToken","compositeKeyOf","value","finite","record","entryRejected","\"price\"","listed","\"listed\"","entryOk","time","\"time\"","VOLUME_FIELDS","data","field","trades"],"sources":["../../../src/classes/market.ts"],"sourcesContent":["import { compositeKeyOf, isTokenClassKey } from '@gala-chain/gala-core-sdk';\nimport {\n MARKET_PRICE_BATCH_LIMIT,\n PRICE_SERIES_TIMEFRAMES,\n type MarketPrice,\n type PriceSeries,\n type PriceSeriesPoint,\n type PriceSeriesTimeframe,\n type VolumeSummary,\n} from '../types/market.js';\nimport {\n asEntryRecord,\n entryOk,\n entryRejected,\n readEntries,\n type EntryParse,\n type InvalidEntryHandler,\n} from '../utils/lenient.js';\nimport type { TokenRef } from '../utils/ordering.js';\nimport { GSwapSDKError } from './gswap_sdk_error.js';\nimport type { HttpClient } from './http_client.js';\n\n/**\n * The ids `GET /market/price` and `GET /market/price-series` accept (the backend's own allowlist):\n * a symbol, or a class key in the `$` or `|` form. A comma would split the price request.\n */\nconst MARKET_TOKEN_PATTERN = /^[A-Za-z0-9._$+/:|-]{1,120}$/u;\n\n/** Options of every market read. */\nexport interface MarketReadOptions {\n signal?: AbortSignal | undefined;\n}\n\n/** Options of {@link Market.getPrices} and {@link Market.getPriceSeries}. */\nexport interface MarketListOptions extends MarketReadOptions {\n /** Overrides the service-level handler for this call. */\n onInvalidEntry?: InvalidEntryHandler | undefined;\n}\n\n/**\n * Market data from the swap backend: USD prices (`GET /market/price`), price series\n * (`GET /market/price-series`) and the platform volume totals (`GET /v2/trade/volume`). Public\n * reads: no signer, no wallet.\n *\n * A token is named by a symbol (`GALA`) or a class key (`GALA$Unit$none$none`,\n * `GALA|Unit|none|none` or the object). The backend answers each token under the exact id it was\n * asked for, so the answer is read back under that id: a token the backend has no entry for is\n * `undefined`, never another token's price.\n */\nexport class Market {\n /**\n * Creates a market service. `options.onInvalidEntry` is told about every price or price point\n * that was skipped as unusable.\n *\n * @example\n * ```ts\n * const market = new Market('https://dex-backend-prod1.defi.gala.com', httpClient);\n * ```\n */\n constructor(\n private readonly dexBackendBaseUrl: string,\n private readonly httpClient: HttpClient,\n private readonly options: { onInvalidEntry?: InvalidEntryHandler | undefined } = {},\n ) {}\n\n /**\n * USD prices of 1 to 500 tokens, in the order asked (a token named twice is asked for once).\n * The ids go in as many requests as their length needs (see {@link MARKET_PRICE_QUERY_BYTES}),\n * one after another; any request failing fails the call. A token the backend did not answer\n * for is `undefined`; one it answered for unusably is `undefined` too, and reported to\n * `onInvalidEntry`. An answer that is not an object is `INVALID_CHAIN_RESPONSE`.\n *\n * @example\n * ```ts\n * const [gala, gusdc] = await gSwap.market.getPrices(['GALA$Unit$none$none', 'GUSDC$Unit$none$none']);\n * console.log(gala?.price, gusdc?.price);\n * ```\n */\n public async getPrices(\n tokens: readonly TokenRef[],\n options?: MarketListOptions,\n ): Promise<Array<MarketPrice | undefined>> {\n if (!Array.isArray(tokens) || tokens.length < 1 || tokens.length > MARKET_PRICE_BATCH_LIMIT) {\n throw new GSwapSDKError('INVALID_TOKEN_BATCH', {\n method: 'getPrices',\n count: Array.isArray(tokens) ? tokens.length : 0,\n max: MARKET_PRICE_BATCH_LIMIT,\n });\n }\n const ids = tokens.map(marketTokenOf);\n // One request per batch, one after another (the endpoint allows 60 a minute): each batch's\n // answer is read under the ids that batch asked for, never another batch's.\n const answered = new Map<string, unknown>();\n for (const batch of priceBatches([...new Set(ids)])) {\n const response = await this.httpClient.sendGetRequest<unknown>(\n this.dexBackendBaseUrl,\n '/market',\n '/price',\n { assetIds: batch.join(',') },\n signalOption(options),\n );\n const answer = asEntryRecord(response);\n if (answer === undefined) {\n throw new GSwapSDKError(\n 'INVALID_CHAIN_RESPONSE',\n { resource: 'market prices' },\n { body: response, write: false },\n );\n }\n for (const id of batch) {\n if (Object.hasOwn(answer, id)) answered.set(id, answer[id]);\n }\n }\n const onInvalidEntry = options?.onInvalidEntry ?? this.options.onInvalidEntry;\n return ids.map((id, index) => {\n if (!answered.has(id)) return undefined;\n const entry = answered.get(id);\n const parsed = parseMarketPrice(id, entry);\n if (parsed.ok) return parsed.value;\n try {\n onInvalidEntry?.({ source: 'prices', index, entry, reason: parsed.reason });\n } catch {\n // A diagnostics hook must not turn a lenient read into a failed one.\n }\n return undefined;\n });\n }\n\n /**\n * The USD price of one token, or `undefined` when the backend has no entry for it (see\n * {@link Market.getPrices}).\n *\n * @example\n * ```ts\n * const gala = await gSwap.market.getPrice('GALA$Unit$none$none');\n * if (gala?.price != null) console.log(`GALA: $${gala.price}`);\n * ```\n */\n public async getPrice(\n token: TokenRef,\n options?: MarketListOptions,\n ): Promise<MarketPrice | undefined> {\n const [price] = await this.getPrices([token], options);\n return price;\n }\n\n /**\n * A token's USD price line for one window (1D, 1W, 1M, 1Y or ALL; any other is\n * `INVALID_TIMEFRAME` before anything is sent). The whole series in one answer, oldest first; a\n * token the backend has no prices for is an empty series. An unusable point is skipped and\n * reported to `onInvalidEntry`; an answer that is not a series is `INVALID_CHAIN_RESPONSE`.\n *\n * @example\n * ```ts\n * const series = await gSwap.market.getPriceSeries('GALA$Unit$none$none', '1W');\n * console.log(series.prices.length, series.currentPrice);\n * ```\n */\n public async getPriceSeries(\n token: TokenRef,\n timeframe: PriceSeriesTimeframe,\n options?: MarketListOptions,\n ): Promise<PriceSeries> {\n if (!(PRICE_SERIES_TIMEFRAMES as readonly unknown[]).includes(timeframe)) {\n throw new GSwapSDKError('INVALID_TIMEFRAME', {\n value: String(timeframe),\n allowed: PRICE_SERIES_TIMEFRAMES.join(', '),\n });\n }\n const id = marketTokenOf(token);\n const response = await this.httpClient.sendGetRequest<unknown>(\n this.dexBackendBaseUrl,\n '/market',\n '/price-series',\n { token: id, timeframe },\n signalOption(options),\n );\n const answer = asEntryRecord(response);\n const currentPrice = answer === undefined ? undefined : nullableFinite(answer['currentPrice']);\n const change24h = answer === undefined ? undefined : nullableFinite(answer['change24h']);\n if (\n answer === undefined ||\n !Array.isArray(answer['prices']) ||\n currentPrice === undefined ||\n change24h === undefined\n ) {\n throw new GSwapSDKError(\n 'INVALID_CHAIN_RESPONSE',\n { resource: 'price series' },\n { body: response, write: false },\n );\n }\n const prices = readEntries(\n 'priceSeries',\n answer['prices'],\n parsePricePoint,\n options?.onInvalidEntry ?? this.options.onInvalidEntry,\n );\n return { token: id, timeframe, prices, currentPrice, change24h };\n }\n\n /**\n * TVL, volume and trade totals across every current-contract pool. Every field must be a finite\n * number, or the answer is `INVALID_CHAIN_RESPONSE`.\n *\n * @example\n * ```ts\n * const volume = await gSwap.market.getVolumeSummary();\n * console.log(`24h: $${volume.volume1d.toFixed(0)} (${(volume.volume1dDelta * 100).toFixed(1)} %)`);\n * ```\n */\n public async getVolumeSummary(options?: MarketReadOptions): Promise<VolumeSummary> {\n const response = await this.httpClient.sendGetRequest<unknown>(\n this.dexBackendBaseUrl,\n '/v2/trade',\n '/volume',\n undefined,\n signalOption(options),\n );\n const summary = parseVolumeSummary(asEntryRecord(asEntryRecord(response)?.['data']));\n if (summary === undefined) {\n throw new GSwapSDKError(\n 'INVALID_CHAIN_RESPONSE',\n { resource: 'volume summary' },\n { body: response, write: false },\n );\n }\n return summary;\n }\n}\n\n/**\n * The most query bytes one `GET /market/price` carries. The backend refuses a request line over\n * 16 KB (Node's default header limit: 300 launchpad keys, 17.7 KB encoded, answer HTTP 431,\n * recorded on prod 2026-10-13), so a long list is split well under it (about 135 launchpad keys\n * per request, 270 short ones).\n */\nexport const MARKET_PRICE_QUERY_BYTES = 8_000;\n\n/**\n * The ids in request-sized batches: at most {@link MARKET_PRICE_BATCH_LIMIT} ids and\n * {@link MARKET_PRICE_QUERY_BYTES} of encoded `assetIds` each (the standard encoding's size, the\n * larger of the two, with a `%2C` per id).\n */\nexport function priceBatches(ids: readonly string[]): string[][] {\n const batches: string[][] = [];\n let batch: string[] = [];\n let bytes = 0;\n for (const id of ids) {\n const size = encodeURIComponent(id).length + 3;\n if (\n batch.length > 0 &&\n (bytes + size > MARKET_PRICE_QUERY_BYTES || batch.length === MARKET_PRICE_BATCH_LIMIT)\n ) {\n batches.push(batch);\n batch = [];\n bytes = 0;\n }\n batch.push(id);\n bytes += size;\n }\n if (batch.length > 0) batches.push(batch);\n return batches;\n}\n\n/**\n * The id a market read sends for a token: a string as given (a symbol or a class key in either\n * form), an object key in its `$` form. Anything the backend's allowlist refuses is refused here.\n */\nfunction marketTokenOf(token: TokenRef): string {\n if (typeof token !== 'string') {\n if (!isTokenClassKey(token)) {\n throw new GSwapSDKError('INVALID_TOKEN_CLASS_KEY', {\n tokenClassKey: describeValue(token),\n });\n }\n return checkedMarketToken(compositeKeyOf(token));\n }\n return checkedMarketToken(token);\n}\n\nfunction checkedMarketToken(id: string): string {\n if (!MARKET_TOKEN_PATTERN.test(id)) {\n throw new GSwapSDKError('INVALID_MARKET_TOKEN', { token: id });\n }\n return id;\n}\n\nfunction describeValue(value: unknown): string {\n try {\n return JSON.stringify(value) ?? String(value);\n } catch {\n return Object.prototype.toString.call(value);\n }\n}\n\nfunction signalOption(options: MarketReadOptions | undefined): { signal: AbortSignal } | undefined {\n return options?.signal === undefined ? undefined : { signal: options.signal };\n}\n\n/** A finite number, `null` for `null`, `undefined` for anything else (an unusable value). */\nfunction nullableFinite(value: unknown): number | null | undefined {\n if (value === null) return null;\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\nfunction finite(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\nfunction parseMarketPrice(token: string, entry: unknown): EntryParse<MarketPrice> {\n const record = asEntryRecord(entry);\n if (record === undefined) return entryRejected('not an object');\n const price = nullableFinite(record['price']);\n if (price === undefined || (price !== null && price < 0)) {\n return entryRejected('price is not a non-negative number or null');\n }\n const change24h = nullableFinite(record['change24h']);\n if (change24h === undefined) return entryRejected('change24h is not a number or null');\n const listed = record['listed'];\n if (typeof listed !== 'boolean') return entryRejected('listed is not a boolean');\n return entryOk({ token, price, change24h, listed });\n}\n\nfunction parsePricePoint(entry: unknown): EntryParse<PriceSeriesPoint> {\n const record = asEntryRecord(entry);\n const time = finite(record?.['time']);\n const price = finite(record?.['price']);\n if (time === undefined || !Number.isInteger(time) || time < 0) {\n return entryRejected('time is not unix seconds');\n }\n if (price === undefined || price < 0) return entryRejected('price is not a non-negative number');\n return entryOk({ time, price });\n}\n\nconst VOLUME_FIELDS = [\n 'tvl',\n 'volume1d',\n 'volume7d',\n 'volume30d',\n 'trades1d',\n 'volume1dDelta',\n 'volume7dDelta',\n 'volume30dDelta',\n] as const satisfies ReadonlyArray<keyof VolumeSummary>;\n\nfunction parseVolumeSummary(data: Record<string, unknown> | undefined): VolumeSummary | undefined {\n if (data === undefined) return undefined;\n const summary: Partial<Record<keyof VolumeSummary, number>> = {};\n for (const field of VOLUME_FIELDS) {\n const value = finite(data[field]);\n if (value === undefined) return undefined;\n summary[field] = value;\n }\n const trades = summary.trades1d;\n if (trades === undefined || !Number.isInteger(trades) || trades < 0) return undefined;\n return summary as VolumeSummary;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"onramper.js","names":["ONRAMPER_MODES","TEXT_FIELDS","AMOUNT_FIELDS","invalidField","GSwapSDKError","widgetBody","request","body","\"mode\"","field","value","widgetUrlOf","url","asEntryRecord","response","\"data\"","\"url\"","Onramper","dexBackendBaseUrl","httpClient","createOnramper","options","HttpClient"],"sources":["../../../src/classes/onramper.ts"],"sourcesContent":["import type { HttpRequestor, WireOptions } from '@gala-chain/gala-core-sdk';\nimport { asEntryRecord } from '../utils/lenient.js';\nimport { GSwapSDKError } from './gswap_sdk_error.js';\nimport { HttpClient } from './http_client.js';\n\n/** What the Onramper widget does. */\nexport const ONRAMPER_MODES = ['buy', 'sell', 'swap'] as const;\n\n/** One of {@link ONRAMPER_MODES}. */\nexport type OnramperMode = (typeof ONRAMPER_MODES)[number];\n\n/**\n * The request of {@link Onramper.generateWidgetUrl}: the fields of the swap backend's\n * `GenerateWidgetUrlDto`, no others (the backend refuses a field it does not know). A field left\n * out is the backend's default (`mode` `buy`, `defaultFiat` `USD`, `defaultAmount` 100,\n * `sellDefaultAmount` 1, `onlyCryptoNetworks` `gala`, `defaultPaymentMethod` `creditcard`,\n * `networkPrefix` `gala_gala`).\n */\nexport interface OnramperWidgetRequest {\n /** The wallet that receives the crypto, sent as given (the backend accepts `0x…` or `eth|…` Ethereum addresses and `client|…` aliases, and canonicalizes them). */\n walletAddress: string;\n mode?: OnramperMode | undefined;\n defaultFiat?: string | undefined;\n /** The fiat amount the widget opens with (the backend accepts 15 to 15 000, at most 2 decimals). */\n defaultAmount?: number | undefined;\n /** The crypto amount a sell opens with (0.000001 to 1 000 000, at most 18 decimals). */\n sellDefaultAmount?: number | undefined;\n onlyCryptoNetworks?: string | undefined;\n defaultPaymentMethod?: string | undefined;\n defaultCrypto?: string | undefined;\n onlyPaymentMethods?: string | undefined;\n excludePaymentMethods?: string | undefined;\n onlyFiat?: string | undefined;\n excludeFiat?: string | undefined;\n isAddressEditable?: string | undefined;\n networkPrefix?: string | undefined;\n}\n\n/** The request fields that are text, besides `walletAddress` and `mode`. */\nconst TEXT_FIELDS = [\n 'defaultFiat',\n 'onlyCryptoNetworks',\n 'defaultPaymentMethod',\n 'defaultCrypto',\n 'onlyPaymentMethods',\n 'excludePaymentMethods',\n 'onlyFiat',\n 'excludeFiat',\n 'isAddressEditable',\n 'networkPrefix',\n] as const;\n\n/** The request fields that are amounts. */\nconst AMOUNT_FIELDS = ['defaultAmount', 'sellDefaultAmount'] as const;\n\nconst METHOD = 'onramper.generateWidgetUrl';\n\nfunction invalidField(field: string, expected: string): GSwapSDKError {\n return new GSwapSDKError('INVALID_DTO', { method: METHOD, field, expected });\n}\n\n/**\n * The body to send: the fields the caller gave, checked. A field left out (or `undefined`) is not\n * sent, so the backend's default applies. An amount that is not a finite number is refused here:\n * `JSON.stringify` would turn `NaN` into `null`, which the backend reads as \"not given\" and\n * silently replaces with its default amount.\n */\nfunction widgetBody(request: OnramperWidgetRequest): Record<string, string | number> {\n if (typeof request !== 'object' || request === null) {\n throw invalidField('request', 'an object');\n }\n if (typeof request.walletAddress !== 'string' || request.walletAddress.trim() === '') {\n throw invalidField('walletAddress', 'a non-empty string');\n }\n const body: Record<string, string | number> = { walletAddress: request.walletAddress };\n if (request.mode !== undefined) {\n if (!(ONRAMPER_MODES as readonly unknown[]).includes(request.mode)) {\n throw invalidField('mode', ONRAMPER_MODES.join(', '));\n }\n body['mode'] = request.mode;\n }\n for (const field of TEXT_FIELDS) {\n const value: unknown = request[field];\n if (value === undefined) continue;\n if (typeof value !== 'string') throw invalidField(field, 'a string');\n body[field] = value;\n }\n for (const field of AMOUNT_FIELDS) {\n const value: unknown = request[field];\n if (value === undefined) continue;\n if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {\n throw invalidField(field, 'a finite number, 0 or more');\n }\n body[field] = value;\n }\n return body;\n}\n\n/** The widget URL of the backend's `{ status, error, message, data: { url } }` answer. */\nfunction widgetUrlOf(response: unknown): string {\n const data = asEntryRecord(asEntryRecord(response)?.['data']);\n const url = data?.['url'];\n if (typeof url === 'string' && url !== '') {\n try {\n // Only to know it is an absolute URL; the backend's text is returned as it is.\n void new URL(url);\n return url;\n } catch {\n // Falls through to the invalid-answer error below.\n }\n }\n throw new GSwapSDKError(\n 'INVALID_CHAIN_RESPONSE',\n { resource: 'onramper widget url' },\n { body: response, write: false },\n );\n}\n\n/**\n * The swap backend's Onramper (fiat on-ramp) widget URL. The route is PUBLIC: the request carries\n * no `Authorization` header and no signature, whatever session the app has, so no token can leak to\n * it. The backend signs the wallet into the URL; the app adds its own look (theme query parameters)\n * to the returned URL.\n */\nexport class Onramper {\n /**\n * Creates the service. To get one without building a whole {@link GSwap} client, use\n * {@link createOnramper}.\n *\n * @example\n * ```ts\n * const onramper = new Onramper('https://dex-backend-prod1.defi.gala.com', httpClient);\n * ```\n */\n constructor(\n private readonly dexBackendBaseUrl: string,\n private readonly httpClient: HttpClient,\n ) {}\n\n /**\n * Asks the backend for a signed Onramper widget URL for `request.walletAddress`.\n *\n * @returns The widget URL.\n * @throws GSwapSDKError `INVALID_DTO` (before anything is sent: no wallet address, an unknown\n * `mode`, a text field that is not text, an amount that is not a finite number of 0 or more),\n * `HTTP_ERROR` (the backend refused: `serverMessage` is its message, e.g. \"Failed to generate\n * widget URL. Check server configuration.\" for a 500, its validation message for a 400, the\n * throttle for a 429), `REQUEST_FAILED` (no answer), `INVALID_CHAIN_RESPONSE` (an answer without\n * a usable `data.url`).\n *\n * @example\n * ```ts\n * const url = await gSwap.onramper.generateWidgetUrl({\n * walletAddress: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8',\n * defaultCrypto: 'gala',\n * onlyCryptoNetworks: 'gala',\n * mode: 'buy',\n * });\n * window.open(url);\n * ```\n */\n public async generateWidgetUrl(request: OnramperWidgetRequest): Promise<string> {\n const body = widgetBody(request);\n const response = await this.httpClient.sendPostRequest<unknown>(\n this.dexBackendBaseUrl,\n '/api/v1/onramper',\n '/generate-widget-url',\n body,\n );\n return widgetUrlOf(response);\n }\n}\n\n/** Options of {@link createOnramper}. */\nexport interface OnramperOptions {\n /** The swap backend's base URL (no trailing slash needed). */\n dexBackendBaseUrl: string;\n /** Your own HTTP requestor instead of `fetch`. */\n httpRequestor?: HttpRequestor | undefined;\n /** Request timeout in milliseconds (default 30 000). */\n timeoutMs?: number | undefined;\n /** Query encoding and `User-Agent` of the requests. */\n wire?: WireOptions | undefined;\n}\n\n/**\n * An {@link Onramper} for a swap backend, without building a {@link GSwap} client (no signer, no\n * wallet, no pool or stream services): the call for an app that only needs the widget URL.\n *\n * @example\n * ```ts\n * import { createOnramper } from '@gala-chain/gswap-sdk';\n *\n * const onramper = createOnramper({ dexBackendBaseUrl: 'https://dex-backend-prod1.defi.gala.com' });\n * const url = await onramper.generateWidgetUrl({ walletAddress, mode: 'buy' });\n * ```\n */\nexport function createOnramper(options: OnramperOptions): Onramper {\n return new Onramper(\n options.dexBackendBaseUrl.replace(/\\/$/u, ''),\n new HttpClient(options.httpRequestor, options.timeoutMs, options.wire),\n );\n}\n"],"mappings":"uJAMA,MAAaA,EAAiB,CAAC,MAAO,OAAQ,MAAM,EAiC9CC,EAAc,CAClB,cACA,qBACA,uBACA,gBACA,qBACA,wBACA,WACA,cACA,oBACA,eACF,EAGMC,EAAgB,CAAC,gBAAiB,mBAAmB,EAI3D,SAASC,EAAa,EAAe,EAAiC,CACpE,OAAO,IAAIC,EAAc,cAAe,CAAE,OAAQ,6BAAQ,QAAO,UAAS,CAAC,CAC7E,CAQA,SAASC,EAAW,EAAiE,CACnF,GAAI,OAAOC,GAAY,WAAYA,EACjC,MAAMH,EAAa,UAAW,WAAW,EAE3C,GAAI,OAAOG,EAAQ,eAAkB,UAAYA,EAAQ,cAAc,KAAK,IAAM,GAChF,MAAMH,EAAa,gBAAiB,oBAAoB,EAE1D,IAAMI,EAAwC,CAAE,cAAeD,EAAQ,aAAc,EACrF,GAAIA,EAAQ,OAAS,IAAA,GAAW,CAC9B,GAAI,CAAEN,EAAsC,SAASM,EAAQ,IAAI,EAC/D,MAAMH,EAAa,OAAQH,EAAe,KAAK,IAAI,CAAC,EAEtD,EAAKQ,KAAUF,EAAQ,IACzB,CACA,IAAK,IAAMG,KAASR,EAAa,CAC/B,IAAMS,EAAiBJ,EAAQG,GAC/B,GAAIC,IAAU,IAAA,GACd,IAAI,OAAOA,GAAU,SAAU,MAAMP,EAAaM,EAAO,UAAU,EACnE,EAAKA,GAASC,CADqD,CAErE,CACA,IAAK,IAAMD,KAASP,EAAe,CACjC,IAAMQ,EAAiBJ,EAAQG,GAC/B,GAAIC,IAAU,IAAA,GACd,IAAI,OAAOA,GAAU,UAAY,CAAC,OAAO,SAASA,CAAK,GAAKA,EAAQ,EAClE,MAAMP,EAAaM,EAAO,4BAA4B,EAExD,EAAKA,GAASC,CAF0C,CAG1D,CACA,OAAOH,CACT,CAGA,SAASI,EAAY,EAA2B,CAE9C,IAAMC,EADOC,EAAcA,EAAcC,CAAQ,CAAC,EAAGC,IACtC,CAAC,EAAGC,IACnB,GAAI,OAAOJ,GAAQ,UAAYA,IAAQ,GACrC,GAAI,CAGF,OADA,IAAS,IAAIA,CAAG,EACTA,CACT,MAAQ,CAER,CAEF,MAAM,IAAIR,EACR,yBACA,CAAE,SAAU,qBAAsB,EAClC,CAAE,KAAMU,EAAU,MAAO,EAAM,CACjC,CACF,CAQA,IAAaG,EAAb,KAAsB,CAUpB,YACE,EACA,EACA,CAFiB,KAAA,kBAAAC,EACA,KAAA,WAAAC,CAChB,CAwBH,MAAa,kBAAkB,EAAiD,CAC9E,IAAMZ,EAAOF,EAAWC,CAAO,EAO/B,OAAOK,EAAY,MANI,KAAK,WAAW,gBACrC,KAAK,kBACL,mBACA,uBACAJ,CACF,CAC2B,CAC7B,CACF,EA0BA,SAAgBa,EAAe,EAAoC,CACjE,OAAO,IAAIH,EACTI,EAAQ,kBAAkB,QAAQ,OAAQ,EAAE,EAC5C,IAAIC,EAAWD,EAAQ,cAAeA,EAAQ,UAAWA,EAAQ,IAAI,CACvE,CACF"}
1
+ {"version":3,"file":"onramper.js","names":["ONRAMPER_MODES","TEXT_FIELDS","AMOUNT_FIELDS","invalidField","GSwapSDKError","widgetBody","request","body","\"mode\"","field","value","widgetUrlOf","url","asEntryRecord","response","\"data\"","\"url\"","Onramper","dexBackendBaseUrl","httpClient","createOnramper","options","HttpClient"],"sources":["../../../src/classes/onramper.ts"],"sourcesContent":["import type { HttpRequestor, WireOptions } from '@gala-chain/gala-core-sdk';\nimport { asEntryRecord } from '../utils/lenient.js';\nimport { GSwapSDKError } from './gswap_sdk_error.js';\nimport { HttpClient } from './http_client.js';\n\n/** What the Onramper widget does. */\nexport const ONRAMPER_MODES = ['buy', 'sell', 'swap'] as const;\n\n/** One of {@link ONRAMPER_MODES}. */\nexport type OnramperMode = (typeof ONRAMPER_MODES)[number];\n\n/**\n * The request of {@link Onramper.generateWidgetUrl}: the fields of the swap backend's\n * `GenerateWidgetUrlDto`, no others (the backend refuses a field it does not know). A field left\n * out is the backend's default (`mode` `buy`, `defaultFiat` `USD`, `defaultAmount` 100,\n * `sellDefaultAmount` 1, `onlyCryptoNetworks` `gala`, `defaultPaymentMethod` `creditcard`,\n * `networkPrefix` `gala_gala`).\n */\nexport interface OnramperWidgetRequest {\n /** The wallet that receives the crypto, sent as given (the backend accepts `0x…` or `eth|…` Ethereum addresses and `client|…` aliases, and canonicalizes them). */\n walletAddress: string;\n mode?: OnramperMode | undefined;\n defaultFiat?: string | undefined;\n /** The fiat amount the widget opens with (the backend accepts 15 to 15 000, at most 2 decimals). */\n defaultAmount?: number | undefined;\n /** The crypto amount a sell opens with (0.000001 to 1 000 000, at most 18 decimals). */\n sellDefaultAmount?: number | undefined;\n onlyCryptoNetworks?: string | undefined;\n defaultPaymentMethod?: string | undefined;\n defaultCrypto?: string | undefined;\n onlyPaymentMethods?: string | undefined;\n excludePaymentMethods?: string | undefined;\n onlyFiat?: string | undefined;\n excludeFiat?: string | undefined;\n isAddressEditable?: string | undefined;\n networkPrefix?: string | undefined;\n}\n\n/** The request fields that are text, besides `walletAddress` and `mode`. */\nconst TEXT_FIELDS = [\n 'defaultFiat',\n 'onlyCryptoNetworks',\n 'defaultPaymentMethod',\n 'defaultCrypto',\n 'onlyPaymentMethods',\n 'excludePaymentMethods',\n 'onlyFiat',\n 'excludeFiat',\n 'isAddressEditable',\n 'networkPrefix',\n] as const;\n\n/** The request fields that are amounts. */\nconst AMOUNT_FIELDS = ['defaultAmount', 'sellDefaultAmount'] as const;\n\nconst METHOD = 'onramper.generateWidgetUrl';\n\nfunction invalidField(field: string, expected: string): GSwapSDKError {\n return new GSwapSDKError('INVALID_DTO', { method: METHOD, field, expected });\n}\n\n/**\n * The body to send: the fields the caller gave, checked. A field left out (or `undefined`) is not\n * sent, so the backend's default applies. An amount that is not a finite number is refused here:\n * `JSON.stringify` would turn `NaN` into `null`, which the backend reads as \"not given\" and\n * silently replaces with its default amount.\n */\nfunction widgetBody(request: OnramperWidgetRequest): Record<string, string | number> {\n if (typeof request !== 'object' || request === null) {\n throw invalidField('request', 'an object');\n }\n if (typeof request.walletAddress !== 'string' || request.walletAddress.trim() === '') {\n throw invalidField('walletAddress', 'a non-empty string');\n }\n const body: Record<string, string | number> = { walletAddress: request.walletAddress };\n if (request.mode !== undefined) {\n if (!(ONRAMPER_MODES as readonly unknown[]).includes(request.mode)) {\n throw invalidField('mode', ONRAMPER_MODES.join(', '));\n }\n body['mode'] = request.mode;\n }\n for (const field of TEXT_FIELDS) {\n const value: unknown = request[field];\n if (value === undefined) continue;\n if (typeof value !== 'string') throw invalidField(field, 'a string');\n body[field] = value;\n }\n for (const field of AMOUNT_FIELDS) {\n const value: unknown = request[field];\n if (value === undefined) continue;\n if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {\n throw invalidField(field, 'a finite number, 0 or more');\n }\n body[field] = value;\n }\n return body;\n}\n\n/** The widget URL of the backend's `{ status, error, message, data: { url } }` answer. */\nfunction widgetUrlOf(response: unknown): string {\n const data = asEntryRecord(asEntryRecord(response)?.['data']);\n const url = data?.['url'];\n if (typeof url === 'string' && url !== '') {\n try {\n // Only to know it is an absolute URL; the backend's text is returned as it is.\n void new URL(url);\n return url;\n } catch {\n // Falls through to the invalid-answer error below.\n }\n }\n throw new GSwapSDKError(\n 'INVALID_CHAIN_RESPONSE',\n { resource: 'onramper widget url' },\n { body: response, write: false },\n );\n}\n\n/**\n * The swap backend's Onramper (fiat on-ramp) widget URL. The route is PUBLIC: the request carries\n * no `Authorization` header and no signature, whatever session the app has, so no token can leak to\n * it. The backend signs the wallet into the URL; the app adds its own look (theme query parameters)\n * to the returned URL.\n */\nexport class Onramper {\n /**\n * Creates the service. To get one without building a whole {@link GSwap} client, use\n * {@link createOnramper}.\n *\n * @example\n * ```ts\n * const onramper = new Onramper('https://dex-backend-prod1.defi.gala.com', httpClient);\n * ```\n */\n constructor(\n private readonly dexBackendBaseUrl: string,\n private readonly httpClient: HttpClient,\n ) {}\n\n /**\n * Asks the backend for a signed Onramper widget URL for `request.walletAddress`.\n *\n * @returns The widget URL.\n * @throws GSwapSDKError `INVALID_DTO` (before anything is sent: no wallet address, an unknown\n * `mode`, a text field that is not text, an amount that is not a finite number of 0 or more),\n * `HTTP_ERROR` (the backend refused: `serverMessage` is its message, e.g. \"Failed to generate\n * widget URL. Check server configuration.\" for a 500, its validation message for a 400, the\n * throttle for a 429), `REQUEST_FAILED` (no answer), `INVALID_CHAIN_RESPONSE` (an answer without\n * a usable `data.url`).\n *\n * @example\n * ```ts\n * const url = await gSwap.onramper.generateWidgetUrl({\n * walletAddress: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8',\n * defaultCrypto: 'gala',\n * onlyCryptoNetworks: 'gala',\n * mode: 'buy',\n * });\n * window.open(url);\n * ```\n */\n public async generateWidgetUrl(request: OnramperWidgetRequest): Promise<string> {\n const body = widgetBody(request);\n const response = await this.httpClient.sendPostRequest<unknown>(\n this.dexBackendBaseUrl,\n '/api/v1/onramper',\n '/generate-widget-url',\n body,\n );\n return widgetUrlOf(response);\n }\n}\n\n/** Options of {@link createOnramper}. */\nexport interface OnramperOptions {\n /** The swap backend's base URL (no trailing slash needed). */\n dexBackendBaseUrl: string;\n /** Your own HTTP requestor instead of `fetch`. */\n httpRequestor?: HttpRequestor | undefined;\n /** Request timeout in milliseconds (default 30 000). */\n timeoutMs?: number | undefined;\n /** Query encoding and `User-Agent` of the requests. */\n wire?: WireOptions | undefined;\n}\n\n/**\n * An {@link Onramper} for a swap backend, without building a {@link GSwap} client (no signer, no\n * wallet, no pool or stream services): the call for an app that only needs the widget URL.\n *\n * @example\n * ```ts\n * import { createOnramper } from '@gala-chain/gswap-sdk';\n *\n * const onramper = createOnramper({ dexBackendBaseUrl: 'https://dex-backend-prod1.defi.gala.com' });\n * const url = await onramper.generateWidgetUrl({ walletAddress, mode: 'buy' });\n * ```\n */\nexport function createOnramper(options: OnramperOptions): Onramper {\n return new Onramper(\n options.dexBackendBaseUrl.replace(/\\/$/u, ''),\n new HttpClient(options.httpRequestor, options.timeoutMs, options.wire),\n );\n}\n"],"mappings":"A,uJAMA,MAAaA,EAAiB,CAAC,MAAO,OAAQ,MAAM,EAiC9CC,EAAc,CAClB,cACA,qBACA,uBACA,gBACA,qBACA,wBACA,WACA,cACA,oBACA,eACF,EAGMC,EAAgB,CAAC,gBAAiB,mBAAmB,EAI3D,SAASC,EAAa,EAAe,EAAiC,CACpE,OAAO,IAAIC,EAAc,cAAe,CAAE,OAAQ,6BAAQ,QAAO,UAAS,CAAC,CAC7E,CAQA,SAASC,EAAW,EAAiE,CACnF,GAAI,OAAOC,GAAY,WAAYA,EACjC,MAAMH,EAAa,UAAW,WAAW,EAE3C,GAAI,OAAOG,EAAQ,eAAkB,UAAYA,EAAQ,cAAc,KAAK,IAAM,GAChF,MAAMH,EAAa,gBAAiB,oBAAoB,EAE1D,IAAMI,EAAwC,CAAE,cAAeD,EAAQ,aAAc,EACrF,GAAIA,EAAQ,OAAS,IAAA,GAAW,CAC9B,GAAI,CAAEN,EAAsC,SAASM,EAAQ,IAAI,EAC/D,MAAMH,EAAa,OAAQH,EAAe,KAAK,IAAI,CAAC,EAEtD,EAAKQ,KAAUF,EAAQ,IACzB,CACA,IAAK,IAAMG,KAASR,EAAa,CAC/B,IAAMS,EAAiBJ,EAAQG,GAC/B,GAAIC,IAAU,IAAA,GACd,IAAI,OAAOA,GAAU,SAAU,MAAMP,EAAaM,EAAO,UAAU,EACnE,EAAKA,GAASC,CADqD,CAErE,CACA,IAAK,IAAMD,KAASP,EAAe,CACjC,IAAMQ,EAAiBJ,EAAQG,GAC/B,GAAIC,IAAU,IAAA,GACd,IAAI,OAAOA,GAAU,UAAY,CAAC,OAAO,SAASA,CAAK,GAAKA,EAAQ,EAClE,MAAMP,EAAaM,EAAO,4BAA4B,EAExD,EAAKA,GAASC,CAF0C,CAG1D,CACA,OAAOH,CACT,CAGA,SAASI,EAAY,EAA2B,CAE9C,IAAMC,EADOC,EAAcA,EAAcC,CAAQ,CAAC,EAAGC,IACtC,CAAC,EAAGC,IACnB,GAAI,OAAOJ,GAAQ,UAAYA,IAAQ,GACrC,GAAI,CAGF,OADA,IAAS,IAAIA,CAAG,EACTA,CACT,MAAQ,CAER,CAEF,MAAM,IAAIR,EACR,yBACA,CAAE,SAAU,qBAAsB,EAClC,CAAE,KAAMU,EAAU,MAAO,EAAM,CACjC,CACF,CAQA,IAAaG,EAAb,KAAsB,CAUpB,YACE,EACA,EACA,CAFiB,KAAA,kBAAAC,EACA,KAAA,WAAAC,CAChB,CAwBH,MAAa,kBAAkB,EAAiD,CAC9E,IAAMZ,EAAOF,EAAWC,CAAO,EAO/B,OAAOK,EAAY,MANI,KAAK,WAAW,gBACrC,KAAK,kBACL,mBACA,uBACAJ,CACF,CAC2B,CAC7B,CACF,EA0BA,SAAgBa,EAAe,EAAoC,CACjE,OAAO,IAAIH,EACTI,EAAQ,kBAAkB,QAAQ,OAAQ,EAAE,EAC5C,IAAIC,EAAWD,EAAQ,cAAeA,EAAQ,UAAWA,EAAQ,IAAI,CACvE,CACF,C"}
@@ -1 +1 @@
1
- {"version":3,"file":"pending_swap_fate.js","names":["PENDING_SWAP_FATES","FATE_OF_OUTCOME","pendingSwapFateOfOutcome","outcome","pendingSwapFateFor","classifyFailure","error"],"sources":["../../../src/classes/pending_swap_fate.ts"],"sourcesContent":["import { classifyFailure } from '../utils/failure_classification.js';\nimport type { FailureOutcome } from '@gala-chain/gala-core-sdk';\n\n/**\n * What to do with a pending swap after confirming it failed (the answer of\n * {@link pendingSwapFateFor}).\n *\n * - `forget`: the chain REFUSED the trade, so it did not happen. Drop it AND tell the user.\n * - `forget_quietly`: the confirmation can never succeed (the key is refused: `invalid_input`).\n * Drop it without claiming anything about the trade.\n * - `keep`: the trade may still have been applied (a timeout, a broken connection, a gateway\n * hiccup, anything not proven). Leave it pending and look again later.\n *\n * This module holds the decision and nothing of the store, so an app shell that only needs the\n * fate pays for `classifyFailure` and this table, not for `PendingSwapStore`.\n */\nexport const PENDING_SWAP_FATES = ['forget', 'forget_quietly', 'keep'] as const;\n\n/** One member of {@link PENDING_SWAP_FATES}. */\nexport type PendingSwapFate = (typeof PENDING_SWAP_FATES)[number];\n\n/**\n * The fate of a pending swap for each failure OUTCOME. Only a chain rejection proves the trade did\n * not happen; an input that can never be confirmed is dropped quietly; every other outcome leaves\n * the trade possibly applied. Typed over `FailureOutcome`, so a new outcome does not compile until\n * it is decided here.\n */\nconst FATE_OF_OUTCOME = {\n rejected: 'forget',\n invalid_input: 'forget_quietly',\n unknown: 'keep',\n not_queued: 'keep',\n not_sent: 'keep',\n transient: 'keep',\n} as const satisfies Record<FailureOutcome, PendingSwapFate>;\n\n/**\n * {@link pendingSwapFateFor} from an outcome the app already holds (for example the\n * `classifyFailure(error).outcome` it stored on its own failure wrapper). An outcome this SDK\n * version does not know (a newer SDK at run time) is `keep`: never forget what may have happened.\n */\nexport function pendingSwapFateOfOutcome(outcome: FailureOutcome): PendingSwapFate {\n return Object.hasOwn(FATE_OF_OUTCOME, outcome) ? FATE_OF_OUTCOME[outcome] : 'keep';\n}\n\n/**\n * The fate of a queued swap whose confirmation (`confirmAppliedTrade`) just failed with `error`,\n * from the SDK's classification alone: `forget` for a chain rejection (`rejected`), `forget_quietly`\n * for a key that can never be confirmed (`invalid_input`), `keep` for every other outcome\n * (`unknown`, `not_queued`, `not_sent`, `transient`) and for any value the SDK cannot judge. It\n * accepts an SDK error or a plain-object view of one (see `FailureView`). It replaces the\n * hand-written \"forget it or keep it\" table an app would otherwise keep.\n *\n * @example\n * ```ts\n * try {\n * await confirmAppliedTrade(client, swap);\n * } catch (error) {\n * if ((await store.settle(swap.uniqueKey, error)) === 'forget') tellTheUserItFailed();\n * }\n * ```\n */\nexport function pendingSwapFateFor(error: unknown): PendingSwapFate {\n return pendingSwapFateOfOutcome(classifyFailure(error, { afterQueued: true }).outcome);\n}\n"],"mappings":"qEAgBA,MAAaA,EAAqB,CAAC,SAAU,iBAAkB,MAAM,EAW/DC,EAAkB,CACtB,SAAU,SACV,cAAe,iBACf,QAAS,OACT,WAAY,OACZ,SAAU,OACV,UAAW,MACb,EAOA,SAAgBC,EAAyB,EAA0C,CACjF,OAAO,OAAO,OAAOD,EAAiBE,CAAO,EAAIF,EAAgBE,GAAW,MAC9E,CAmBA,SAAgBC,EAAmB,EAAiC,CAClE,OAAOF,EAAyBG,EAAgBC,EAAO,CAAE,YAAa,EAAK,CAAC,CAAC,CAAC,OAAO,CACvF"}
1
+ {"version":3,"file":"pending_swap_fate.js","names":["PENDING_SWAP_FATES","FATE_OF_OUTCOME","pendingSwapFateOfOutcome","outcome","pendingSwapFateFor","classifyFailure","error"],"sources":["../../../src/classes/pending_swap_fate.ts"],"sourcesContent":["import { classifyFailure } from '../utils/failure_classification.js';\nimport type { FailureOutcome } from '@gala-chain/gala-core-sdk';\n\n/**\n * What to do with a pending swap after confirming it failed (the answer of\n * {@link pendingSwapFateFor}).\n *\n * - `forget`: the chain REFUSED the trade, so it did not happen. Drop it AND tell the user.\n * - `forget_quietly`: the confirmation can never succeed (the key is refused: `invalid_input`).\n * Drop it without claiming anything about the trade.\n * - `keep`: the trade may still have been applied (a timeout, a broken connection, a gateway\n * hiccup, anything not proven). Leave it pending and look again later.\n *\n * This module holds the decision and nothing of the store, so an app shell that only needs the\n * fate pays for `classifyFailure` and this table, not for `PendingSwapStore`.\n */\nexport const PENDING_SWAP_FATES = ['forget', 'forget_quietly', 'keep'] as const;\n\n/** One member of {@link PENDING_SWAP_FATES}. */\nexport type PendingSwapFate = (typeof PENDING_SWAP_FATES)[number];\n\n/**\n * The fate of a pending swap for each failure OUTCOME. Only a chain rejection proves the trade did\n * not happen; an input that can never be confirmed is dropped quietly; every other outcome leaves\n * the trade possibly applied. Typed over `FailureOutcome`, so a new outcome does not compile until\n * it is decided here.\n */\nconst FATE_OF_OUTCOME = {\n rejected: 'forget',\n invalid_input: 'forget_quietly',\n unknown: 'keep',\n not_queued: 'keep',\n not_sent: 'keep',\n transient: 'keep',\n} as const satisfies Record<FailureOutcome, PendingSwapFate>;\n\n/**\n * {@link pendingSwapFateFor} from an outcome the app already holds (for example the\n * `classifyFailure(error).outcome` it stored on its own failure wrapper). An outcome this SDK\n * version does not know (a newer SDK at run time) is `keep`: never forget what may have happened.\n */\nexport function pendingSwapFateOfOutcome(outcome: FailureOutcome): PendingSwapFate {\n return Object.hasOwn(FATE_OF_OUTCOME, outcome) ? FATE_OF_OUTCOME[outcome] : 'keep';\n}\n\n/**\n * The fate of a queued swap whose confirmation (`confirmAppliedTrade`) just failed with `error`,\n * from the SDK's classification alone: `forget` for a chain rejection (`rejected`), `forget_quietly`\n * for a key that can never be confirmed (`invalid_input`), `keep` for every other outcome\n * (`unknown`, `not_queued`, `not_sent`, `transient`) and for any value the SDK cannot judge. It\n * accepts an SDK error or a plain-object view of one (see `FailureView`). It replaces the\n * hand-written \"forget it or keep it\" table an app would otherwise keep.\n *\n * @example\n * ```ts\n * try {\n * await confirmAppliedTrade(client, swap);\n * } catch (error) {\n * if ((await store.settle(swap.uniqueKey, error)) === 'forget') tellTheUserItFailed();\n * }\n * ```\n */\nexport function pendingSwapFateFor(error: unknown): PendingSwapFate {\n return pendingSwapFateOfOutcome(classifyFailure(error, { afterQueued: true }).outcome);\n}\n"],"mappings":"A,qEAgBA,MAAaA,EAAqB,CAAC,SAAU,iBAAkB,MAAM,EAW/DC,EAAkB,CACtB,SAAU,SACV,cAAe,iBACf,QAAS,OACT,WAAY,OACZ,SAAU,OACV,UAAW,MACb,EAOA,SAAgBC,EAAyB,EAA0C,CACjF,OAAO,OAAO,OAAOD,EAAiBE,CAAO,EAAIF,EAAgBE,GAAW,MAC9E,CAmBA,SAAgBC,EAAmB,EAAiC,CAClE,OAAOF,EAAyBG,EAAgBC,EAAO,CAAE,YAAa,EAAK,CAAC,CAAC,CAAC,OAAO,CACvF,C"}
@@ -1,2 +1,2 @@
1
- import e from"../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/defineProperty.js";import{pendingSwapFateFor as t}from"./pending_swap_fate.js";import{isValidUniqueKey as n}from"@gala-chain/gala-core-sdk";const r=`pendingSwaps.v1`,i=6048e5,a=[`expired`,`invalid_unique_key`,`chain_rejected`,`unconfirmable`];var o=class{constructor(t,n={}){this.storage=t,e(this,`storageKey`,void 0),e(this,`darkFlagField`,void 0),e(this,`insertAt`,void 0),e(this,`listOrder`,void 0),e(this,`now`,void 0),e(this,`onPrune`,void 0),e(this,`queue`,Promise.resolve()),e(this,`toStored`,e=>({uniqueKey:e.uniqueKey,requestTransactionId:e.requestTransactionId,wallet:e.wallet,token0Symbol:e.token0Symbol,token1Symbol:e.token1Symbol,[this.darkFlagField]:e.isDarkSwap,createdAt:e.createdAt})),this.storageKey=n.storageKey??`pendingSwaps.v1`,this.darkFlagField=n.darkFlagField??`isDarkSwap`,this.insertAt=n.insertAt??`end`,this.listOrder=n.listOrder??`stored`,this.now=n.now??Date.now,this.onPrune=n.onPrune}remember(e){return n(e.uniqueKey)?this.enqueue(async t=>{let n=(await this.read(t)).filter(t=>t.uniqueKey!==e.uniqueKey).map(this.toStored),r=this.toStored(e);await t.set(this.storageKey,this.insertAt===`end`?[...n,r]:[r,...n])}):(this.report([{swap:e,reason:`invalid_unique_key`}]),Promise.resolve())}forget(e){return this.enqueue(async t=>{let n=await this.read(t);await t.set(this.storageKey,n.filter(t=>t.uniqueKey!==e).map(this.toStored))})}async settle(e,n){let r=t(n);if(r===`keep`)return r;let i=await this.enqueue(async t=>{let n=await this.read(t),r=n.find(t=>t.uniqueKey===e);if(r!==void 0)return await t.set(this.storageKey,n.filter(t=>t.uniqueKey!==e).map(this.toStored)),r});return i!==void 0&&this.report([{swap:i,reason:r===`forget`?`chain_rejected`:`unconfirmable`}]),r}async list(e){return await this.enqueue(async t=>{let n=await this.read(t),r=this.now(),i=[],a=[];for(let e of n){let t=this.pruneReasonOf(e,r);t===void 0?a.push(e):i.push({swap:e,reason:t})}i.length>0&&(await t.set(this.storageKey,a.map(this.toStored)),this.report(i));let o=a.filter(t=>t.wallet===e);return this.listOrder===`newest-first`?o.sort((e,t)=>t.createdAt-e.createdAt):o})??[]}pruneReasonOf(e,t){return n(e.uniqueKey)?t-e.createdAt<6048e5?void 0:`expired`:`invalid_unique_key`}report(e){for(let t of e)try{this.onPrune?.(t)}catch{}}enqueue(e){let t=this.storage;if(t===void 0)return Promise.resolve(void 0);let n=this.queue.then(async()=>{try{return await e(t)}catch{return}});return this.queue=n,n}async read(e){let t=await e.get(this.storageKey);if(!Array.isArray(t))return[];let n=[];for(let e of t){let t=this.fromStored(e);t!==void 0&&n.push(t)}return n}fromStored(e){if(typeof e!=`object`||!e)return;let t=e,n=t[this.darkFlagField],{uniqueKey:r,requestTransactionId:i,wallet:a,token0Symbol:o,token1Symbol:s,createdAt:c}=t;if(!(typeof r!=`string`||typeof i!=`string`&&i!==null||typeof a!=`string`||typeof o!=`string`||typeof s!=`string`||typeof n!=`boolean`||typeof c!=`number`||!Number.isFinite(c)))return{uniqueKey:r,requestTransactionId:i,wallet:a,token0Symbol:o,token1Symbol:s,isDarkSwap:n,createdAt:c}}};export{r as PENDING_SWAPS_STORAGE_KEY,i as PENDING_SWAP_EXPIRY_MS,a as PENDING_SWAP_PRUNE_REASONS,o as PendingSwapStore};
1
+ import e from"../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/defineProperty.js";import{pendingSwapFateFor as t}from"./pending_swap_fate.js";import{isValidUniqueKey as n}from"@gala-chain/gala-core-sdk";const r=`pendingSwaps.v1`,i=6048e5,a=[`expired`,`invalid_unique_key`,`chain_rejected`,`unconfirmable`];var o=class{constructor(t,n={}){this.storage=t,e(this,`storageKey`,void 0),e(this,`darkFlagField`,void 0),e(this,`insertAt`,void 0),e(this,`listOrder`,void 0),e(this,`now`,void 0),e(this,`onPrune`,void 0),e(this,`queue`,Promise.resolve()),e(this,`toStored`,e=>({uniqueKey:e.uniqueKey,requestTransactionId:e.requestTransactionId,wallet:e.wallet,token0Symbol:e.token0Symbol,token1Symbol:e.token1Symbol,[this.darkFlagField]:e.isDarkSwap,createdAt:e.createdAt})),this.storageKey=n.storageKey??`pendingSwaps.v1`,this.darkFlagField=n.darkFlagField??`isDarkSwap`,this.insertAt=n.insertAt??`end`,this.listOrder=n.listOrder??`stored`,this.now=n.now??Date.now,this.onPrune=n.onPrune}remember(e){return n(e.uniqueKey)?this.enqueue(async t=>{let n=(await this.read(t)).filter(t=>t.uniqueKey!==e.uniqueKey).map(this.toStored),r=this.toStored(e);await t.set(this.storageKey,this.insertAt===`end`?[...n,r]:[r,...n])}):(this.report([{swap:e,reason:`invalid_unique_key`}]),Promise.resolve())}forget(e){return this.enqueue(async t=>{let n=await this.read(t);await t.set(this.storageKey,n.filter(t=>t.uniqueKey!==e).map(this.toStored))})}async settle(e,n){let r=t(n);if(r===`keep`)return r;let i=await this.enqueue(async t=>{let n=await this.read(t),r=n.find(t=>t.uniqueKey===e);if(r!==void 0)return await t.set(this.storageKey,n.filter(t=>t.uniqueKey!==e).map(this.toStored)),r});return i!==void 0&&this.report([{swap:i,reason:r===`forget`?`chain_rejected`:`unconfirmable`}]),r}async list(e){return await this.enqueue(async t=>{let n=await this.read(t),r=this.now(),i=[],a=[];for(let e of n){let t=this.pruneReasonOf(e,r);t===void 0?a.push(e):i.push({swap:e,reason:t})}i.length>0&&(await t.set(this.storageKey,a.map(this.toStored)),this.report(i));let o=a.filter(t=>t.wallet===e);return this.listOrder===`newest-first`?o.sort((e,t)=>t.createdAt-e.createdAt):o})??[]}pruneReasonOf(e,t){return n(e.uniqueKey)?t-e.createdAt<6048e5?void 0:`expired`:`invalid_unique_key`}report(e){for(let t of e)try{this.onPrune?.(t)}catch{}}enqueue(e){let t=this.storage;if(t===void 0)return Promise.resolve(void 0);let n=this.queue.then(async()=>{try{return await e(t)}catch{return}});return this.queue=n,n}async read(e){let t=await e.get(this.storageKey);if(!Array.isArray(t))return[];let n=[];for(let e of t){let t=this.fromStored(e);t!==void 0&&n.push(t)}return n}fromStored(e){if(typeof e!=`object`||!e)return;let t=e,n=t[this.darkFlagField],{uniqueKey:r,requestTransactionId:i,wallet:a,token0Symbol:o,token1Symbol:s,createdAt:c}=t;if(!(typeof r!=`string`||typeof i!=`string`&&i!==null||typeof a!=`string`||typeof o!=`string`||typeof s!=`string`||typeof n!=`boolean`||typeof c!=`number`||!Number.isFinite(c)))return{uniqueKey:r,requestTransactionId:i,wallet:a,token0Symbol:o,token1Symbol:s,isDarkSwap:n,createdAt:c}}};export{r as PENDING_SWAPS_STORAGE_KEY,i as PENDING_SWAP_EXPIRY_MS,a as PENDING_SWAP_PRUNE_REASONS,o as PendingSwapStore};
2
2
  //# sourceMappingURL=pending_swaps.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"pending_swaps.js","names":["_defineProperty","PENDING_SWAPS_STORAGE_KEY","PENDING_SWAP_EXPIRY_MS","PENDING_SWAP_PRUNE_REASONS","PendingSwapStore","storage","swap","options","isValidUniqueKey","others","entry","stored","current","uniqueKey","fate","pendingSwapFateFor","error","removed","now","pruned","kept","reason","mine","wallet","right","left","run","operation","value","entries","item","flag","requestTransactionId","token0Symbol","token1Symbol","createdAt"],"sources":["../../../src/classes/pending_swaps.ts"],"sourcesContent":["import { isValidUniqueKey } from '@gala-chain/gala-core-sdk';\nimport { pendingSwapFateFor, type PendingSwapFate } from './pending_swap_fate.js';\nimport type { StorageAdapter } from './storage_adapters.js';\n\n/** Default storage key; the stored value is a plain array of entries. */\nexport const PENDING_SWAPS_STORAGE_KEY = 'pendingSwaps.v1';\n\n/** Entries older than this are dropped the next time a wallet's swaps are listed. */\nexport const PENDING_SWAP_EXPIRY_MS = 7 * 24 * 60 * 60 * 1000;\n\n/** A RequestTrade that was accepted by the gateway but whose outcome has not been read yet. */\nexport interface PendingSwap {\n uniqueKey: string;\n requestTransactionId: string | null;\n /** The wallet (account) that submitted the swap; `list` filters by it. */\n wallet: string;\n token0Symbol: string;\n token1Symbol: string;\n /** Whether the swap was a shadow (\"dark\") swap. */\n isDarkSwap: boolean;\n /** Epoch milliseconds when the swap was queued. */\n createdAt: number;\n}\n\n/**\n * Why the store dropped a pending swap (the `reason` of {@link PrunedPendingSwap}).\n *\n * - `expired`: older than {@link PENDING_SWAP_EXPIRY_MS}.\n * - `invalid_unique_key`: its `uniqueKey` is not a valid apply-results key (1 to 128 characters\n * of `A-Za-z0-9._:-`). `confirmAppliedTrade` refuses such a key with `INVALID_UNIQUE_KEY`\n * before reading anything and always will, so keeping the entry would retry it forever. The\n * SDK never signs such a key; an entry like this is corrupt or foreign.\n * - `chain_rejected`: {@link PendingSwapStore.settle} dropped it because a confirmation proved the\n * chain REFUSED the trade (`pendingSwapFateFor` is `forget`): it did not happen.\n * - `unconfirmable`: {@link PendingSwapStore.settle} dropped it because a confirmation failed in a\n * way that never changes (`forget_quietly`: the key itself is refused): nothing can be learned.\n */\nexport const PENDING_SWAP_PRUNE_REASONS = [\n 'expired',\n 'invalid_unique_key',\n 'chain_rejected',\n 'unconfirmable',\n] as const;\n\n/** One member of {@link PENDING_SWAP_PRUNE_REASONS}. */\nexport type PendingSwapPruneReason = (typeof PENDING_SWAP_PRUNE_REASONS)[number];\n\n/** A pending swap the store dropped, and why. */\nexport interface PrunedPendingSwap {\n swap: PendingSwap;\n reason: PendingSwapPruneReason;\n}\n\n/** Options for {@link PendingSwapStore}. */\nexport interface PendingSwapStoreOptions {\n /**\n * Called once per dropped entry (after the drop is persisted, for `list`; at once for a\n * `remember` that refuses an invalid key), so the app can tell the user or log why a swap it\n * was tracking is gone. Diagnostics only: what it does, or throws, never changes the store.\n */\n onPrune?: ((pruned: PrunedPendingSwap) => void) | undefined;\n /** Storage key. Default {@link PENDING_SWAPS_STORAGE_KEY}. */\n storageKey?: string | undefined;\n /**\n * The stored name of the dark-swap flag: `isDarkSwap` (default) or `shadow`, for storage written\n * by an older web app. Entries are always exposed to callers as `isDarkSwap`.\n */\n darkFlagField?: 'isDarkSwap' | 'shadow' | undefined;\n /**\n * Where `remember` puts a new entry in the stored array: `end` (default) or `start`.\n */\n insertAt?: 'end' | 'start' | undefined;\n /**\n * The order of `list`: `stored` (default: oldest first for `insertAt: 'end'`) or\n * `newest-first`.\n */\n listOrder?: 'stored' | 'newest-first' | undefined;\n /** Clock, in epoch milliseconds. Defaults to `Date.now`. */\n now?: (() => number) | undefined;\n}\n\n/**\n * Remembers queued swaps so their outcome can be confirmed after the UI, popup or service worker\n * that submitted them is gone (`confirmAppliedTrade` with the stored `uniqueKey`).\n *\n * Guarantees:\n * - Persistence is best effort and NEVER throws: a failing or missing storage must not turn an\n * accepted request into a client-side resubmission. `list` resolves `[]` when storage fails.\n * - Operations of one store instance are serialized, so concurrent `remember` / `forget` calls\n * cannot lose each other's write (read-modify-write over an async adapter). Two store instances\n * over the same storage (for example popup and service worker) are not coordinated.\n * - Entries that do not match the expected shape are ignored and dropped on the next write.\n * - Entries expire after seven days (an entry exactly seven days old is expired); `list` removes\n * them from storage.\n * - An entry whose `uniqueKey` is not a valid apply-results key is terminal, not pending:\n * `confirmAppliedTrade` fails it with `INVALID_UNIQUE_KEY` every time, so a resume loop would\n * retry it forever. `list` prunes it like an expired one, and `remember` refuses to save it;\n * both tell `onPrune` the reason (`PENDING_SWAP_PRUNE_REASONS`).\n * - New entries go to the end (or `insertAt: 'start'`); `list` returns the wallet's entries in\n * stored order (or `listOrder: 'newest-first'`).\n * - `storageKey`, `darkFlagField`, `insertAt` and `listOrder` adapt the stored layout to an\n * existing storage. JSON key order inside an entry is not preserved.\n *\n * Passing no `storage` yields a store that remembers nothing.\n *\n * @example\n * ```ts\n * const store = new PendingSwapStore({\n * get: async (key) => (await chrome.storage.local.get(key))[key],\n * set: (key, value) => chrome.storage.local.set({ [key]: value }),\n * });\n * await store.remember({ uniqueKey, requestTransactionId, wallet, token0Symbol: 'GALA',\n * token1Symbol: 'GUSDC', isDarkSwap: false, createdAt: Date.now() });\n * for (const swap of await store.list(wallet)) await confirmAppliedTrade(client, swap);\n * ```\n */\nexport class PendingSwapStore {\n private readonly storageKey: string;\n private readonly darkFlagField: 'isDarkSwap' | 'shadow';\n private readonly insertAt: 'end' | 'start';\n private readonly listOrder: 'stored' | 'newest-first';\n private readonly now: () => number;\n private readonly onPrune: ((pruned: PrunedPendingSwap) => void) | undefined;\n private queue: Promise<unknown> = Promise.resolve();\n\n constructor(\n private readonly storage: StorageAdapter | undefined,\n options: PendingSwapStoreOptions = {},\n ) {\n this.storageKey = options.storageKey ?? PENDING_SWAPS_STORAGE_KEY;\n this.darkFlagField = options.darkFlagField ?? 'isDarkSwap';\n this.insertAt = options.insertAt ?? 'end';\n this.listOrder = options.listOrder ?? 'stored';\n this.now = options.now ?? Date.now;\n this.onPrune = options.onPrune;\n }\n\n /**\n * Saves (or replaces, by `uniqueKey`) a pending swap. A swap whose `uniqueKey` is not a valid\n * apply-results key is not saved (it could never be confirmed); it is reported to `onPrune` as\n * `invalid_unique_key`.\n */\n public remember(swap: PendingSwap): Promise<void> {\n if (!isValidUniqueKey(swap.uniqueKey)) {\n this.report([{ swap, reason: 'invalid_unique_key' }]);\n return Promise.resolve();\n }\n return this.enqueue(async (storage) => {\n const current = await this.read(storage);\n const others = current\n .filter((entry) => entry.uniqueKey !== swap.uniqueKey)\n .map(this.toStored);\n const stored = this.toStored(swap);\n await storage.set(\n this.storageKey,\n this.insertAt === 'end' ? [...others, stored] : [stored, ...others],\n );\n });\n }\n\n /** Removes a pending swap once its outcome is known. */\n public forget(uniqueKey: string): Promise<void> {\n return this.enqueue(async (storage) => {\n const current = await this.read(storage);\n await storage.set(\n this.storageKey,\n current.filter((entry) => entry.uniqueKey !== uniqueKey).map(this.toStored),\n );\n });\n }\n\n /**\n * Applies {@link pendingSwapFateFor} to a swap whose confirmation failed with `error`, and\n * returns the fate so the app can word it (tell the user on `forget`, say nothing on\n * `forget_quietly`, keep polling on `keep`):\n *\n * - `forget`: the entry is removed and `onPrune` hears `chain_rejected`.\n * - `forget_quietly`: the entry is removed and `onPrune` hears `unconfirmable`.\n * - `keep`: nothing changes and `onPrune` is not called.\n *\n * Like every store operation it is serialized and best effort: if storage fails the entry stays\n * (a later `settle` or `list` handles it) and the fate is still returned. A key that is not\n * stored (already forgotten) is not an error and `onPrune` is not called for it.\n */\n public async settle(uniqueKey: string, error: unknown): Promise<PendingSwapFate> {\n const fate = pendingSwapFateFor(error);\n if (fate === 'keep') return fate;\n const removed = await this.enqueue(async (storage) => {\n const current = await this.read(storage);\n const swap = current.find((entry) => entry.uniqueKey === uniqueKey);\n if (swap === undefined) return undefined;\n await storage.set(\n this.storageKey,\n current.filter((entry) => entry.uniqueKey !== uniqueKey).map(this.toStored),\n );\n return swap;\n });\n if (removed !== undefined) {\n this.report([\n { swap: removed, reason: fate === 'forget' ? 'chain_rejected' : 'unconfirmable' },\n ]);\n }\n return fate;\n }\n\n /**\n * The wallet's pending swaps that can still be confirmed (see `listOrder`). Entries that never\n * can be are pruned from storage, for every wallet, and reported to `onPrune`: expired ones\n * (`expired`) and ones whose `uniqueKey` is not a valid apply-results key\n * (`invalid_unique_key`: `confirmAppliedTrade` would fail `INVALID_UNIQUE_KEY` on it forever).\n */\n public async list(wallet: string): Promise<PendingSwap[]> {\n const result = await this.enqueue(async (storage) => {\n const current = await this.read(storage);\n const now = this.now();\n const pruned: PrunedPendingSwap[] = [];\n const kept: PendingSwap[] = [];\n for (const entry of current) {\n const reason = this.pruneReasonOf(entry, now);\n if (reason === undefined) kept.push(entry);\n else pruned.push({ swap: entry, reason });\n }\n if (pruned.length > 0) {\n await storage.set(this.storageKey, kept.map(this.toStored));\n this.report(pruned);\n }\n const mine = kept.filter((entry) => entry.wallet === wallet);\n return this.listOrder === 'newest-first'\n ? mine.sort((left, right) => right.createdAt - left.createdAt)\n : mine;\n });\n return result ?? [];\n }\n\n private pruneReasonOf(entry: PendingSwap, now: number): PendingSwapPruneReason | undefined {\n if (!isValidUniqueKey(entry.uniqueKey)) return 'invalid_unique_key';\n return now - entry.createdAt < PENDING_SWAP_EXPIRY_MS ? undefined : 'expired';\n }\n\n /** Tells `onPrune` about each dropped entry; the hook is diagnostics and cannot break the store. */\n private report(pruned: readonly PrunedPendingSwap[]): void {\n for (const entry of pruned) {\n try {\n this.onPrune?.(entry);\n } catch {\n // A diagnostics hook must not turn a store operation into a failure.\n }\n }\n }\n\n private enqueue<T>(operation: (storage: StorageAdapter) => Promise<T>): Promise<T | undefined> {\n const storage = this.storage;\n if (storage === undefined) return Promise.resolve(undefined);\n const run = this.queue.then(async () => {\n try {\n return await operation(storage);\n } catch {\n // Storage can be missing, full, or disappear while a context is closing. Persistence is\n // best effort by contract.\n return undefined;\n }\n });\n this.queue = run;\n return run;\n }\n\n private async read(storage: StorageAdapter): Promise<PendingSwap[]> {\n const value = await storage.get(this.storageKey);\n if (!Array.isArray(value)) return [];\n const entries: PendingSwap[] = [];\n for (const item of value as unknown[]) {\n const entry = this.fromStored(item);\n if (entry !== undefined) entries.push(entry);\n }\n return entries;\n }\n\n private readonly toStored = (swap: PendingSwap): Record<string, unknown> => ({\n uniqueKey: swap.uniqueKey,\n requestTransactionId: swap.requestTransactionId,\n wallet: swap.wallet,\n token0Symbol: swap.token0Symbol,\n token1Symbol: swap.token1Symbol,\n [this.darkFlagField]: swap.isDarkSwap,\n createdAt: swap.createdAt,\n });\n\n private fromStored(value: unknown): PendingSwap | undefined {\n if (value === null || typeof value !== 'object') return undefined;\n const entry = value as Record<string, unknown>;\n const flag = entry[this.darkFlagField];\n const { uniqueKey, requestTransactionId, wallet, token0Symbol, token1Symbol, createdAt } =\n entry;\n if (\n typeof uniqueKey !== 'string' ||\n !(typeof requestTransactionId === 'string' || requestTransactionId === null) ||\n typeof wallet !== 'string' ||\n typeof token0Symbol !== 'string' ||\n typeof token1Symbol !== 'string' ||\n typeof flag !== 'boolean' ||\n typeof createdAt !== 'number' ||\n !Number.isFinite(createdAt)\n ) {\n return undefined;\n }\n return {\n uniqueKey,\n requestTransactionId,\n wallet,\n token0Symbol,\n token1Symbol,\n isDarkSwap: flag,\n createdAt,\n };\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"pending_swaps.js","names":["_defineProperty","PENDING_SWAPS_STORAGE_KEY","PENDING_SWAP_EXPIRY_MS","PENDING_SWAP_PRUNE_REASONS","PendingSwapStore","storage","swap","options","isValidUniqueKey","others","entry","stored","current","uniqueKey","fate","pendingSwapFateFor","error","removed","now","pruned","kept","reason","mine","wallet","right","left","run","operation","value","entries","item","flag","requestTransactionId","token0Symbol","token1Symbol","createdAt"],"sources":["../../../src/classes/pending_swaps.ts"],"sourcesContent":["import { isValidUniqueKey } from '@gala-chain/gala-core-sdk';\nimport { pendingSwapFateFor, type PendingSwapFate } from './pending_swap_fate.js';\nimport type { StorageAdapter } from './storage_adapters.js';\n\n/** Default storage key; the stored value is a plain array of entries. */\nexport const PENDING_SWAPS_STORAGE_KEY = 'pendingSwaps.v1';\n\n/** Entries older than this are dropped the next time a wallet's swaps are listed. */\nexport const PENDING_SWAP_EXPIRY_MS = 7 * 24 * 60 * 60 * 1000;\n\n/** A RequestTrade that was accepted by the gateway but whose outcome has not been read yet. */\nexport interface PendingSwap {\n uniqueKey: string;\n requestTransactionId: string | null;\n /** The wallet (account) that submitted the swap; `list` filters by it. */\n wallet: string;\n token0Symbol: string;\n token1Symbol: string;\n /** Whether the swap was a shadow (\"dark\") swap. */\n isDarkSwap: boolean;\n /** Epoch milliseconds when the swap was queued. */\n createdAt: number;\n}\n\n/**\n * Why the store dropped a pending swap (the `reason` of {@link PrunedPendingSwap}).\n *\n * - `expired`: older than {@link PENDING_SWAP_EXPIRY_MS}.\n * - `invalid_unique_key`: its `uniqueKey` is not a valid apply-results key (1 to 128 characters\n * of `A-Za-z0-9._:-`). `confirmAppliedTrade` refuses such a key with `INVALID_UNIQUE_KEY`\n * before reading anything and always will, so keeping the entry would retry it forever. The\n * SDK never signs such a key; an entry like this is corrupt or foreign.\n * - `chain_rejected`: {@link PendingSwapStore.settle} dropped it because a confirmation proved the\n * chain REFUSED the trade (`pendingSwapFateFor` is `forget`): it did not happen.\n * - `unconfirmable`: {@link PendingSwapStore.settle} dropped it because a confirmation failed in a\n * way that never changes (`forget_quietly`: the key itself is refused): nothing can be learned.\n */\nexport const PENDING_SWAP_PRUNE_REASONS = [\n 'expired',\n 'invalid_unique_key',\n 'chain_rejected',\n 'unconfirmable',\n] as const;\n\n/** One member of {@link PENDING_SWAP_PRUNE_REASONS}. */\nexport type PendingSwapPruneReason = (typeof PENDING_SWAP_PRUNE_REASONS)[number];\n\n/** A pending swap the store dropped, and why. */\nexport interface PrunedPendingSwap {\n swap: PendingSwap;\n reason: PendingSwapPruneReason;\n}\n\n/** Options for {@link PendingSwapStore}. */\nexport interface PendingSwapStoreOptions {\n /**\n * Called once per dropped entry (after the drop is persisted, for `list`; at once for a\n * `remember` that refuses an invalid key), so the app can tell the user or log why a swap it\n * was tracking is gone. Diagnostics only: what it does, or throws, never changes the store.\n */\n onPrune?: ((pruned: PrunedPendingSwap) => void) | undefined;\n /** Storage key. Default {@link PENDING_SWAPS_STORAGE_KEY}. */\n storageKey?: string | undefined;\n /**\n * The stored name of the dark-swap flag: `isDarkSwap` (default) or `shadow`, for storage written\n * by an older web app. Entries are always exposed to callers as `isDarkSwap`.\n */\n darkFlagField?: 'isDarkSwap' | 'shadow' | undefined;\n /**\n * Where `remember` puts a new entry in the stored array: `end` (default) or `start`.\n */\n insertAt?: 'end' | 'start' | undefined;\n /**\n * The order of `list`: `stored` (default: oldest first for `insertAt: 'end'`) or\n * `newest-first`.\n */\n listOrder?: 'stored' | 'newest-first' | undefined;\n /** Clock, in epoch milliseconds. Defaults to `Date.now`. */\n now?: (() => number) | undefined;\n}\n\n/**\n * Remembers queued swaps so their outcome can be confirmed after the UI, popup or service worker\n * that submitted them is gone (`confirmAppliedTrade` with the stored `uniqueKey`).\n *\n * Guarantees:\n * - Persistence is best effort and NEVER throws: a failing or missing storage must not turn an\n * accepted request into a client-side resubmission. `list` resolves `[]` when storage fails.\n * - Operations of one store instance are serialized, so concurrent `remember` / `forget` calls\n * cannot lose each other's write (read-modify-write over an async adapter). Two store instances\n * over the same storage (for example popup and service worker) are not coordinated.\n * - Entries that do not match the expected shape are ignored and dropped on the next write.\n * - Entries expire after seven days (an entry exactly seven days old is expired); `list` removes\n * them from storage.\n * - An entry whose `uniqueKey` is not a valid apply-results key is terminal, not pending:\n * `confirmAppliedTrade` fails it with `INVALID_UNIQUE_KEY` every time, so a resume loop would\n * retry it forever. `list` prunes it like an expired one, and `remember` refuses to save it;\n * both tell `onPrune` the reason (`PENDING_SWAP_PRUNE_REASONS`).\n * - New entries go to the end (or `insertAt: 'start'`); `list` returns the wallet's entries in\n * stored order (or `listOrder: 'newest-first'`).\n * - `storageKey`, `darkFlagField`, `insertAt` and `listOrder` adapt the stored layout to an\n * existing storage. JSON key order inside an entry is not preserved.\n *\n * Passing no `storage` yields a store that remembers nothing.\n *\n * @example\n * ```ts\n * const store = new PendingSwapStore({\n * get: async (key) => (await chrome.storage.local.get(key))[key],\n * set: (key, value) => chrome.storage.local.set({ [key]: value }),\n * });\n * await store.remember({ uniqueKey, requestTransactionId, wallet, token0Symbol: 'GALA',\n * token1Symbol: 'GUSDC', isDarkSwap: false, createdAt: Date.now() });\n * for (const swap of await store.list(wallet)) await confirmAppliedTrade(client, swap);\n * ```\n */\nexport class PendingSwapStore {\n private readonly storageKey: string;\n private readonly darkFlagField: 'isDarkSwap' | 'shadow';\n private readonly insertAt: 'end' | 'start';\n private readonly listOrder: 'stored' | 'newest-first';\n private readonly now: () => number;\n private readonly onPrune: ((pruned: PrunedPendingSwap) => void) | undefined;\n private queue: Promise<unknown> = Promise.resolve();\n\n constructor(\n private readonly storage: StorageAdapter | undefined,\n options: PendingSwapStoreOptions = {},\n ) {\n this.storageKey = options.storageKey ?? PENDING_SWAPS_STORAGE_KEY;\n this.darkFlagField = options.darkFlagField ?? 'isDarkSwap';\n this.insertAt = options.insertAt ?? 'end';\n this.listOrder = options.listOrder ?? 'stored';\n this.now = options.now ?? Date.now;\n this.onPrune = options.onPrune;\n }\n\n /**\n * Saves (or replaces, by `uniqueKey`) a pending swap. A swap whose `uniqueKey` is not a valid\n * apply-results key is not saved (it could never be confirmed); it is reported to `onPrune` as\n * `invalid_unique_key`.\n */\n public remember(swap: PendingSwap): Promise<void> {\n if (!isValidUniqueKey(swap.uniqueKey)) {\n this.report([{ swap, reason: 'invalid_unique_key' }]);\n return Promise.resolve();\n }\n return this.enqueue(async (storage) => {\n const current = await this.read(storage);\n const others = current\n .filter((entry) => entry.uniqueKey !== swap.uniqueKey)\n .map(this.toStored);\n const stored = this.toStored(swap);\n await storage.set(\n this.storageKey,\n this.insertAt === 'end' ? [...others, stored] : [stored, ...others],\n );\n });\n }\n\n /** Removes a pending swap once its outcome is known. */\n public forget(uniqueKey: string): Promise<void> {\n return this.enqueue(async (storage) => {\n const current = await this.read(storage);\n await storage.set(\n this.storageKey,\n current.filter((entry) => entry.uniqueKey !== uniqueKey).map(this.toStored),\n );\n });\n }\n\n /**\n * Applies {@link pendingSwapFateFor} to a swap whose confirmation failed with `error`, and\n * returns the fate so the app can word it (tell the user on `forget`, say nothing on\n * `forget_quietly`, keep polling on `keep`):\n *\n * - `forget`: the entry is removed and `onPrune` hears `chain_rejected`.\n * - `forget_quietly`: the entry is removed and `onPrune` hears `unconfirmable`.\n * - `keep`: nothing changes and `onPrune` is not called.\n *\n * Like every store operation it is serialized and best effort: if storage fails the entry stays\n * (a later `settle` or `list` handles it) and the fate is still returned. A key that is not\n * stored (already forgotten) is not an error and `onPrune` is not called for it.\n */\n public async settle(uniqueKey: string, error: unknown): Promise<PendingSwapFate> {\n const fate = pendingSwapFateFor(error);\n if (fate === 'keep') return fate;\n const removed = await this.enqueue(async (storage) => {\n const current = await this.read(storage);\n const swap = current.find((entry) => entry.uniqueKey === uniqueKey);\n if (swap === undefined) return undefined;\n await storage.set(\n this.storageKey,\n current.filter((entry) => entry.uniqueKey !== uniqueKey).map(this.toStored),\n );\n return swap;\n });\n if (removed !== undefined) {\n this.report([\n { swap: removed, reason: fate === 'forget' ? 'chain_rejected' : 'unconfirmable' },\n ]);\n }\n return fate;\n }\n\n /**\n * The wallet's pending swaps that can still be confirmed (see `listOrder`). Entries that never\n * can be are pruned from storage, for every wallet, and reported to `onPrune`: expired ones\n * (`expired`) and ones whose `uniqueKey` is not a valid apply-results key\n * (`invalid_unique_key`: `confirmAppliedTrade` would fail `INVALID_UNIQUE_KEY` on it forever).\n */\n public async list(wallet: string): Promise<PendingSwap[]> {\n const result = await this.enqueue(async (storage) => {\n const current = await this.read(storage);\n const now = this.now();\n const pruned: PrunedPendingSwap[] = [];\n const kept: PendingSwap[] = [];\n for (const entry of current) {\n const reason = this.pruneReasonOf(entry, now);\n if (reason === undefined) kept.push(entry);\n else pruned.push({ swap: entry, reason });\n }\n if (pruned.length > 0) {\n await storage.set(this.storageKey, kept.map(this.toStored));\n this.report(pruned);\n }\n const mine = kept.filter((entry) => entry.wallet === wallet);\n return this.listOrder === 'newest-first'\n ? mine.sort((left, right) => right.createdAt - left.createdAt)\n : mine;\n });\n return result ?? [];\n }\n\n private pruneReasonOf(entry: PendingSwap, now: number): PendingSwapPruneReason | undefined {\n if (!isValidUniqueKey(entry.uniqueKey)) return 'invalid_unique_key';\n return now - entry.createdAt < PENDING_SWAP_EXPIRY_MS ? undefined : 'expired';\n }\n\n /** Tells `onPrune` about each dropped entry; the hook is diagnostics and cannot break the store. */\n private report(pruned: readonly PrunedPendingSwap[]): void {\n for (const entry of pruned) {\n try {\n this.onPrune?.(entry);\n } catch {\n // A diagnostics hook must not turn a store operation into a failure.\n }\n }\n }\n\n private enqueue<T>(operation: (storage: StorageAdapter) => Promise<T>): Promise<T | undefined> {\n const storage = this.storage;\n if (storage === undefined) return Promise.resolve(undefined);\n const run = this.queue.then(async () => {\n try {\n return await operation(storage);\n } catch {\n // Storage can be missing, full, or disappear while a context is closing. Persistence is\n // best effort by contract.\n return undefined;\n }\n });\n this.queue = run;\n return run;\n }\n\n private async read(storage: StorageAdapter): Promise<PendingSwap[]> {\n const value = await storage.get(this.storageKey);\n if (!Array.isArray(value)) return [];\n const entries: PendingSwap[] = [];\n for (const item of value as unknown[]) {\n const entry = this.fromStored(item);\n if (entry !== undefined) entries.push(entry);\n }\n return entries;\n }\n\n private readonly toStored = (swap: PendingSwap): Record<string, unknown> => ({\n uniqueKey: swap.uniqueKey,\n requestTransactionId: swap.requestTransactionId,\n wallet: swap.wallet,\n token0Symbol: swap.token0Symbol,\n token1Symbol: swap.token1Symbol,\n [this.darkFlagField]: swap.isDarkSwap,\n createdAt: swap.createdAt,\n });\n\n private fromStored(value: unknown): PendingSwap | undefined {\n if (value === null || typeof value !== 'object') return undefined;\n const entry = value as Record<string, unknown>;\n const flag = entry[this.darkFlagField];\n const { uniqueKey, requestTransactionId, wallet, token0Symbol, token1Symbol, createdAt } =\n entry;\n if (\n typeof uniqueKey !== 'string' ||\n !(typeof requestTransactionId === 'string' || requestTransactionId === null) ||\n typeof wallet !== 'string' ||\n typeof token0Symbol !== 'string' ||\n typeof token1Symbol !== 'string' ||\n typeof flag !== 'boolean' ||\n typeof createdAt !== 'number' ||\n !Number.isFinite(createdAt)\n ) {\n return undefined;\n }\n return {\n uniqueKey,\n requestTransactionId,\n wallet,\n token0Symbol,\n token1Symbol,\n isDarkSwap: flag,\n createdAt,\n };\n }\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import e from"../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/defineProperty.js";import{GSwapSDKError as t,isGSwapError as n}from"./gswap_sdk_error.js";import{validateFee as r}from"../utils/validation.js";import{HttpClient as i}from"./http_client.js";import{asEntryRecord as a,entryOk as o,entryRejected as s,finiteNumberOf as c,readEntries as l}from"../utils/lenient.js";import{orderSymbols as u,readOrderedSymbols as d}from"../utils/ordering.js";import{queryTokenOf as f,rawTokenOf as p,tradingSymbolOf as m}from"./symbols.js";var h=class{constructor(t,n,r={}){this.gateway=t,this.symbols=n,this.options=r,e(this,`http`,void 0),this.http=new i(t.httpRequestor,t.requestTimeoutMs??3e4,t.wire)}async getPools(e){let t=await this.http.sendGetRequest(this.gateway.dexBackendBaseUrl,`/v2/trade`,`/pools`,v(e),e?.signal===void 0?void 0:{signal:e.signal});return l(`pools`,t.data,S,e?.onInvalidEntry??this.options.onInvalidEntry)}watchNewPools(e,n={}){let r=n.intervalMs??3e4;if(typeof r!=`number`||!Number.isFinite(r)||r<1)throw new t(`INVALID_POLL_INTERVAL`,{value:String(r)});let i,a=!1,o=!1,s=e=>{try{n.onError?.(e)}catch{}},c=e=>(n.minTvl===void 0||(e.tvlUsd??0)>=n.minTvl)&&(n.tokens===void 0||n.tokens.includes(e.token0)||n.tokens.includes(e.token1)),l=async()=>{if(!(a||o)){o=!0;try{let t=await this.getPools();if(i===void 0){i=new Set(t.map(e=>e.poolRef));return}for(let n of t){if(a)return;if(!i.has(n.poolRef)&&(i.add(n.poolRef),c(n)))try{e(n)}catch(e){s(e)}}}catch(e){a||s(e)}finally{o=!1}}};l();let u=setInterval(()=>void l(),r);return()=>{a=!0,clearInterval(u)}}async getPool(e,t,n){return r(n),this.getBackend(`/pool`,await this.backendParams(e,t,n))}async getSlot0(e,t,n){return r(n),this.getBackend(`/slot0`,await this.backendParams(e,t,n))}async getCompositePool(e,t,n){if(r(n),this.symbols.passthrough===!0)return this.getBackend(`/composite-pool`,{token0:p(e),token1:p(t),fee:String(n)});let i=await m(this.symbols,e),a=await m(this.symbols,t),o=d(u(i,a));return this.getBackend(`/composite-pool`,{token0:o.token0,token1:o.token1,fee:String(n)})}async backendParams(e,t,n){return{token0:await f(this.symbols,e),token1:await f(this.symbols,t),fee:String(n)}}async getBackend(e,r){try{return(await this.http.sendGetRequest(this.gateway.dexBackendBaseUrl,`/v2/trade`,e,r)).data}catch(e){throw n(e)?e:new t(`REQUEST_FAILED`,{operation:`pool`},{cause:e,write:!1})}}};const g=[`tvl`,`volume1d`,`volume30d`],_=1e3;function v(e){if(e===void 0)return;let n={};if(e.search!==void 0){if(e.search.length>64)throw new t(`SEARCH_TOO_LONG`,{length:e.search.length,maxLength:64});n.search=e.search}if(e.sortBy!==void 0){if(!g.includes(e.sortBy))throw new t(`INVALID_SORT_BY`,{value:String(e.sortBy),allowed:g.join(`,`)});n.sortBy=e.sortBy}if(e.sortOrder!==void 0){if(e.sortOrder!==`asc`&&e.sortOrder!==`desc`)throw new t(`INVALID_SORT_ORDER`,{value:String(e.sortOrder)});n.sortOrder=e.sortOrder}if(e.page!==void 0){if(!Number.isInteger(e.page)||e.page<1)throw new t(`INVALID_PAGE`,{value:e.page});n.page=String(e.page)}if(e.limit!==void 0){if(!Number.isInteger(e.limit)||e.limit<1||e.limit>_)throw new t(`INVALID_LIMIT`,{value:e.limit,min:1,max:_});n.limit=String(e.limit)}return Object.keys(n).length===0?void 0:n}const y=[`poolName`,`token0CompositeKey`,`token1CompositeKey`,`token0Image`,`token1Image`,`creator`,`price`,`sqrtPrice`,`token0Tvl`,`token1Tvl`],b=[`tickSpacing`,`protocolFees`,`tradingFees`,`tick`,`volume1d`,`volume7d`,`volume30d`,`trades1d`,`fee24h`,`token0Price`,`token1Price`],x=[`fee`,`tvlUsd`,`apr1d`];function S(e){let t=a(e);if(t===void 0)return s(`the row is not an object`);for(let e of[`token0`,`token1`,`poolRef`])if(typeof t[e]!=`string`)return s(`${e} is not a string`);if(t.contractVersion!==void 0&&t.contractVersion!==`v2`)return s(`contractVersion is not v2`);let n={...t};for(let e of x){let r=t[e];if(r===void 0&&e!==`fee`)continue;let i=c(r);if(i===void 0)return s(`${e} is not a number`);n[e]=i}for(let e of y)if(t[e]!==void 0&&typeof t[e]!=`string`)return s(`${e} is not a string`);for(let e of b){let n=t[e];if(n!==void 0&&(typeof n!=`number`||!Number.isFinite(n)))return s(`${e} is not a number`)}return o(n)}export{h as Pools};
1
+ import e from"../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/defineProperty.js";import{GSwapSDKError as t,isGSwapError as n}from"./gswap_sdk_error.js";import{validateFee as r}from"../utils/validation.js";import{HttpClient as i}from"./http_client.js";import{asEntryRecord as a,entryOk as o,entryRejected as s,finiteNumberOf as c,readEntries as l}from"../utils/lenient.js";import{orderSymbols as u,readOrderedSymbols as d}from"../utils/ordering.js";import{queryTokenOf as f,rawTokenOf as p,tradingSymbolOf as m}from"./symbols.js";var h=class{constructor(t,n,r={}){this.gateway=t,this.symbols=n,this.options=r,e(this,`http`,void 0),this.http=new i(t.httpRequestor,t.requestTimeoutMs??3e4,t.wire)}async getPools(e){let t=await this.http.sendGetRequest(this.gateway.dexBackendBaseUrl,`/v2/trade`,`/pools`,v(e),e?.signal===void 0?void 0:{signal:e.signal});return l(`pools`,t.data,S,e?.onInvalidEntry??this.options.onInvalidEntry)}watchNewPools(e,n={}){let r=n.intervalMs??3e4;if(typeof r!=`number`||!Number.isFinite(r)||r<1)throw new t(`INVALID_POLL_INTERVAL`,{value:String(r)});let i,a=!1,o=!1,s=e=>{try{n.onError?.(e)}catch{}},c=e=>(n.minTvl===void 0||(e.tvlUsd??0)>=n.minTvl)&&(n.tokens===void 0||n.tokens.includes(e.token0)||n.tokens.includes(e.token1)),l=async()=>{if(!(a||o)){o=!0;try{let t=await this.getPools();if(i===void 0){i=new Set(t.map(e=>e.poolRef));return}for(let n of t){if(a)return;if(!i.has(n.poolRef)&&(i.add(n.poolRef),c(n)))try{e(n)}catch(e){s(e)}}}catch(e){a||s(e)}finally{o=!1}}};l();let u=setInterval(()=>void l(),r);return()=>{a=!0,clearInterval(u)}}async getPool(e,t,n){return r(n),this.getBackend(`/pool`,await this.backendParams(e,t,n))}async getSlot0(e,t,n){return r(n),this.getBackend(`/slot0`,await this.backendParams(e,t,n))}async getCompositePool(e,t,n){if(r(n),this.symbols.passthrough===!0)return this.getBackend(`/composite-pool`,{token0:p(e),token1:p(t),fee:String(n)});let i=await m(this.symbols,e),a=await m(this.symbols,t),o=d(u(i,a));return this.getBackend(`/composite-pool`,{token0:o.token0,token1:o.token1,fee:String(n)})}async backendParams(e,t,n){return{token0:await f(this.symbols,e),token1:await f(this.symbols,t),fee:String(n)}}async getBackend(e,r){try{return(await this.http.sendGetRequest(this.gateway.dexBackendBaseUrl,`/v2/trade`,e,r)).data}catch(e){throw n(e)?e:new t(`REQUEST_FAILED`,{operation:`pool`},{cause:e,write:!1})}}};const g=[`tvl`,`volume1d`,`volume30d`],_=1e3;function v(e){if(e===void 0)return;let n={};if(e.search!==void 0){if(e.search.length>64)throw new t(`SEARCH_TOO_LONG`,{length:e.search.length,maxLength:64});n.search=e.search}if(e.sortBy!==void 0){if(!g.includes(e.sortBy))throw new t(`INVALID_SORT_BY`,{value:String(e.sortBy),allowed:g.join(`,`)});n.sortBy=e.sortBy}if(e.sortOrder!==void 0){if(e.sortOrder!==`asc`&&e.sortOrder!==`desc`)throw new t(`INVALID_SORT_ORDER`,{value:String(e.sortOrder)});n.sortOrder=e.sortOrder}if(e.page!==void 0){if(!Number.isInteger(e.page)||e.page<1)throw new t(`INVALID_PAGE`,{value:e.page});n.page=String(e.page)}if(e.limit!==void 0){if(!Number.isInteger(e.limit)||e.limit<1||e.limit>_)throw new t(`INVALID_LIMIT`,{value:e.limit,min:1,max:_});n.limit=String(e.limit)}return Object.keys(n).length===0?void 0:n}const y=[`poolName`,`token0CompositeKey`,`token1CompositeKey`,`token0Image`,`token1Image`,`creator`,`price`,`sqrtPrice`,`token0Tvl`,`token1Tvl`],b=[`tickSpacing`,`protocolFees`,`tradingFees`,`tick`,`volume1d`,`volume7d`,`volume30d`,`trades1d`,`fee24h`,`token0Price`,`token1Price`],x=[`fee`,`tvlUsd`,`apr1d`];function S(e){let t=a(e);if(t===void 0)return s(`the row is not an object`);for(let e of[`token0`,`token1`,`poolRef`])if(typeof t[e]!=`string`)return s(`${e} is not a string`);if(t.contractVersion!==void 0&&t.contractVersion!==`v2`)return s(`contractVersion is not v2`);let n={...t};for(let e of x){let r=t[e];if(r===void 0&&e!==`fee`)continue;let i=c(r);if(i===void 0)return s(`${e} is not a number`);n[e]=i}for(let e of y)if(t[e]!==void 0&&typeof t[e]!=`string`)return s(`${e} is not a string`);for(let e of b){let n=t[e];if(n!==void 0&&(typeof n!=`number`||!Number.isFinite(n)))return s(`${e} is not a number`)}return o(n)}export{h as Pools};
2
2
  //# sourceMappingURL=pools.js.map