@gala-chain/gswap-sdk 1.2.0-rc.9 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # gSwap SDK
2
2
 
3
3
  `@gala-chain/gswap-sdk` is the TypeScript SDK for the current GalaChain DEX
4
- contract. Version 1.0 uses trading symbols for pool operations, signs writes
5
- with the signer supplied by the application, and submits them through the Chain
4
+ contract. It uses trading symbols for pool operations, signs writes with the
5
+ signer supplied by the application, and submits them through the Chain
6
6
  Gateway. Swap requests are queued for later application.
7
7
 
8
8
  ## Install
@@ -11,6 +11,10 @@ Gateway. Swap requests are queued for later application.
11
11
  npm install @gala-chain/gswap-sdk
12
12
  ```
13
13
 
14
+ `latest` is `1.2.0` (signing primitives, the error catalog, failure
15
+ classification, the `/wallet` subpath). Pin an exact version
16
+ (`@gala-chain/gswap-sdk@1.2.0`) for a wallet or app build.
17
+
14
18
  ## Quick start
15
19
 
16
20
  ```typescript
@@ -65,28 +69,39 @@ See the [realtime stream guide](./docs/docs/tutorial-basics/realtime-stream.md).
65
69
 
66
70
  ## Signers and identity
67
71
 
68
- | Signer | Scheme | Identity resolved by the chain |
69
- | --------------------- | ---------------------------------------------------------------------------------- | ---------------------------------------- |
70
- | `PrivateKeySigner` | GalaChain native signature | Recovered from the private key |
71
- | `GalaWalletSigner` | Native `gala_signChainDto`; opt-in `personal-sign` needs a wallet with fix 3ad7ae6 | Selected Ethereum account (`0x...` form) |
72
- | `BrowserWalletSigner` | EIP-1193 `personal_sign` | Selected Ethereum account (`0x...` form) |
72
+ | Signer | Scheme | Identity resolved by the chain |
73
+ | --------------------- | ------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------- |
74
+ | `PrivateKeySigner` | GalaChain native signature | Recovered from the private key |
75
+ | `GalaWalletSigner` | Native `gala_signChainDto`; opt-in `personal-sign` needs a wallet build with fix 34ca4e4 (unreleased) | Selected account (`0x...`, `eth\|...`, `client\|...`) |
76
+ | `BrowserWalletSigner` | EIP-1193 `personal_sign`; opt-in `{ scheme: 'eip712' }` (`eth_signTypedData_v4`) | Selected Ethereum account (`eip712` also resolves a `client\|` alias) |
73
77
 
74
78
  Native signing is the default for server keys and current Gala Wallets.
75
79
  Browser wallet personal-sign signs the bare serialized DTO (the wallet applies
76
80
  the EIP-191 header) and sends that header as the DTO's `prefix`, which is what
77
- the chain prepends when it recovers the signer. EIP-712 is
78
- not part of the v2 write protocol.
81
+ the chain prepends when it recovers the signer. EIP-712 is opt-in on
82
+ `BrowserWalletSigner` for `RequestTrade`, `AddLiquidity`, `RemoveLiquidity` and
83
+ `CollectPositionFees` (`CreatePool` still uses personal-sign); it is not the
84
+ default and not part of the Gala Wallet path. See
85
+ [Signing and Identity](./docs/docs/other/signing-and-identity.md).
79
86
 
80
87
  ## Wallet and app integration
81
88
 
82
89
  The SDK core is platform neutral (only `fetch`, `globalThis.crypto` and timers) and ships the
83
90
  building blocks a wallet or app needs, each behind an adapter: `confirmAppliedTrade` (apply-results
84
91
  polling with `AbortSignal`, `AppliedTradeError`), `PendingSwapStore` (async `StorageAdapter`;
85
- `storageAdapterFromChromeStorage(chrome.storage.local)` for an extension),
92
+ `storageAdapterFromChromeStorage(chrome.storage.local)` for an extension, `webStorageAdapter(localStorage)`
93
+ for a web app),
86
94
  `DexFees` (DryRun fee quotes), `swaps.swapShadow` (dark swaps through `X-Shadow`), a configurable
87
95
  `uniqueKeyPrefix`, and the realtime stream with an injected `WebSocket` (and an `apiKey` for
88
96
  server-side clients). See [App and Wallet Integration](./docs/docs/other/app-integration.md).
89
97
 
98
+ Errors carry a stable `code` and flat `params` that an app localizes; `classifyFailure`,
99
+ `pendingSwapFateFor`, `isNoMarket` and the other deciders answer retry and outcome questions from the
100
+ code, never from message text ([Error Handling](./docs/docs/other/error-handling.md)). App-shell code
101
+ that must not load the SDK imports its wallet helpers from `@gala-chain/gswap-sdk/wallet`
102
+ ([Bundle size](./docs/docs/other/bundle-size.md)). Tick and price math, including the contract's
103
+ in-range rule, is in [Tick and Price Math](./docs/docs/other/tick-and-price-math.md).
104
+
90
105
  ## Documentation
91
106
 
92
107
  Read the [gSwap SDK documentation](https://galachain.github.io/gswap-sdk/docs/intro)
@@ -1 +1 @@
1
- {"version":3,"file":"dex_fees.js","names":[],"sources":["../../../src/classes/dex_fees.ts"],"sourcesContent":["import type { HttpRequestor } from '../types/http_requestor.js';\nimport type { WireOptions } from '../types/wire.js';\nimport {\n buildRequestHeaders,\n fetchRequestor,\n readResponseBody,\n requestWithTimeout,\n} from '../utils/transport.js';\nimport { signPreSend } from '../utils/pre_send.js';\nimport { GSwapSDKError } from './gswap_sdk_error.js';\nimport type { GalaChainSigner } from './signers.js';\n\n/** DEX methods the backend accepts a fee quote for (its allow-list enumerates the same six). */\nexport const DEX_FEE_METHODS = [\n 'Swap',\n 'RequestTrade',\n 'AddLiquidity',\n 'RemoveLiquidity',\n 'CollectPositionFees',\n 'CreatePool',\n] as const;\nexport type DexFeeMethod = (typeof DEX_FEE_METHODS)[number];\n\n/** Which pool contract the DTO being quoted targets: legacy (V3) or current (GalaChainDex). */\nexport type DexFeeContract = 'dexv3-contract' | 'dex-contract';\n\n/** The chain method name the signed DryRun wrapper is signed under. */\nexport const DRY_RUN_METHOD = 'DryRun';\n\n/** `uniqueKey` prefix of the DryRun WRAPPER (distinct from the inner DTO's key). */\nexport const DRY_RUN_UNIQUE_KEY_PREFIX = 'galaconnect-operation-';\n\n/** True when `method` is a DEX method a fee can be quoted for. */\nexport function isDexFeeMethod(method: unknown): method is DexFeeMethod {\n return typeof method === 'string' && (DEX_FEE_METHODS as readonly string[]).includes(method);\n}\n\n/** Current-contract typed DEX messages use trading symbols instead of TokenClassKey objects. */\nexport function isCurrentContractDexMessage(\n message: Record<string, unknown> | null | undefined,\n method: unknown,\n): boolean {\n return (\n isDexFeeMethod(method) &&\n typeof message?.['token0'] === 'string' &&\n typeof message['token1'] === 'string'\n );\n}\n\n/**\n * The backend's response. It ALWAYS carries a usable `fees[0].feeInGala`: the backend decides what\n * to serve when the chain cannot price the operation, so the client never invents a fee.\n *\n * `source` is informational: `'dry-run'` is simulated on chain and authoritative (possibly a\n * legitimate `'0'`), `'default'` means the chain could not price it and the platform default was\n * served.\n */\nexport interface DexFeeQuote {\n fees: Array<{ feeInGala: string }>;\n source: 'dry-run' | 'default';\n}\n\n/**\n * Reduces an EIP-712 approval `message` to the DTO the chain actually receives: drops the signing\n * envelope (`signature`, `prefix`, `types`, `domain`) and transport metadata (`trace`).\n */\nexport function toChainDto(message: Record<string, unknown>): Record<string, unknown> {\n const chainDto: Record<string, unknown> = { ...message };\n for (const field of ['signature', 'prefix', 'types', 'domain', 'trace']) {\n delete chainDto[field];\n }\n return chainDto;\n}\n\n/**\n * Signs the DryRun WRAPPER the gateway accepts for every contract:\n * `{ callerPublicKey, method, dto: <inner DTO object>, uniqueKey, signature }`. The inner DTO\n * travels unsigned and as an object; no `signerPublicKey` is sent because the signature already\n * recovers it (the gateway rejects it as REDUNDANT_SIGNER_PUBLIC_KEY). The wrapper carries its own\n * `uniqueKey`, distinct from the DTO's.\n */\nexport function buildDryRunWrapper(\n signer: GalaChainSigner,\n callerPublicKey: string,\n method: string,\n dto: Record<string, unknown>,\n uniqueKeyPrefix: string = DRY_RUN_UNIQUE_KEY_PREFIX,\n): Promise<Record<string, unknown>> {\n return signPreSend(signer, DRY_RUN_METHOD, {\n callerPublicKey,\n method,\n dto,\n uniqueKey: `${uniqueKeyPrefix}${globalThis.crypto.randomUUID()}`,\n });\n}\n\n/** The transport and backend a fee quote reads from. */\nexport interface DexFeesClient {\n /** Swap backend origin, e.g. `https://dex-backend-prod1.defi.gala.com`. */\n dexBackendBaseUrl: string;\n /** Defaults to the global `fetch`. */\n httpRequestor?: HttpRequestor | undefined;\n /** Per-request timeout in milliseconds (default 30 000). */\n requestTimeoutMs?: number | undefined;\n /**\n * Request headers of the fee POST (`userAgent`, `headers`); see {@link WireOptions}. The SDK's\n * `Accept: application/json` is a default that `wire.headers` may replace.\n */\n wire?: WireOptions | undefined;\n}\n\n/** Options for a single quote. */\nexport interface DexFeeQuoteOptions {\n /** GalaChain channel. Default `asset`. */\n channel?: string | undefined;\n /**\n * Contract the DTO targets. Default `dexv3-contract` (what every legacy caller quotes against);\n * a current-contract operation must pass `dex-contract` explicitly.\n */\n contract?: DexFeeContract | undefined;\n}\n\n/**\n * Network-fee quote for GalaChain DEX methods, derived from a server-side DryRun.\n *\n * The client only SIGNS the DryRun wrapper (with whatever {@link GalaChainSigner} it is given) and\n * the backend forwards it to the gateway, where the simulated writes carry the fee; the backend\n * never holds a key. Intended for wallets: the DTO quoted is the typed message a dApp handed over.\n *\n * @example\n * ```ts\n * const fees = new DexFees({ dexBackendBaseUrl }, new PrivateKeySigner(sessionKey));\n * const quote = await fees.quote(publicKey, 'RequestTrade', toChainDto(message), {\n * contract: 'dex-contract',\n * });\n * const feeInGala = quote?.fees[0]?.feeInGala;\n * ```\n */\nexport class DexFees {\n private readonly requestor: HttpRequestor;\n private readonly baseUrl: string;\n private readonly requestTimeoutMs: number;\n private readonly wire: WireOptions | undefined;\n\n constructor(\n client: DexFeesClient,\n private readonly signer: GalaChainSigner | undefined,\n ) {\n this.requestor = client.httpRequestor ?? fetchRequestor;\n this.baseUrl = client.dexBackendBaseUrl.replace(/\\/$/u, '');\n this.requestTimeoutMs = client.requestTimeoutMs ?? 30_000;\n this.wire = client.wire;\n }\n\n /**\n * Quotes the network fee for `method` applied to `message`. The message is reduced with\n * {@link toChainDto} first, so an EIP-712 approval message can be passed as is.\n *\n * Resolves `null` when the BACKEND could not be reached or answered unusably (offline, 4xx/5xx,\n * malformed body): a transport outcome, not a fee. Deciding what to charge when the chain is\n * silent is the backend's job (it serves `source: 'default'`), so the client never substitutes\n * a fee of its own, with ONE exception that mirrors the wallet: a `dex-contract` quote answered\n * 403 or 404 means the contract has no fee route yet and resolves `{ source: 'default', '0' }`.\n * Never throws on transport, so a fee lookup cannot block or break a signing screen.\n *\n * @throws GSwapSDKError NO_SIGNER when constructed without a signer; `SIGNING_FAILED` or\n * `KEY_PROVIDER_FAILED` (a pre-send error, the original as `cause`) when the signer fails, for\n * example a locked wallet.\n */\n public async quote(\n callerPublicKey: string,\n method: DexFeeMethod,\n message: Record<string, unknown>,\n options: DexFeeQuoteOptions = {},\n ): Promise<DexFeeQuote | null> {\n if (this.signer === undefined) throw GSwapSDKError.noSignerError();\n const channel = options.channel ?? 'asset';\n const contract = options.contract ?? 'dexv3-contract';\n const wrapper = await buildDryRunWrapper(\n this.signer,\n callerPublicKey,\n method,\n toChainDto(message),\n );\n const url = `${this.baseUrl}/wallet/connect/galachain/api/${encodeURIComponent(channel)}/${encodeURIComponent(contract)}/${encodeURIComponent(method)}/fee`;\n\n let status: number;\n let ok: boolean;\n let body: unknown;\n try {\n const response = await requestWithTimeout(\n this.requestor,\n url,\n {\n method: 'POST',\n headers: buildRequestHeaders(this.wire, {\n json: true,\n defaults: { Accept: 'application/json' },\n }),\n body: JSON.stringify(wrapper),\n },\n this.requestTimeoutMs,\n );\n status = response.status;\n ok = response.ok;\n body = ok ? await readResponseBody(response) : undefined;\n } catch {\n return null;\n }\n\n if (!ok) {\n if (contract === 'dex-contract' && (status === 403 || status === 404)) {\n return { source: 'default', fees: [{ feeInGala: '0' }] };\n }\n return null;\n }\n // Accept whatever fee the backend served: 'default' is as usable as 'dry-run'. Only a body\n // missing the fee outright is unusable.\n return readQuote(body);\n }\n}\n\nfunction readQuote(body: unknown): DexFeeQuote | null {\n if (typeof body !== 'object' || body === null) return null;\n const fees = (body as { fees?: unknown }).fees;\n if (\n !Array.isArray(fees) ||\n typeof (fees[0] as { feeInGala?: unknown } | undefined)?.feeInGala !== 'string'\n ) {\n return null;\n }\n return body as DexFeeQuote;\n}\n"],"mappings":"6TAaA,MAAa,EAAkB,CAC7B,OACA,eACA,eACA,kBACA,sBACA,YACF,EAOa,EAAiB,SAGjB,EAA4B,yBAGzC,SAAgB,EAAe,EAAyC,CACtE,OAAO,OAAO,GAAW,UAAa,EAAsC,SAAS,CAAM,CAC7F,CAGA,SAAgB,EACd,EACA,EACS,CACT,OACE,EAAe,CAAM,GACrB,OAAO,GAAU,QAAc,UAC/B,OAAO,EAAQ,QAAc,QAEjC,CAmBA,SAAgB,EAAW,EAA2D,CACpF,IAAM,EAAoC,CAAE,GAAG,CAAQ,EACvD,IAAK,IAAM,IAAS,CAAC,YAAa,SAAU,QAAS,SAAU,OAAO,EACpE,OAAO,EAAS,GAElB,OAAO,CACT,CASA,SAAgB,EACd,EACA,EACA,EACA,EACA,EAA0B,EACQ,CAClC,OAAO,EAAY,EAAQ,EAAgB,CACzC,kBACA,SACA,MACA,UAAW,GAAG,IAAkB,WAAW,OAAO,WAAW,GAC/D,CAAC,CACH,CA4CA,IAAa,EAAb,KAAqB,CAMnB,YACE,EACA,EACA,CADiB,KAAA,OAAA,EAPF,EAAA,KAAA,YAAA,IAAA,EAAA,EACA,EAAA,KAAA,UAAA,IAAA,EAAA,EACA,EAAA,KAAA,mBAAA,IAAA,EAAA,EACA,EAAA,KAAA,OAAA,IAAA,EAAA,EAMf,KAAK,UAAY,EAAO,eAAiB,EACzC,KAAK,QAAU,EAAO,kBAAkB,QAAQ,OAAQ,EAAE,EAC1D,KAAK,iBAAmB,EAAO,kBAAoB,IACnD,KAAK,KAAO,EAAO,IACrB,CAiBA,MAAa,MACX,EACA,EACA,EACA,EAA8B,CAAC,EACF,CAC7B,GAAI,KAAK,SAAW,IAAA,GAAW,MAAM,EAAc,cAAc,EACjE,IAAM,EAAU,EAAQ,SAAW,QAC7B,EAAW,EAAQ,UAAY,iBAC/B,EAAU,MAAM,EACpB,KAAK,OACL,EACA,EACA,EAAW,CAAO,CACpB,EACM,EAAM,GAAG,KAAK,QAAQ,gCAAgC,mBAAmB,CAAO,EAAE,GAAG,mBAAmB,CAAQ,EAAE,GAAG,mBAAmB,CAAM,EAAE,MAElJ,EACA,EACA,EACJ,GAAI,CACF,IAAM,EAAW,MAAM,EACrB,KAAK,UACL,EACA,CACE,OAAQ,OACR,QAAS,EAAoB,KAAK,KAAM,CACtC,KAAM,GACN,SAAU,CAAE,OAAQ,kBAAmB,CACzC,CAAC,EACD,KAAM,KAAK,UAAU,CAAO,CAC9B,EACA,KAAK,gBACP,EACA,EAAS,EAAS,OAClB,EAAK,EAAS,GACd,EAAO,EAAK,MAAM,EAAiB,CAAQ,EAAI,IAAA,EACjD,MAAQ,CACN,OAAO,IACT,CAUA,OARK,EAQE,EAAU,CAAI,EAPf,IAAa,iBAAmB,IAAW,KAAO,IAAW,KACxD,CAAE,OAAQ,UAAW,KAAM,CAAC,CAAE,UAAW,GAAI,CAAC,CAAE,EAElD,IAKX,CACF,EAEA,SAAS,EAAU,EAAmC,CACpD,GAAI,OAAO,GAAS,WAAY,EAAe,OAAO,KACtD,IAAM,EAAQ,EAA4B,KAO1C,MALE,CAAC,MAAM,QAAQ,CAAI,GACnB,OAAQ,EAAK,EAAE,EAA0C,WAAc,SAEhE,KAEF,CACT"}
1
+ {"version":3,"file":"dex_fees.js","names":[],"sources":["../../../src/classes/dex_fees.ts"],"sourcesContent":["import type { HttpRequestor } from '../types/http_requestor.js';\nimport type { WireOptions } from '../types/wire.js';\nimport {\n buildRequestHeaders,\n fetchRequestor,\n readResponseBody,\n requestWithTimeout,\n} from '../utils/transport.js';\nimport { signPreSend } from '../utils/pre_send.js';\nimport { GSwapSDKError } from './gswap_sdk_error.js';\nimport type { GalaChainSigner } from './signers.js';\n\n/** DEX methods the backend accepts a fee quote for (its allow-list enumerates the same six). */\nexport const DEX_FEE_METHODS = [\n 'Swap',\n 'RequestTrade',\n 'AddLiquidity',\n 'RemoveLiquidity',\n 'CollectPositionFees',\n 'CreatePool',\n] as const;\n/** A chain method whose network fee `DexFees.quote` can price (see `isDexFeeMethod`). */\nexport type DexFeeMethod = (typeof DEX_FEE_METHODS)[number];\n\n/** Which pool contract the DTO being quoted targets: legacy (V3) or current (GalaChainDex). */\nexport type DexFeeContract = 'dexv3-contract' | 'dex-contract';\n\n/** The chain method name the signed DryRun wrapper is signed under. */\nexport const DRY_RUN_METHOD = 'DryRun';\n\n/** `uniqueKey` prefix of the DryRun WRAPPER (distinct from the inner DTO's key). */\nexport const DRY_RUN_UNIQUE_KEY_PREFIX = 'galaconnect-operation-';\n\n/** True when `method` is a DEX method a fee can be quoted for. */\nexport function isDexFeeMethod(method: unknown): method is DexFeeMethod {\n return typeof method === 'string' && (DEX_FEE_METHODS as readonly string[]).includes(method);\n}\n\n/** Current-contract typed DEX messages use trading symbols instead of TokenClassKey objects. */\nexport function isCurrentContractDexMessage(\n message: Record<string, unknown> | null | undefined,\n method: unknown,\n): boolean {\n return (\n isDexFeeMethod(method) &&\n typeof message?.['token0'] === 'string' &&\n typeof message['token1'] === 'string'\n );\n}\n\n/**\n * The backend's response. It ALWAYS carries a usable `fees[0].feeInGala`: the backend decides what\n * to serve when the chain cannot price the operation, so the client never invents a fee.\n *\n * `source` is informational: `'dry-run'` is simulated on chain and authoritative (possibly a\n * legitimate `'0'`), `'default'` means the chain could not price it and the platform default was\n * served.\n */\nexport interface DexFeeQuote {\n fees: Array<{ feeInGala: string }>;\n source: 'dry-run' | 'default';\n}\n\n/**\n * Reduces an EIP-712 approval `message` to the DTO the chain actually receives: drops the signing\n * envelope (`signature`, `prefix`, `types`, `domain`) and transport metadata (`trace`).\n */\nexport function toChainDto(message: Record<string, unknown>): Record<string, unknown> {\n const chainDto: Record<string, unknown> = { ...message };\n for (const field of ['signature', 'prefix', 'types', 'domain', 'trace']) {\n delete chainDto[field];\n }\n return chainDto;\n}\n\n/**\n * Signs the DryRun WRAPPER the gateway accepts for every contract:\n * `{ callerPublicKey, method, dto: <inner DTO object>, uniqueKey, signature }`. The inner DTO\n * travels unsigned and as an object; no `signerPublicKey` is sent because the signature already\n * recovers it (the gateway rejects it as REDUNDANT_SIGNER_PUBLIC_KEY). The wrapper carries its own\n * `uniqueKey`, distinct from the DTO's.\n */\nexport function buildDryRunWrapper(\n signer: GalaChainSigner,\n callerPublicKey: string,\n method: string,\n dto: Record<string, unknown>,\n uniqueKeyPrefix: string = DRY_RUN_UNIQUE_KEY_PREFIX,\n): Promise<Record<string, unknown>> {\n return signPreSend(signer, DRY_RUN_METHOD, {\n callerPublicKey,\n method,\n dto,\n uniqueKey: `${uniqueKeyPrefix}${globalThis.crypto.randomUUID()}`,\n });\n}\n\n/** The transport and backend a fee quote reads from. */\nexport interface DexFeesClient {\n /** Swap backend origin, e.g. `https://dex-backend-prod1.defi.gala.com`. */\n dexBackendBaseUrl: string;\n /** Defaults to the global `fetch`. */\n httpRequestor?: HttpRequestor | undefined;\n /** Per-request timeout in milliseconds (default 30 000). */\n requestTimeoutMs?: number | undefined;\n /**\n * Request headers of the fee POST (`userAgent`, `headers`); see {@link WireOptions}. The SDK's\n * `Accept: application/json` is a default that `wire.headers` may replace.\n */\n wire?: WireOptions | undefined;\n}\n\n/** Options for a single quote. */\nexport interface DexFeeQuoteOptions {\n /** GalaChain channel. Default `asset`. */\n channel?: string | undefined;\n /**\n * Contract the DTO targets. Default `dexv3-contract` (what every legacy caller quotes against);\n * a current-contract operation must pass `dex-contract` explicitly.\n */\n contract?: DexFeeContract | undefined;\n}\n\n/**\n * Network-fee quote for GalaChain DEX methods, derived from a server-side DryRun.\n *\n * The client only SIGNS the DryRun wrapper (with whatever {@link GalaChainSigner} it is given) and\n * the backend forwards it to the gateway, where the simulated writes carry the fee; the backend\n * never holds a key. Intended for wallets: the DTO quoted is the typed message a dApp handed over.\n *\n * @example\n * ```ts\n * const fees = new DexFees({ dexBackendBaseUrl }, new PrivateKeySigner(sessionKey));\n * const quote = await fees.quote(publicKey, 'RequestTrade', toChainDto(message), {\n * contract: 'dex-contract',\n * });\n * const feeInGala = quote?.fees[0]?.feeInGala;\n * ```\n */\nexport class DexFees {\n private readonly requestor: HttpRequestor;\n private readonly baseUrl: string;\n private readonly requestTimeoutMs: number;\n private readonly wire: WireOptions | undefined;\n\n constructor(\n client: DexFeesClient,\n private readonly signer: GalaChainSigner | undefined,\n ) {\n this.requestor = client.httpRequestor ?? fetchRequestor;\n this.baseUrl = client.dexBackendBaseUrl.replace(/\\/$/u, '');\n this.requestTimeoutMs = client.requestTimeoutMs ?? 30_000;\n this.wire = client.wire;\n }\n\n /**\n * Quotes the network fee for `method` applied to `message`. The message is reduced with\n * {@link toChainDto} first, so an EIP-712 approval message can be passed as is.\n *\n * Resolves `null` when the BACKEND could not be reached or answered unusably (offline, 4xx/5xx,\n * malformed body): a transport outcome, not a fee. Deciding what to charge when the chain is\n * silent is the backend's job (it serves `source: 'default'`), so the client never substitutes\n * a fee of its own, with ONE exception that mirrors the wallet: a `dex-contract` quote answered\n * 403 or 404 means the contract has no fee route yet and resolves `{ source: 'default', '0' }`.\n * Never throws on transport, so a fee lookup cannot block or break a signing screen.\n *\n * @throws GSwapSDKError NO_SIGNER when constructed without a signer; `SIGNING_FAILED` or\n * `KEY_PROVIDER_FAILED` (a pre-send error, the original as `cause`) when the signer fails, for\n * example a locked wallet.\n */\n public async quote(\n callerPublicKey: string,\n method: DexFeeMethod,\n message: Record<string, unknown>,\n options: DexFeeQuoteOptions = {},\n ): Promise<DexFeeQuote | null> {\n if (this.signer === undefined) throw GSwapSDKError.noSignerError();\n const channel = options.channel ?? 'asset';\n const contract = options.contract ?? 'dexv3-contract';\n const wrapper = await buildDryRunWrapper(\n this.signer,\n callerPublicKey,\n method,\n toChainDto(message),\n );\n const url = `${this.baseUrl}/wallet/connect/galachain/api/${encodeURIComponent(channel)}/${encodeURIComponent(contract)}/${encodeURIComponent(method)}/fee`;\n\n let status: number;\n let ok: boolean;\n let body: unknown;\n try {\n const response = await requestWithTimeout(\n this.requestor,\n url,\n {\n method: 'POST',\n headers: buildRequestHeaders(this.wire, {\n json: true,\n defaults: { Accept: 'application/json' },\n }),\n body: JSON.stringify(wrapper),\n },\n this.requestTimeoutMs,\n );\n status = response.status;\n ok = response.ok;\n body = ok ? await readResponseBody(response) : undefined;\n } catch {\n return null;\n }\n\n if (!ok) {\n if (contract === 'dex-contract' && (status === 403 || status === 404)) {\n return { source: 'default', fees: [{ feeInGala: '0' }] };\n }\n return null;\n }\n // Accept whatever fee the backend served: 'default' is as usable as 'dry-run'. Only a body\n // missing the fee outright is unusable.\n return readQuote(body);\n }\n}\n\nfunction readQuote(body: unknown): DexFeeQuote | null {\n if (typeof body !== 'object' || body === null) return null;\n const fees = (body as { fees?: unknown }).fees;\n if (\n !Array.isArray(fees) ||\n typeof (fees[0] as { feeInGala?: unknown } | undefined)?.feeInGala !== 'string'\n ) {\n return null;\n }\n return body as DexFeeQuote;\n}\n"],"mappings":"6TAaA,MAAa,EAAkB,CAC7B,OACA,eACA,eACA,kBACA,sBACA,YACF,EAQa,EAAiB,SAGjB,EAA4B,yBAGzC,SAAgB,EAAe,EAAyC,CACtE,OAAO,OAAO,GAAW,UAAa,EAAsC,SAAS,CAAM,CAC7F,CAGA,SAAgB,EACd,EACA,EACS,CACT,OACE,EAAe,CAAM,GACrB,OAAO,GAAU,QAAc,UAC/B,OAAO,EAAQ,QAAc,QAEjC,CAmBA,SAAgB,EAAW,EAA2D,CACpF,IAAM,EAAoC,CAAE,GAAG,CAAQ,EACvD,IAAK,IAAM,IAAS,CAAC,YAAa,SAAU,QAAS,SAAU,OAAO,EACpE,OAAO,EAAS,GAElB,OAAO,CACT,CASA,SAAgB,EACd,EACA,EACA,EACA,EACA,EAA0B,EACQ,CAClC,OAAO,EAAY,EAAQ,EAAgB,CACzC,kBACA,SACA,MACA,UAAW,GAAG,IAAkB,WAAW,OAAO,WAAW,GAC/D,CAAC,CACH,CA4CA,IAAa,EAAb,KAAqB,CAMnB,YACE,EACA,EACA,CADiB,KAAA,OAAA,EAPF,EAAA,KAAA,YAAA,IAAA,EAAA,EACA,EAAA,KAAA,UAAA,IAAA,EAAA,EACA,EAAA,KAAA,mBAAA,IAAA,EAAA,EACA,EAAA,KAAA,OAAA,IAAA,EAAA,EAMf,KAAK,UAAY,EAAO,eAAiB,EACzC,KAAK,QAAU,EAAO,kBAAkB,QAAQ,OAAQ,EAAE,EAC1D,KAAK,iBAAmB,EAAO,kBAAoB,IACnD,KAAK,KAAO,EAAO,IACrB,CAiBA,MAAa,MACX,EACA,EACA,EACA,EAA8B,CAAC,EACF,CAC7B,GAAI,KAAK,SAAW,IAAA,GAAW,MAAM,EAAc,cAAc,EACjE,IAAM,EAAU,EAAQ,SAAW,QAC7B,EAAW,EAAQ,UAAY,iBAC/B,EAAU,MAAM,EACpB,KAAK,OACL,EACA,EACA,EAAW,CAAO,CACpB,EACM,EAAM,GAAG,KAAK,QAAQ,gCAAgC,mBAAmB,CAAO,EAAE,GAAG,mBAAmB,CAAQ,EAAE,GAAG,mBAAmB,CAAM,EAAE,MAElJ,EACA,EACA,EACJ,GAAI,CACF,IAAM,EAAW,MAAM,EACrB,KAAK,UACL,EACA,CACE,OAAQ,OACR,QAAS,EAAoB,KAAK,KAAM,CACtC,KAAM,GACN,SAAU,CAAE,OAAQ,kBAAmB,CACzC,CAAC,EACD,KAAM,KAAK,UAAU,CAAO,CAC9B,EACA,KAAK,gBACP,EACA,EAAS,EAAS,OAClB,EAAK,EAAS,GACd,EAAO,EAAK,MAAM,EAAiB,CAAQ,EAAI,IAAA,EACjD,MAAQ,CACN,OAAO,IACT,CAUA,OARK,EAQE,EAAU,CAAI,EAPf,IAAa,iBAAmB,IAAW,KAAO,IAAW,KACxD,CAAE,OAAQ,UAAW,KAAM,CAAC,CAAE,UAAW,GAAI,CAAC,CAAE,EAElD,IAKX,CACF,EAEA,SAAS,EAAU,EAAmC,CACpD,GAAI,OAAO,GAAS,WAAY,EAAe,OAAO,KACtD,IAAM,EAAQ,EAA4B,KAO1C,MALE,CAAC,MAAM,QAAQ,CAAI,GACnB,OAAQ,EAAK,EAAE,EAA0C,WAAc,SAEhE,KAEF,CACT"}
@@ -1 +1 @@
1
- {"version":3,"file":"gateway.js","names":[],"sources":["../../../src/classes/gateway.ts"],"sourcesContent":["import type { HttpRequestor, HTTPResponse } from '../types/http_requestor.js';\nimport type { WireOptions } from '../types/wire.js';\nimport {\n getObjectProperty,\n getStringProperty,\n GSwapSDKError,\n isGSwapError,\n UPDATE_REQUIRED_STATUS,\n type GSwapErrorContext,\n} from './gswap_sdk_error.js';\nimport { serverMessageOfBody } from '../utils/server_message.js';\nimport { shadowFailures } from '../utils/shadow_errors.js';\nimport type { SubmissionOutcomeUnknownReason } from './error_catalog.js';\nimport type { ShadowSubmitResult } from './shadow_submit.js';\nimport { SubmittedTransaction, type PositionConfirmationResult } from './submitted_transaction.js';\nimport {\n buildRequestHeaders,\n fetchRequestor,\n readBlockNumberHeader,\n readHeader,\n readResponseBody,\n readRetryAfter,\n requestWithTimeout,\n validateTimeoutMs,\n} from '../utils/transport.js';\nimport type { AppliedTrade, Position } from '../types/v2_results.js';\n\n/** Options for a chain-gateway client. */\nexport interface ChainGatewayOptions {\n dexBackendBaseUrl: string;\n httpRequestor?: HttpRequestor | undefined;\n walletAddress?: string | undefined;\n /**\n * Timeout of every gateway request in milliseconds (default 30 000); also the per-read timeout\n * of the services built on this gateway and of `confirm()`. `Infinity` arms no timer, for an\n * app whose HTTP stack owns request lifetimes.\n */\n chainCallTimeoutMs?: number | undefined;\n /**\n * Wire format of every request of this gateway and of the services built on it: the headers\n * (`userAgent`, `headers`) of reads AND of the POSTs that submit writes, and the query encoding\n * of reads. `X-Wallet-Address` and `X-Shadow` always win over a same-named `wire.headers` entry.\n */\n wire?: WireOptions | undefined;\n /**\n * Tells the gateway which rejections of YOUR `httpRequestor` mean \"no HTTP answer arrived\" (the\n * connection failed, was reset or dropped), as opposed to an answer your requestor turned into\n * an error (an interceptor that rejects on a 4xx, a version wall). Used only when submitting a\n * write: a rejection it accepts becomes `SUBMISSION_OUTCOME_UNKNOWN` with `reason: 'transport'`\n * (the request may have been queued; the original error is `cause`), and any other rejection\n * reaches the caller unchanged. The SDK's own `TRANSPORT_ERROR` (the default `fetch` requestor,\n * `httpRequestorFromAxios`) always counts, so this is only for requestors that reject with\n * their own errors. Never throw from it; a throw is treated as \"not a transport failure\".\n *\n * @example\n * ```ts\n * new ChainGateway({\n * dexBackendBaseUrl,\n * httpRequestor: galaFetch, // a fetch wrapper: its TypeError is the network failing\n * isTransportFailure: (error) => error instanceof TypeError,\n * });\n * ```\n */\n isTransportFailure?: ((error: unknown) => boolean) | undefined;\n /**\n * Tells the gateway which rejections of YOUR `httpRequestor` are an HTTP ANSWER, and what the\n * answer was: a requestor built on an HTTP client that rejects every non-2xx (an interceptor\n * that throws on a 4xx, a version wall that throws on a 426) loses the answer the SDK reads\n * errors from. Return `{ status, body }` (the parsed JSON, or the raw text; `headers` for\n * `Retry-After`) for such a rejection and the write is handled exactly as if the requestor had\n * resolved that answer: the gateway's own code becomes its catalogued error\n * (`DTO_INVALID`, `RATE_LIMITED`, `CHAIN_DISPATCH_FAILED`, ...), an uncoded body `HTTP_ERROR`,\n * both carrying the signed `uniqueKey` and classified by `classifyFailure` as a write. Return\n * `undefined` for any other rejection: it reaches the caller unchanged. Used only when\n * submitting a write; `isTransportFailure` is asked first (no answer proves nothing, so\n * \"maybe queued\" wins), and a throw from this hook, or a `status` outside 100-599, means \"not an\n * answer\". Never throw from it.\n *\n * @example\n * ```ts\n * new ChainGateway({\n * dexBackendBaseUrl,\n * httpRequestor: httpRequestorFromAxios(instance, { rejectOnHttpError: true }),\n * answerOfRejection: (error) =>\n * error instanceof GatewayError && error.status !== undefined\n * ? { status: error.status, body: error.data }\n * : undefined,\n * });\n * ```\n */\n answerOfRejection?: ((error: unknown) => RejectedAnswer | undefined) | undefined;\n}\n\n/** The HTTP answer a rejection of the app's requestor stands for (see `ChainGatewayOptions.answerOfRejection`). */\nexport interface RejectedAnswer {\n /** The HTTP status of the answer (100-599). */\n status: number;\n /** The answer's body: parsed JSON when it was JSON, else the raw text. */\n body: unknown;\n /** The answer's headers, for `Retry-After`. */\n headers?: Record<string, string | undefined> | undefined;\n}\n\n/** Optional attribution and operation-specific confirmation hooks for a write. */\nexport interface ChainSubmitOptions {\n walletAddress?: string | undefined;\n positionConfirmation?: ((signal: AbortSignal) => Promise<PositionConfirmationResult>) | undefined;\n}\n\n/** Thin client for backend-routed v2 writes: synchronous liquidity writes and queued RequestTrade. */\nexport class ChainGateway {\n private readonly requestor: HttpRequestor;\n private readonly walletAddress: string | undefined;\n private readonly chainCallTimeoutMs: number;\n private readonly isTransportFailure: ((error: unknown) => boolean) | undefined;\n private readonly answerOfRejectionHook:\n ((error: unknown) => RejectedAnswer | undefined) | undefined;\n public readonly requestTimeoutMs: number;\n public readonly dexBackendBaseUrl: string;\n /** Wire format shared by the read services built on this gateway. */\n public readonly wire: WireOptions;\n\n /** Create a chain gateway client. */\n constructor(options: ChainGatewayOptions) {\n this.dexBackendBaseUrl = trimTrailingSlash(options.dexBackendBaseUrl);\n this.requestor = options.httpRequestor ?? fetchRequestor;\n this.walletAddress = options.walletAddress;\n this.chainCallTimeoutMs = validateTimeoutMs(\n options.chainCallTimeoutMs ?? 30_000,\n 'chainCallTimeoutMs',\n );\n this.requestTimeoutMs = this.chainCallTimeoutMs;\n this.wire = options.wire ?? {};\n this.isTransportFailure = options.isTransportFailure;\n this.answerOfRejectionHook = options.answerOfRejection;\n }\n\n /**\n * Submit a signed v2 DTO through the chain gateway.\n *\n * @throws GSwapSDKError `SUBMISSION_OUTCOME_UNKNOWN` when no answer was read: `params.reason` is\n * `'timeout'` (the POST exceeded `chainCallTimeoutMs`) or `'transport'` (the connection failed\n * or broke off). Either way the write MAY have been queued: keep `error.uniqueKey` and confirm\n * it. Whether to offer the user a Retry (a NEW request under a fresh key) is the app's policy;\n * the SDK states only that the outcome is unknown. Reads keep `TRANSPORT_ERROR`.\n */\n public async submit(\n method: 'RequestTrade',\n signedBody: Record<string, unknown>,\n options?: ChainSubmitOptions,\n ): Promise<SubmittedTransaction<AppliedTrade>>;\n public async submit(\n method: string,\n signedBody: Record<string, unknown>,\n options?: ChainSubmitOptions,\n ): Promise<SubmittedTransaction<Position | null>>;\n public async submit(\n method: string,\n signedBody: Record<string, unknown>,\n options?: ChainSubmitOptions,\n ): Promise<SubmittedTransaction> {\n // The synchronous swap was removed from the contract and from this SDK. This is the only\n // place the retired name may appear in the write path; the class guard in\n // tests/foundation.test.ts pins that nothing signs or submits it.\n if (method === 'Trade') {\n throw new GSwapSDKError('METHOD_NOT_SUPPORTED', { method });\n }\n const url = this.writeUrl(method);\n const walletAddress = options?.walletAddress ?? this.walletAddress;\n const headers = buildRequestHeaders(this.wire, {\n json: true,\n fixed: walletAddress === undefined ? {} : { 'X-Wallet-Address': walletAddress },\n });\n const { response, body } = await this.post(method, url, headers, signedBody);\n // Every failure after an answer names the write it is about, so an app can confirm it and a\n // failure classifier can tell a write's HTTP failure from a read's.\n const uniqueKey = getStringProperty(signedBody, 'uniqueKey') ?? '';\n const context = responseContext(response, body, url, uniqueKey);\n\n if (!response.ok) {\n throw gatewayError(response, body, url, uniqueKey);\n }\n\n const envelope = asRecord(body);\n // `error: true` is the gateway refusing the write, even inside a 2xx. It is never a transaction.\n if (envelope?.['error'] === true) {\n throw new GSwapSDKError(\n 'GATEWAY_REPORTED_ERROR',\n { serverMessage: serverMessageOfBody(envelope) },\n context,\n );\n }\n const data = asRecord(envelope?.['data'] ?? envelope?.['Data']);\n if (data === undefined) {\n throw new GSwapSDKError('GATEWAY_DATA_MISSING', {}, context);\n }\n\n const transactionId = getStringProperty(data, 'transactionId');\n const mode = getStringProperty(data, 'mode');\n const rawTransactionId = data['transactionId'];\n const bodyBlockNumber = data['blockNumber'];\n const invalidMode = (): GSwapSDKError<'GATEWAY_MODE_INVALID'> =>\n new GSwapSDKError('GATEWAY_MODE_INVALID', {}, context);\n if (mode !== 'sync' && mode !== 'queued') {\n throw invalidMode();\n }\n if (\n ('transactionId' in data && typeof rawTransactionId !== 'string') ||\n (bodyBlockNumber !== undefined && bodyBlockNumber !== null && !isBlockNumber(bodyBlockNumber))\n ) {\n throw invalidMode();\n }\n if (method === 'RequestTrade' && mode === 'sync') {\n throw new GSwapSDKError('GATEWAY_SYNC_REQUEST_TRADE', {}, context);\n }\n if (method === 'RequestTrade' && mode === 'queued') {\n assertQueuedUnderSignedKey(response, body, url, data, signedBody);\n } else if (mode === 'queued' || !('result' in data)) {\n throw invalidMode();\n }\n\n // Only synchronous writes carry an executed chain result in the gateway response.\n if (mode === 'sync') {\n const embeddedResult = asRecord(data['result']);\n if (embeddedResult !== undefined) {\n const resultStatus = embeddedResult['Status'];\n if (resultStatus !== undefined && resultStatus !== 1) {\n throw GSwapSDKError.fromChainError(\n getStringProperty(embeddedResult, 'ErrorKey') ?? 'CHAIN_ERROR',\n getStringProperty(embeddedResult, 'Message') ?? '',\n context,\n );\n }\n }\n }\n\n const headerTransactionId = readHeader(response, 'x-transaction-id');\n const resolvedTransactionId =\n transactionId === undefined || transactionId === '' ? headerTransactionId : transactionId;\n const blockNumber: number | undefined =\n bodyBlockNumber === null || bodyBlockNumber === undefined\n ? readBlockNumberHeader(response)\n : isBlockNumber(bodyBlockNumber)\n ? bodyBlockNumber\n : undefined;\n return new SubmittedTransaction({\n method,\n uniqueKey,\n transactionId:\n resolvedTransactionId === undefined || resolvedTransactionId === ''\n ? null\n : resolvedTransactionId,\n blockNumber: blockNumber ?? null,\n mode,\n result: mode === 'queued' ? undefined : data['result'],\n envelope: body,\n dexBackendBaseUrl: this.dexBackendBaseUrl,\n httpRequestor: this.requestor,\n requestTimeoutMs: this.chainCallTimeoutMs,\n wire: this.wire,\n ...(options?.positionConfirmation === undefined\n ? {}\n : { positionConfirmation: options.positionConfirmation }),\n });\n }\n\n /**\n * Submit a signed v2 DTO as a SHADOW (dark) write: the same route and body as {@link submit},\n * plus `X-Wallet-Address` and `X-Shadow: true`, so defi-backend marks the transaction private\n * before it reaches the contract. The wallet address is required (the backend resolves the\n * owner from it).\n *\n * Unlike `submit`, EVERY HTTP status resolves: the result is the raw `{ httpStatus, body }`,\n * because shadow failures are classified from the status together with the body's `code`\n * (`SHADOW_UNAVAILABLE`, `SHADOW_DISABLED`, `SHADOW_UNSUPPORTED_TYPE`, `SHADOW_RELAY_REJECTED`);\n * see `readShadowSubmitOutcome`. Only a failure to get an answer rejects, and it rejects with\n * `SUBMISSION_OUTCOME_UNKNOWN` (`params.reason` `'timeout'` or `'transport'`): the request may\n * have been accepted. Every error it throws has `error.shadow === true`, and a non-SDK object\n * that escapes (your requestor's own rejection) answers `isShadowError(error)`.\n *\n * @example\n * ```ts\n * const result = await gateway.submitShadow('RequestTrade', signedDto, { walletAddress });\n * const outcome = readShadowSubmitOutcome('RequestTrade', signedDto, result);\n * ```\n */\n public async submitShadow(\n method: string,\n signedBody: Record<string, unknown>,\n options?: ChainSubmitOptions,\n ): Promise<ShadowSubmitResult> {\n return shadowFailures(() => this.sendShadow(method, signedBody, options));\n }\n\n private async sendShadow(\n method: string,\n signedBody: Record<string, unknown>,\n options?: ChainSubmitOptions,\n ): Promise<ShadowSubmitResult> {\n if (method === 'Trade') {\n throw new GSwapSDKError('METHOD_NOT_SUPPORTED', { method });\n }\n const walletAddress = options?.walletAddress ?? this.walletAddress;\n if (walletAddress === undefined) {\n throw new GSwapSDKError('SHADOW_SUBMIT_NEEDS_WALLET_ADDRESS', {});\n }\n // Header order is frontend_v2's: X-Wallet-Address, then X-Shadow.\n const headers = buildRequestHeaders(this.wire, {\n json: true,\n fixed: { 'X-Wallet-Address': walletAddress, 'X-Shadow': 'true' },\n });\n const { response, body } = await this.post(method, this.writeUrl(method), headers, signedBody);\n return { httpStatus: response.status, body };\n }\n\n private writeUrl(method: string): string {\n return `${this.dexBackendBaseUrl}/v1/chain/asset/dex-contract/${method}`;\n }\n\n /**\n * POSTs a signed body and reads the answer. Neither a timeout nor a transport failure proves the\n * write was not submitted, so both are `SUBMISSION_OUTCOME_UNKNOWN` (`params.reason` says which),\n * never a retryable read failure. A rejection that is an answer (see `isTransportFailure`) is\n * rethrown unchanged.\n */\n private async post(\n method: string,\n url: string,\n headers: Record<string, string>,\n signedBody: Record<string, unknown>,\n ): Promise<{ response: HTTPResponse; body: unknown }> {\n // Serialized BEFORE the request is attempted and outside the try below: a body that cannot be\n // serialized (a cycle, a BigInt) was never sent, so it must not be mistaken for a transport\n // failure the app's `isTransportFailure` might claim, which would call it \"maybe queued\".\n let payload: string;\n try {\n payload = JSON.stringify(signedBody);\n } catch (cause: unknown) {\n throw new GSwapSDKError('SIGNING_FAILED', { method, reason: 'failed' }, { cause });\n }\n const outcomeUnknown = (\n reason: SubmissionOutcomeUnknownReason,\n cause: unknown,\n ): GSwapSDKError<'SUBMISSION_OUTCOME_UNKNOWN'> =>\n new GSwapSDKError(\n 'SUBMISSION_OUTCOME_UNKNOWN',\n { reason },\n { uniqueKey: getStringProperty(signedBody, 'uniqueKey') ?? '', url, cause },\n );\n let response: HTTPResponse;\n try {\n response = await requestWithTimeout(\n this.requestor,\n url,\n {\n method: 'POST',\n headers,\n body: payload,\n },\n this.chainCallTimeoutMs,\n );\n } catch (error: unknown) {\n if (isGSwapError(error, 'REQUEST_TIMEOUT')) throw outcomeUnknown('timeout', error);\n if (isGSwapError(error, 'TRANSPORT_ERROR') || this.isTransportRejection(error)) {\n throw outcomeUnknown('transport', error);\n }\n // A rejection that IS an HTTP answer (the app's interceptor rejecting on a non-2xx, a\n // version wall) is handled exactly as the answer would have been, so the app does not\n // rebuild the SDK's error from it. Anything else the app's requestor threw reaches the\n // caller unchanged.\n const answered = this.answerOfRejection(error);\n if (answered !== undefined) return answered;\n throw error;\n }\n try {\n return { response, body: await readResponseBody(response) };\n } catch (error: unknown) {\n // The answer's headers arrived but its body broke off: the status alone does not say what\n // the gateway did with the write.\n throw outcomeUnknown('transport', error);\n }\n }\n\n /**\n * The HTTP answer a rejection of the app's requestor stands for, as the response the gateway\n * would have read. `undefined` when the app named none, when its hook throws, or when what it\n * returned is not an HTTP status; then the rejection is not an answer.\n */\n private answerOfRejection(error: unknown): { response: HTTPResponse; body: unknown } | undefined {\n let answer: RejectedAnswer | undefined;\n try {\n answer = this.answerOfRejectionHook?.(error);\n } catch {\n return undefined;\n }\n if (answer === undefined) return undefined;\n const { status, body, headers } = answer;\n if (!Number.isInteger(status) || status < 100 || status > 599) return undefined;\n const text = typeof body === 'string' ? body : '';\n const response: HTTPResponse = {\n ok: status >= 200 && status < 300,\n status,\n json: async () => body,\n text: async () => text,\n headers: headers ?? {},\n };\n return { response, body };\n }\n\n private isTransportRejection(error: unknown): boolean {\n try {\n return this.isTransportFailure?.(error) === true;\n } catch {\n return false;\n }\n }\n\n /** Expose the requestor for sibling read services that share this gateway transport. */\n public get httpRequestor(): HttpRequestor {\n return this.requestor;\n }\n}\n\nfunction trimTrailingSlash(value: string): string {\n return value.replace(/\\/$/u, '');\n}\n\nfunction responseContext(\n response: HTTPResponse,\n body: unknown,\n url: string,\n uniqueKey?: string,\n): GSwapErrorContext {\n return {\n status: response.status,\n body,\n url,\n ...(uniqueKey === undefined || uniqueKey === '' ? {} : { uniqueKey }),\n };\n}\n\n/**\n * A queued RequestTrade acknowledgement is a swap only if it names the exact key that was signed:\n * the key is the only handle `confirm()` and every recovery path have on the trade, so an\n * acknowledgement without it (or with another) proves nothing about THIS request. Fails closed:\n * an absent echo is as unacceptable as a wrong one. The code is `MISSING_UNIQUE_KEY` or\n * `UNIQUE_KEY_MISMATCH`, `error.uniqueKey` the signed key.\n *\n * Only RequestTrade is checked: a synchronous write is proven by its own executed chain result,\n * and the apps never required an echo for it.\n */\nfunction assertQueuedUnderSignedKey(\n response: HTTPResponse,\n body: unknown,\n url: string,\n data: Record<string, unknown>,\n signedBody: Record<string, unknown>,\n): void {\n const signedUniqueKey = getStringProperty(signedBody, 'uniqueKey') ?? '';\n const echoed = data['uniqueKey'];\n if (typeof echoed === 'string' && echoed !== '' && echoed === signedUniqueKey) return;\n const missing = echoed === undefined || echoed === null || echoed === '';\n const context: GSwapErrorContext = {\n ...responseContext(response, body, url),\n uniqueKey: signedUniqueKey,\n };\n throw missing\n ? new GSwapSDKError('MISSING_UNIQUE_KEY', {}, context)\n : new GSwapSDKError('UNIQUE_KEY_MISMATCH', {}, context);\n}\n\nfunction gatewayError(\n response: HTTPResponse,\n body: unknown,\n url: string,\n uniqueKey: string,\n): GSwapSDKError {\n // The version wall is decided by the status alone, whatever the body names: nothing was queued.\n if (response.status === UPDATE_REQUIRED_STATUS) {\n return new GSwapSDKError(\n 'UPDATE_REQUIRED',\n { status: response.status },\n responseContext(response, body, url, uniqueKey),\n );\n }\n const object = asRecord(body);\n const nested = getObjectProperty(body, 'error');\n const code =\n getStringProperty(object, 'code') ??\n getStringProperty(nested, 'code') ??\n getStringProperty(nested, 'ErrorKey') ??\n 'HTTP_ERROR';\n // Missing server text is an empty param, never SDK-written English standing in for it. A body\n // that is not JSON (an HTML 502 page, a WAF block) is kept as error.bodyText.\n const message = serverMessageOfBody(body);\n const retryAfterMs = response.status === 429 ? readRetryAfter(response) : undefined;\n if (code === 'HTTP_ERROR') {\n return new GSwapSDKError(\n 'HTTP_ERROR',\n { status: response.status, url, serverMessage: message },\n responseContext(response, body, url, uniqueKey),\n );\n }\n return GSwapSDKError.fromGatewayBounce(\n code,\n message,\n response.status,\n body,\n url,\n retryAfterMs,\n uniqueKey === '' ? undefined : uniqueKey,\n );\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n return typeof value === 'object' && value !== null\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\nfunction isBlockNumber(value: unknown): value is number {\n return typeof value === 'number' && Number.isInteger(value) && value >= 0;\n}\n"],"mappings":"8lBA8GA,IAAa,EAAb,KAA0B,CAaxB,YAAY,EAA8B,CAZzB,EAAA,KAAA,YAAA,IAAA,EAAA,EACA,EAAA,KAAA,gBAAA,IAAA,EAAA,EACA,EAAA,KAAA,qBAAA,IAAA,EAAA,EACA,EAAA,KAAA,qBAAA,IAAA,EAAA,EACA,EAAA,KAAA,wBAAA,IAAA,EAAA,EAED,EAAA,KAAA,mBAAA,IAAA,EAAA,EACA,EAAA,KAAA,oBAAA,IAAA,EAAA,EAEA,EAAA,KAAA,OAAA,IAAA,EAAA,EAId,KAAK,kBAAoB,EAAkB,EAAQ,iBAAiB,EACpE,KAAK,UAAY,EAAQ,eAAiB,EAC1C,KAAK,cAAgB,EAAQ,cAC7B,KAAK,mBAAqB,EACxB,EAAQ,oBAAsB,IAC9B,oBACF,EACA,KAAK,iBAAmB,KAAK,mBAC7B,KAAK,KAAO,EAAQ,MAAQ,CAAC,EAC7B,KAAK,mBAAqB,EAAQ,mBAClC,KAAK,sBAAwB,EAAQ,iBACvC,CAqBA,MAAa,OACX,EACA,EACA,EAC+B,CAI/B,GAAI,IAAW,QACb,MAAM,IAAI,EAAc,uBAAwB,CAAE,QAAO,CAAC,EAE5D,IAAM,EAAM,KAAK,SAAS,CAAM,EAC1B,EAAgB,GAAS,eAAiB,KAAK,cAC/C,EAAU,EAAoB,KAAK,KAAM,CAC7C,KAAM,GACN,MAAO,IAAkB,IAAA,GAAY,CAAC,EAAI,CAAE,mBAAoB,CAAc,CAChF,CAAC,EACK,CAAE,WAAU,QAAS,MAAM,KAAK,KAAK,EAAQ,EAAK,EAAS,CAAU,EAGrE,EAAY,EAAkB,EAAY,WAAW,GAAK,GAC1D,EAAU,EAAgB,EAAU,EAAM,EAAK,CAAS,EAE9D,GAAI,CAAC,EAAS,GACZ,MAAM,EAAa,EAAU,EAAM,EAAK,CAAS,EAGnD,IAAM,EAAW,EAAS,CAAI,EAE9B,GAAI,GAAW,QAAa,GAC1B,MAAM,IAAI,EACR,yBACA,CAAE,cAAe,EAAoB,CAAQ,CAAE,EAC/C,CACF,EAEF,IAAM,EAAO,EAAS,GAAW,MAAW,GAAW,IAAO,EAC9D,GAAI,IAAS,IAAA,GACX,MAAM,IAAI,EAAc,uBAAwB,CAAC,EAAG,CAAO,EAG7D,IAAM,EAAgB,EAAkB,EAAM,eAAe,EACvD,EAAO,EAAkB,EAAM,MAAM,EACrC,EAAmB,EAAK,cACxB,EAAkB,EAAK,YACvB,MACJ,IAAI,EAAc,uBAAwB,CAAC,EAAG,CAAO,EAIvD,GAHI,IAAS,QAAU,IAAS,UAI7B,kBAAmB,GAAQ,OAAO,GAAqB,UACvD,GAAqD,MAAQ,CAAC,EAAc,CAAe,EAE5F,MAAM,EAAY,EAEpB,GAAI,IAAW,gBAAkB,IAAS,OACxC,MAAM,IAAI,EAAc,6BAA8B,CAAC,EAAG,CAAO,EAEnE,GAAI,IAAW,gBAAkB,IAAS,SACxC,EAA2B,EAAU,EAAM,EAAK,EAAM,CAAU,OAC3D,GAAI,IAAS,UAAY,EAAE,WAAY,GAC5C,MAAM,EAAY,EAIpB,GAAI,IAAS,OAAQ,CACnB,IAAM,EAAiB,EAAS,EAAK,MAAS,EAC9C,GAAI,IAAmB,IAAA,GAAW,CAChC,IAAM,EAAe,EAAe,OACpC,GAAI,IAAiB,IAAA,IAAa,IAAiB,EACjD,MAAM,EAAc,eAClB,EAAkB,EAAgB,UAAU,GAAK,cACjD,EAAkB,EAAgB,SAAS,GAAK,GAChD,CACF,CAEJ,CACF,CAEA,IAAM,EAAsB,EAAW,EAAU,kBAAkB,EAC7D,EACJ,IAAkB,IAAA,IAAa,IAAkB,GAAK,EAAsB,EACxE,EACJ,GAAoB,KAChB,EAAsB,CAAQ,EAC9B,EAAc,CAAe,EAC3B,EACA,IAAA,GACR,OAAO,IAAI,EAAqB,CAC9B,SACA,YACA,cACE,IAA0B,IAAA,IAAa,IAA0B,GAC7D,KACA,EACN,YAAa,GAAe,KAC5B,OACA,OAAQ,IAAS,SAAW,IAAA,GAAY,EAAK,OAC7C,SAAU,EACV,kBAAmB,KAAK,kBACxB,cAAe,KAAK,UACpB,iBAAkB,KAAK,mBACvB,KAAM,KAAK,KACX,GAAI,GAAS,uBAAyB,IAAA,GAClC,CAAC,EACD,CAAE,qBAAsB,EAAQ,oBAAqB,CAC3D,CAAC,CACH,CAsBA,MAAa,aACX,EACA,EACA,EAC6B,CAC7B,OAAO,MAAqB,KAAK,WAAW,EAAQ,EAAY,CAAO,CAAC,CAC1E,CAEA,MAAc,WACZ,EACA,EACA,EAC6B,CAC7B,GAAI,IAAW,QACb,MAAM,IAAI,EAAc,uBAAwB,CAAE,QAAO,CAAC,EAE5D,IAAM,EAAgB,GAAS,eAAiB,KAAK,cACrD,GAAI,IAAkB,IAAA,GACpB,MAAM,IAAI,EAAc,qCAAsC,CAAC,CAAC,EAGlE,IAAM,EAAU,EAAoB,KAAK,KAAM,CAC7C,KAAM,GACN,MAAO,CAAE,mBAAoB,EAAe,WAAY,MAAO,CACjE,CAAC,EACK,CAAE,WAAU,QAAS,MAAM,KAAK,KAAK,EAAQ,KAAK,SAAS,CAAM,EAAG,EAAS,CAAU,EAC7F,MAAO,CAAE,WAAY,EAAS,OAAQ,MAAK,CAC7C,CAEA,SAAiB,EAAwB,CACvC,MAAO,GAAG,KAAK,kBAAkB,+BAA+B,GAClE,CAQA,MAAc,KACZ,EACA,EACA,EACA,EACoD,CAIpD,IAAI,EACJ,GAAI,CACF,EAAU,KAAK,UAAU,CAAU,CACrC,OAAS,EAAgB,CACvB,MAAM,IAAI,EAAc,iBAAkB,CAAE,SAAQ,OAAQ,QAAS,EAAG,CAAE,OAAM,CAAC,CACnF,CACA,IAAM,GACJ,EACA,IAEA,IAAI,EACF,6BACA,CAAE,QAAO,EACT,CAAE,UAAW,EAAkB,EAAY,WAAW,GAAK,GAAI,MAAK,OAAM,CAC5E,EACE,EACJ,GAAI,CACF,EAAW,MAAM,EACf,KAAK,UACL,EACA,CACE,OAAQ,OACR,UACA,KAAM,CACR,EACA,KAAK,kBACP,CACF,OAAS,EAAgB,CACvB,GAAI,EAAa,EAAO,iBAAiB,EAAG,MAAM,EAAe,UAAW,CAAK,EACjF,GAAI,EAAa,EAAO,iBAAiB,GAAK,KAAK,qBAAqB,CAAK,EAC3E,MAAM,EAAe,YAAa,CAAK,EAMzC,IAAM,EAAW,KAAK,kBAAkB,CAAK,EAC7C,GAAI,IAAa,IAAA,GAAW,OAAO,EACnC,MAAM,CACR,CACA,GAAI,CACF,MAAO,CAAE,WAAU,KAAM,MAAM,EAAiB,CAAQ,CAAE,CAC5D,OAAS,EAAgB,CAGvB,MAAM,EAAe,YAAa,CAAK,CACzC,CACF,CAOA,kBAA0B,EAAuE,CAC/F,IAAI,EACJ,GAAI,CACF,EAAS,KAAK,wBAAwB,CAAK,CAC7C,MAAQ,CACN,MACF,CACA,GAAI,IAAW,IAAA,GAAW,OAC1B,GAAM,CAAE,SAAQ,OAAM,WAAY,EAClC,GAAI,CAAC,OAAO,UAAU,CAAM,GAAK,EAAS,KAAO,EAAS,IAAK,OAC/D,IAAM,EAAO,OAAO,GAAS,SAAW,EAAO,GAQ/C,MAAO,CAAE,SAAA,CANP,GAAI,GAAU,KAAO,EAAS,IAC9B,SACA,KAAM,SAAY,EAClB,KAAM,SAAY,EAClB,QAAS,GAAW,CAAC,CAEP,EAAG,MAAK,CAC1B,CAEA,qBAA6B,EAAyB,CACpD,GAAI,CACF,OAAO,KAAK,qBAAqB,CAAK,IAAM,EAC9C,MAAQ,CACN,MAAO,EACT,CACF,CAGA,IAAW,eAA+B,CACxC,OAAO,KAAK,SACd,CACF,EAEA,SAAS,EAAkB,EAAuB,CAChD,OAAO,EAAM,QAAQ,OAAQ,EAAE,CACjC,CAEA,SAAS,EACP,EACA,EACA,EACA,EACmB,CACnB,MAAO,CACL,OAAQ,EAAS,OACjB,OACA,MACA,GAAI,IAAc,IAAA,IAAa,IAAc,GAAK,CAAC,EAAI,CAAE,WAAU,CACrE,CACF,CAYA,SAAS,EACP,EACA,EACA,EACA,EACA,EACM,CACN,IAAM,EAAkB,EAAkB,EAAY,WAAW,GAAK,GAChE,EAAS,EAAK,UACpB,GAAI,OAAO,GAAW,UAAY,IAAW,IAAM,IAAW,EAAiB,OAC/E,IAAM,EAAU,GAAmC,MAAQ,IAAW,GAChE,EAA6B,CACjC,GAAG,EAAgB,EAAU,EAAM,CAAG,EACtC,UAAW,CACb,EACA,MAAM,EACF,IAAI,EAAc,qBAAsB,CAAC,EAAG,CAAO,EACnD,IAAI,EAAc,sBAAuB,CAAC,EAAG,CAAO,CAC1D,CAEA,SAAS,EACP,EACA,EACA,EACA,EACe,CAEf,GAAI,EAAS,SAAA,IACX,OAAO,IAAI,EACT,kBACA,CAAE,OAAQ,EAAS,MAAO,EAC1B,EAAgB,EAAU,EAAM,EAAK,CAAS,CAChD,EAEF,IAAM,EAAS,EAAS,CAAI,EACtB,EAAS,EAAkB,EAAM,OAAO,EACxC,EACJ,EAAkB,EAAQ,MAAM,GAChC,EAAkB,EAAQ,MAAM,GAChC,EAAkB,EAAQ,UAAU,GACpC,aAGI,EAAU,EAAoB,CAAI,EAClC,EAAe,EAAS,SAAW,IAAM,EAAe,CAAQ,EAAI,IAAA,GAQ1E,OAPI,IAAS,aACJ,IAAI,EACT,aACA,CAAE,OAAQ,EAAS,OAAQ,MAAK,cAAe,CAAQ,EACvD,EAAgB,EAAU,EAAM,EAAK,CAAS,CAChD,EAEK,EAAc,kBACnB,EACA,EACA,EAAS,OACT,EACA,EACA,EACA,IAAc,GAAK,IAAA,GAAY,CACjC,CACF,CAEA,SAAS,EAAS,EAAqD,CACrE,OAAO,OAAO,GAAU,UAAY,EAC/B,EACD,IAAA,EACN,CAEA,SAAS,EAAc,EAAiC,CACtD,OAAO,OAAO,GAAU,UAAY,OAAO,UAAU,CAAK,GAAK,GAAS,CAC1E"}
1
+ {"version":3,"file":"gateway.js","names":[],"sources":["../../../src/classes/gateway.ts"],"sourcesContent":["import type { HttpRequestor, HTTPResponse } from '../types/http_requestor.js';\nimport type { WireOptions } from '../types/wire.js';\nimport {\n getObjectProperty,\n getStringProperty,\n GSwapSDKError,\n isGSwapError,\n UPDATE_REQUIRED_STATUS,\n type GSwapErrorContext,\n} from './gswap_sdk_error.js';\nimport { serverMessageOfBody } from '../utils/server_message.js';\nimport { shadowFailures } from '../utils/shadow_errors.js';\nimport type { SubmissionOutcomeUnknownReason } from './error_catalog.js';\nimport type { ShadowSubmitResult } from './shadow_submit.js';\nimport { SubmittedTransaction, type PositionConfirmationResult } from './submitted_transaction.js';\nimport {\n buildRequestHeaders,\n fetchRequestor,\n readBlockNumberHeader,\n readHeader,\n readResponseBody,\n readRetryAfter,\n requestWithTimeout,\n validateTimeoutMs,\n} from '../utils/transport.js';\nimport type { AppliedTrade, Position } from '../types/v2_results.js';\n\n/** Options for a chain-gateway client. */\nexport interface ChainGatewayOptions {\n dexBackendBaseUrl: string;\n httpRequestor?: HttpRequestor | undefined;\n walletAddress?: string | undefined;\n /**\n * Timeout of every gateway request in milliseconds (default 30 000); also the per-read timeout\n * of the services built on this gateway and of `confirm()`. `Infinity` arms no timer, for an\n * app whose HTTP stack owns request lifetimes.\n */\n chainCallTimeoutMs?: number | undefined;\n /**\n * Wire format of every request of this gateway and of the services built on it: the headers\n * (`userAgent`, `headers`) of reads AND of the POSTs that submit writes, and the query encoding\n * of reads. `X-Wallet-Address` and `X-Shadow` always win over a same-named `wire.headers` entry.\n */\n wire?: WireOptions | undefined;\n /**\n * Tells the gateway which rejections of YOUR `httpRequestor` mean \"no HTTP answer arrived\" (the\n * connection failed, was reset or dropped), as opposed to an answer your requestor turned into\n * an error (an interceptor that rejects on a 4xx, a version wall). Used only when submitting a\n * write: a rejection it accepts becomes `SUBMISSION_OUTCOME_UNKNOWN` with `reason: 'transport'`\n * (the request may have been queued; the original error is `cause`), and any other rejection\n * reaches the caller unchanged. The SDK's own `TRANSPORT_ERROR` (the default `fetch` requestor,\n * `httpRequestorFromAxios`) always counts, so this is only for requestors that reject with\n * their own errors. Never throw from it; a throw is treated as \"not a transport failure\".\n *\n * @example\n * ```ts\n * new ChainGateway({\n * dexBackendBaseUrl,\n * httpRequestor: galaFetch, // a fetch wrapper: its TypeError is the network failing\n * isTransportFailure: (error) => error instanceof TypeError,\n * });\n * ```\n */\n isTransportFailure?: ((error: unknown) => boolean) | undefined;\n /**\n * Tells the gateway which rejections of YOUR `httpRequestor` are an HTTP ANSWER, and what the\n * answer was: a requestor built on an HTTP client that rejects every non-2xx (an interceptor\n * that throws on a 4xx, a version wall that throws on a 426) loses the answer the SDK reads\n * errors from. Return `{ status, body }` (the parsed JSON, or the raw text; `headers` for\n * `Retry-After`) for such a rejection and the write is handled exactly as if the requestor had\n * resolved that answer: the gateway's own code becomes its catalogued error\n * (`DTO_INVALID`, `RATE_LIMITED`, `CHAIN_DISPATCH_FAILED`, ...), an uncoded body `HTTP_ERROR`,\n * both carrying the signed `uniqueKey` and classified by `classifyFailure` as a write. Return\n * `undefined` for any other rejection: it reaches the caller unchanged. Used only when\n * submitting a write; `isTransportFailure` is asked first (no answer proves nothing, so\n * \"maybe queued\" wins), and a throw from this hook, or a `status` outside 100-599, means \"not an\n * answer\". Never throw from it.\n *\n * @example\n * ```ts\n * new ChainGateway({\n * dexBackendBaseUrl,\n * httpRequestor: httpRequestorFromAxios(instance, { rejectOnHttpError: true }),\n * answerOfRejection: (error) =>\n * error instanceof GatewayError && error.status !== undefined\n * ? { status: error.status, body: error.data }\n * : undefined,\n * });\n * ```\n */\n answerOfRejection?: ((error: unknown) => RejectedAnswer | undefined) | undefined;\n}\n\n/** The HTTP answer a rejection of the app's requestor stands for (see `ChainGatewayOptions.answerOfRejection`). */\nexport interface RejectedAnswer {\n /** The HTTP status of the answer (100-599). */\n status: number;\n /** The answer's body: parsed JSON when it was JSON, else the raw text. */\n body: unknown;\n /** The answer's headers, for `Retry-After`. */\n headers?: Record<string, string | undefined> | undefined;\n}\n\n/** Optional attribution and operation-specific confirmation hooks for a write. */\nexport interface ChainSubmitOptions {\n walletAddress?: string | undefined;\n positionConfirmation?: ((signal: AbortSignal) => Promise<PositionConfirmationResult>) | undefined;\n}\n\n/** Thin client for backend-routed v2 writes: synchronous liquidity writes and queued RequestTrade. */\nexport class ChainGateway {\n private readonly requestor: HttpRequestor;\n private readonly walletAddress: string | undefined;\n private readonly chainCallTimeoutMs: number;\n private readonly isTransportFailure: ((error: unknown) => boolean) | undefined;\n private readonly answerOfRejectionHook:\n ((error: unknown) => RejectedAnswer | undefined) | undefined;\n /** The request timeout in milliseconds (`Infinity` when no timer is armed). */\n public readonly requestTimeoutMs: number;\n /** The swap backend base URL every request is sent to, without a trailing slash. */\n public readonly dexBackendBaseUrl: string;\n /** Wire format shared by the read services built on this gateway. */\n public readonly wire: WireOptions;\n\n /** Create a chain gateway client. */\n constructor(options: ChainGatewayOptions) {\n this.dexBackendBaseUrl = trimTrailingSlash(options.dexBackendBaseUrl);\n this.requestor = options.httpRequestor ?? fetchRequestor;\n this.walletAddress = options.walletAddress;\n this.chainCallTimeoutMs = validateTimeoutMs(\n options.chainCallTimeoutMs ?? 30_000,\n 'chainCallTimeoutMs',\n );\n this.requestTimeoutMs = this.chainCallTimeoutMs;\n this.wire = options.wire ?? {};\n this.isTransportFailure = options.isTransportFailure;\n this.answerOfRejectionHook = options.answerOfRejection;\n }\n\n /**\n * Submit a signed v2 DTO through the chain gateway.\n *\n * @throws GSwapSDKError `SUBMISSION_OUTCOME_UNKNOWN` when no answer was read: `params.reason` is\n * `'timeout'` (the POST exceeded `chainCallTimeoutMs`) or `'transport'` (the connection failed\n * or broke off). Either way the write MAY have been queued: keep `error.uniqueKey` and confirm\n * it. Whether to offer the user a Retry (a NEW request under a fresh key) is the app's policy;\n * the SDK states only that the outcome is unknown. Reads keep `TRANSPORT_ERROR`.\n */\n public async submit(\n method: 'RequestTrade',\n signedBody: Record<string, unknown>,\n options?: ChainSubmitOptions,\n ): Promise<SubmittedTransaction<AppliedTrade>>;\n public async submit(\n method: string,\n signedBody: Record<string, unknown>,\n options?: ChainSubmitOptions,\n ): Promise<SubmittedTransaction<Position | null>>;\n public async submit(\n method: string,\n signedBody: Record<string, unknown>,\n options?: ChainSubmitOptions,\n ): Promise<SubmittedTransaction> {\n // The synchronous swap was removed from the contract and from this SDK. This is the only\n // place the retired name may appear in the write path; the class guard in\n // tests/foundation.test.ts pins that nothing signs or submits it.\n if (method === 'Trade') {\n throw new GSwapSDKError('METHOD_NOT_SUPPORTED', { method });\n }\n const url = this.writeUrl(method);\n const walletAddress = options?.walletAddress ?? this.walletAddress;\n const headers = buildRequestHeaders(this.wire, {\n json: true,\n fixed: walletAddress === undefined ? {} : { 'X-Wallet-Address': walletAddress },\n });\n const { response, body } = await this.post(method, url, headers, signedBody);\n // Every failure after an answer names the write it is about, so an app can confirm it and a\n // failure classifier can tell a write's HTTP failure from a read's.\n const uniqueKey = getStringProperty(signedBody, 'uniqueKey') ?? '';\n const context = responseContext(response, body, url, uniqueKey);\n\n if (!response.ok) {\n throw gatewayError(response, body, url, uniqueKey);\n }\n\n const envelope = asRecord(body);\n // `error: true` is the gateway refusing the write, even inside a 2xx. It is never a transaction.\n if (envelope?.['error'] === true) {\n throw new GSwapSDKError(\n 'GATEWAY_REPORTED_ERROR',\n { serverMessage: serverMessageOfBody(envelope) },\n context,\n );\n }\n const data = asRecord(envelope?.['data'] ?? envelope?.['Data']);\n if (data === undefined) {\n throw new GSwapSDKError('GATEWAY_DATA_MISSING', {}, context);\n }\n\n const transactionId = getStringProperty(data, 'transactionId');\n const mode = getStringProperty(data, 'mode');\n const rawTransactionId = data['transactionId'];\n const bodyBlockNumber = data['blockNumber'];\n const invalidMode = (): GSwapSDKError<'GATEWAY_MODE_INVALID'> =>\n new GSwapSDKError('GATEWAY_MODE_INVALID', {}, context);\n if (mode !== 'sync' && mode !== 'queued') {\n throw invalidMode();\n }\n if (\n ('transactionId' in data && typeof rawTransactionId !== 'string') ||\n (bodyBlockNumber !== undefined && bodyBlockNumber !== null && !isBlockNumber(bodyBlockNumber))\n ) {\n throw invalidMode();\n }\n if (method === 'RequestTrade' && mode === 'sync') {\n throw new GSwapSDKError('GATEWAY_SYNC_REQUEST_TRADE', {}, context);\n }\n if (method === 'RequestTrade' && mode === 'queued') {\n assertQueuedUnderSignedKey(response, body, url, data, signedBody);\n } else if (mode === 'queued' || !('result' in data)) {\n throw invalidMode();\n }\n\n // Only synchronous writes carry an executed chain result in the gateway response.\n if (mode === 'sync') {\n const embeddedResult = asRecord(data['result']);\n if (embeddedResult !== undefined) {\n const resultStatus = embeddedResult['Status'];\n if (resultStatus !== undefined && resultStatus !== 1) {\n throw GSwapSDKError.fromChainError(\n getStringProperty(embeddedResult, 'ErrorKey') ?? 'CHAIN_ERROR',\n getStringProperty(embeddedResult, 'Message') ?? '',\n context,\n );\n }\n }\n }\n\n const headerTransactionId = readHeader(response, 'x-transaction-id');\n const resolvedTransactionId =\n transactionId === undefined || transactionId === '' ? headerTransactionId : transactionId;\n const blockNumber: number | undefined =\n bodyBlockNumber === null || bodyBlockNumber === undefined\n ? readBlockNumberHeader(response)\n : isBlockNumber(bodyBlockNumber)\n ? bodyBlockNumber\n : undefined;\n return new SubmittedTransaction({\n method,\n uniqueKey,\n transactionId:\n resolvedTransactionId === undefined || resolvedTransactionId === ''\n ? null\n : resolvedTransactionId,\n blockNumber: blockNumber ?? null,\n mode,\n result: mode === 'queued' ? undefined : data['result'],\n envelope: body,\n dexBackendBaseUrl: this.dexBackendBaseUrl,\n httpRequestor: this.requestor,\n requestTimeoutMs: this.chainCallTimeoutMs,\n wire: this.wire,\n ...(options?.positionConfirmation === undefined\n ? {}\n : { positionConfirmation: options.positionConfirmation }),\n });\n }\n\n /**\n * Submit a signed v2 DTO as a SHADOW (dark) write: the same route and body as {@link submit},\n * plus `X-Wallet-Address` and `X-Shadow: true`, so defi-backend marks the transaction private\n * before it reaches the contract. The wallet address is required (the backend resolves the\n * owner from it).\n *\n * Unlike `submit`, EVERY HTTP status resolves: the result is the raw `{ httpStatus, body }`,\n * because shadow failures are classified from the status together with the body's `code`\n * (`SHADOW_UNAVAILABLE`, `SHADOW_UNSUPPORTED_TYPE`, `SHADOW_RELAY_REJECTED`);\n * see `readShadowSubmitOutcome`. Only a failure to get an answer rejects, and it rejects with\n * `SUBMISSION_OUTCOME_UNKNOWN` (`params.reason` `'timeout'` or `'transport'`): the request may\n * have been accepted. Every error it throws has `error.shadow === true`, and a non-SDK object\n * that escapes (your requestor's own rejection) answers `isShadowError(error)`.\n *\n * @example\n * ```ts\n * const result = await gateway.submitShadow('RequestTrade', signedDto, { walletAddress });\n * const outcome = readShadowSubmitOutcome('RequestTrade', signedDto, result);\n * ```\n */\n public async submitShadow(\n method: string,\n signedBody: Record<string, unknown>,\n options?: ChainSubmitOptions,\n ): Promise<ShadowSubmitResult> {\n return shadowFailures(() => this.sendShadow(method, signedBody, options));\n }\n\n private async sendShadow(\n method: string,\n signedBody: Record<string, unknown>,\n options?: ChainSubmitOptions,\n ): Promise<ShadowSubmitResult> {\n if (method === 'Trade') {\n throw new GSwapSDKError('METHOD_NOT_SUPPORTED', { method });\n }\n const walletAddress = options?.walletAddress ?? this.walletAddress;\n if (walletAddress === undefined) {\n throw new GSwapSDKError('SHADOW_SUBMIT_NEEDS_WALLET_ADDRESS', {});\n }\n // Header order is frontend_v2's: X-Wallet-Address, then X-Shadow.\n const headers = buildRequestHeaders(this.wire, {\n json: true,\n fixed: { 'X-Wallet-Address': walletAddress, 'X-Shadow': 'true' },\n });\n const { response, body } = await this.post(method, this.writeUrl(method), headers, signedBody);\n return { httpStatus: response.status, body };\n }\n\n private writeUrl(method: string): string {\n return `${this.dexBackendBaseUrl}/v1/chain/asset/dex-contract/${method}`;\n }\n\n /**\n * POSTs a signed body and reads the answer. Neither a timeout nor a transport failure proves the\n * write was not submitted, so both are `SUBMISSION_OUTCOME_UNKNOWN` (`params.reason` says which),\n * never a retryable read failure. A rejection that is an answer (see `isTransportFailure`) is\n * rethrown unchanged.\n */\n private async post(\n method: string,\n url: string,\n headers: Record<string, string>,\n signedBody: Record<string, unknown>,\n ): Promise<{ response: HTTPResponse; body: unknown }> {\n // Serialized BEFORE the request is attempted and outside the try below: a body that cannot be\n // serialized (a cycle, a BigInt) was never sent, so it must not be mistaken for a transport\n // failure the app's `isTransportFailure` might claim, which would call it \"maybe queued\".\n let payload: string;\n try {\n payload = JSON.stringify(signedBody);\n } catch (cause: unknown) {\n throw new GSwapSDKError('SIGNING_FAILED', { method, reason: 'failed' }, { cause });\n }\n const outcomeUnknown = (\n reason: SubmissionOutcomeUnknownReason,\n cause: unknown,\n ): GSwapSDKError<'SUBMISSION_OUTCOME_UNKNOWN'> =>\n new GSwapSDKError(\n 'SUBMISSION_OUTCOME_UNKNOWN',\n { reason },\n { uniqueKey: getStringProperty(signedBody, 'uniqueKey') ?? '', url, cause },\n );\n let response: HTTPResponse;\n try {\n response = await requestWithTimeout(\n this.requestor,\n url,\n {\n method: 'POST',\n headers,\n body: payload,\n },\n this.chainCallTimeoutMs,\n );\n } catch (error: unknown) {\n if (isGSwapError(error, 'REQUEST_TIMEOUT')) throw outcomeUnknown('timeout', error);\n if (isGSwapError(error, 'TRANSPORT_ERROR') || this.isTransportRejection(error)) {\n throw outcomeUnknown('transport', error);\n }\n // A rejection that IS an HTTP answer (the app's interceptor rejecting on a non-2xx, a\n // version wall) is handled exactly as the answer would have been, so the app does not\n // rebuild the SDK's error from it. Anything else the app's requestor threw reaches the\n // caller unchanged.\n const answered = this.answerOfRejection(error);\n if (answered !== undefined) return answered;\n throw error;\n }\n try {\n return { response, body: await readResponseBody(response) };\n } catch (error: unknown) {\n // The answer's headers arrived but its body broke off: the status alone does not say what\n // the gateway did with the write.\n throw outcomeUnknown('transport', error);\n }\n }\n\n /**\n * The HTTP answer a rejection of the app's requestor stands for, as the response the gateway\n * would have read. `undefined` when the app named none, when its hook throws, or when what it\n * returned is not an HTTP status; then the rejection is not an answer.\n */\n private answerOfRejection(error: unknown): { response: HTTPResponse; body: unknown } | undefined {\n let answer: RejectedAnswer | undefined;\n try {\n answer = this.answerOfRejectionHook?.(error);\n } catch {\n return undefined;\n }\n if (answer === undefined) return undefined;\n const { status, body, headers } = answer;\n if (!Number.isInteger(status) || status < 100 || status > 599) return undefined;\n const text = typeof body === 'string' ? body : '';\n const response: HTTPResponse = {\n ok: status >= 200 && status < 300,\n status,\n json: async () => body,\n text: async () => text,\n headers: headers ?? {},\n };\n return { response, body };\n }\n\n private isTransportRejection(error: unknown): boolean {\n try {\n return this.isTransportFailure?.(error) === true;\n } catch {\n return false;\n }\n }\n\n /** Expose the requestor for sibling read services that share this gateway transport. */\n public get httpRequestor(): HttpRequestor {\n return this.requestor;\n }\n}\n\nfunction trimTrailingSlash(value: string): string {\n return value.replace(/\\/$/u, '');\n}\n\nfunction responseContext(\n response: HTTPResponse,\n body: unknown,\n url: string,\n uniqueKey?: string,\n): GSwapErrorContext {\n return {\n status: response.status,\n body,\n url,\n ...(uniqueKey === undefined || uniqueKey === '' ? {} : { uniqueKey }),\n };\n}\n\n/**\n * A queued RequestTrade acknowledgement is a swap only if it names the exact key that was signed:\n * the key is the only handle `confirm()` and every recovery path have on the trade, so an\n * acknowledgement without it (or with another) proves nothing about THIS request. Fails closed:\n * an absent echo is as unacceptable as a wrong one. The code is `MISSING_UNIQUE_KEY` or\n * `UNIQUE_KEY_MISMATCH`, `error.uniqueKey` the signed key.\n *\n * Only RequestTrade is checked: a synchronous write is proven by its own executed chain result,\n * and the apps never required an echo for it.\n */\nfunction assertQueuedUnderSignedKey(\n response: HTTPResponse,\n body: unknown,\n url: string,\n data: Record<string, unknown>,\n signedBody: Record<string, unknown>,\n): void {\n const signedUniqueKey = getStringProperty(signedBody, 'uniqueKey') ?? '';\n const echoed = data['uniqueKey'];\n if (typeof echoed === 'string' && echoed !== '' && echoed === signedUniqueKey) return;\n const missing = echoed === undefined || echoed === null || echoed === '';\n const context: GSwapErrorContext = {\n ...responseContext(response, body, url),\n uniqueKey: signedUniqueKey,\n };\n throw missing\n ? new GSwapSDKError('MISSING_UNIQUE_KEY', {}, context)\n : new GSwapSDKError('UNIQUE_KEY_MISMATCH', {}, context);\n}\n\nfunction gatewayError(\n response: HTTPResponse,\n body: unknown,\n url: string,\n uniqueKey: string,\n): GSwapSDKError {\n // The version wall is decided by the status alone, whatever the body names: nothing was queued.\n if (response.status === UPDATE_REQUIRED_STATUS) {\n return new GSwapSDKError(\n 'UPDATE_REQUIRED',\n { status: response.status },\n responseContext(response, body, url, uniqueKey),\n );\n }\n const object = asRecord(body);\n const nested = getObjectProperty(body, 'error');\n const code =\n getStringProperty(object, 'code') ??\n getStringProperty(nested, 'code') ??\n getStringProperty(nested, 'ErrorKey') ??\n 'HTTP_ERROR';\n // Missing server text is an empty param, never SDK-written English standing in for it. A body\n // that is not JSON (an HTML 502 page, a WAF block) is kept as error.bodyText.\n const message = serverMessageOfBody(body);\n const retryAfterMs = response.status === 429 ? readRetryAfter(response) : undefined;\n if (code === 'HTTP_ERROR') {\n return new GSwapSDKError(\n 'HTTP_ERROR',\n { status: response.status, url, serverMessage: message },\n responseContext(response, body, url, uniqueKey),\n );\n }\n return GSwapSDKError.fromGatewayBounce(\n code,\n message,\n response.status,\n body,\n url,\n retryAfterMs,\n uniqueKey === '' ? undefined : uniqueKey,\n );\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n return typeof value === 'object' && value !== null\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\nfunction isBlockNumber(value: unknown): value is number {\n return typeof value === 'number' && Number.isInteger(value) && value >= 0;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"gdex_stream.js","names":[],"sources":["../../../src/classes/gdex_stream.ts"],"sourcesContent":["import { GSWAP_ENVIRONMENTS } from '../types/env.js';\nimport { debugLog } from '../utils/debug.js';\nimport { ReconnectionBackoff } from '../utils/reconnection.js';\nimport { GSwapSDKError } from './gswap_sdk_error.js';\n\nconst GDEX_SUBPROTOCOL = 'gdex.jsonrpc.v1';\nconst RATE_LIMITED = -32003;\nconst MAX_RESUBSCRIBE_RATE_RETRIES = 5;\nconst WEBSOCKET_OPEN = 1;\n\n/** Options for {@link GdexStreamService}. */\nexport interface GdexStreamConfig {\n /** Stream endpoint. Defaults to production (`wss://dex-backend-prod1.defi.gala.com/ws`). */\n url?: string | undefined;\n /** Reconnect attempts after an unexpected close. Default 5. */\n reconnectAttempts?: number | undefined;\n /** Delay before the first reconnect attempt; doubles each attempt. Default 2000 ms. */\n reconnectDelayMs?: number | undefined;\n /** How long a request waits for its response. Default 15000 ms. */\n requestTimeoutMs?: number | undefined;\n /**\n * A standards-compatible WebSocket constructor. Required on Node.js older than 22, which has no\n * global `WebSocket` (pass the one from the `ws` package).\n */\n webSocketImpl?: unknown;\n /**\n * Presents `x-api-key` on the WebSocket upgrade request, which defi-backend uses to serve the\n * PRIVILEGED view (shadow swaps with the wallet unredacted). Without it, or with a wrong key, the\n * connection is simply public: the server never says whether a privileged mode exists.\n *\n * It must travel as an HTTP header, because the server reads no other credential, and a browser\n * `WebSocket` cannot set headers. So this option needs a constructor that accepts them as its\n * third argument (`{ headers }`): the `ws` package on Node.js, or React Native's built-in\n * `WebSocket`. It therefore REQUIRES `webSocketImpl` (the platform default is never assumed to\n * honor headers, and a key that is silently dropped would look like a working privileged\n * connection); the constructor throws otherwise. Use it from server-side code only: a key\n * shipped inside a browser or app bundle is not a secret. Browser clients stay public.\n */\n apiKey?: string | undefined;\n}\n\n/** The channels of the realtime stream. */\nexport type GdexChannel = 'pool' | 'balance' | 'poolCreated' | 'swap' | 'liquidity' | 'blocks';\n\n/** A pool named by its two token class keys (pipe form) and fee tier. */\nexport interface GdexPoolKey {\n token0ClassKey: string;\n token1ClassKey: string;\n fee: number;\n}\n\n/** A pool named either by its key or by its hash (`v2:<poolRef>` for a v2 pool). */\nexport type GdexPoolSelector = { pool: GdexPoolKey } | { poolHash: string };\n\n/** Options common to every subscription. */\nexport interface GdexSubscribeOptions {\n /** Send the current state right after the subscribe response. */\n snapshot?: boolean | undefined;\n /** Trim each event to these fields; identity and envelope fields always stay. */\n fields?: string[] | undefined;\n}\n\n/** Filters for {@link GdexStreamService.subscribeLiquidity}; all optional. */\nexport interface GdexLiquidityFilter {\n /** Only this pool, named by its key. */\n pool?: GdexPoolKey | undefined;\n /** Only this pool, named by its hash (`v2:<poolRef>` for a v2 pool). */\n poolHash?: string | undefined;\n /** Only events for this LP wallet (the position's owner). */\n owner?: string | undefined;\n /** Only this transaction. */\n transactionId?: string | undefined;\n /** Contract version filter. */\n version?: 'v1' | 'v2' | 'all' | undefined;\n}\n\n/** One committed add, remove or collect, as the `liquidity` channel reports it. */\nexport interface GdexLiquidityEvent {\n version: 'v1' | 'v2';\n pool: GdexPoolKey;\n poolHash: string;\n transactionId: string;\n /** `add`, `remove` or `collect`. */\n action: 'add' | 'remove' | 'collect';\n /** The position's owner (the LP wallet). */\n owner: string;\n /** v1: the position id. v2: the chain position key `<pool>|<tickLow>|<tickHigh>|<owner>`. */\n positionId?: string;\n tickLower?: number;\n tickUpper?: number;\n /** v2 only: the position's liquidity after the operation; `\"0\"` means it is closed. */\n liquidityAfter?: string;\n /** Principal moved: deposited by an add, withdrawn by a remove, `\"0\"` for a collect. */\n amount0: string;\n amount1: string;\n /** Fees paid out. v2: always present (`\"0\"` for an add). v1: present on a collect only. */\n fees0?: string;\n fees1?: string;\n /** v2 only. */\n token0Symbol?: string;\n token1Symbol?: string;\n blockNumber: number;\n timestamp: string;\n}\n\nconst LIQUIDITY_ACTIONS: readonly unknown[] = ['add', 'remove', 'collect'];\n\n/** Whether a stream payload carries every field of a {@link GdexLiquidityEvent}. */\nexport function isGdexLiquidityEvent(\n value: Record<string, unknown>,\n): value is Record<string, unknown> & GdexLiquidityEvent {\n const { pool } = value;\n return (\n (value['version'] === 'v1' || value['version'] === 'v2') &&\n typeof pool === 'object' &&\n pool !== null &&\n typeof value['poolHash'] === 'string' &&\n typeof value['transactionId'] === 'string' &&\n LIQUIDITY_ACTIONS.includes(value['action']) &&\n typeof value['owner'] === 'string' &&\n typeof value['amount0'] === 'string' &&\n typeof value['amount1'] === 'string' &&\n typeof value['blockNumber'] === 'number' &&\n typeof value['timestamp'] === 'string'\n );\n}\n\n/** A live subscription. */\nexport interface GdexSubscription {\n subscriptionId: string;\n unsubscribe(): Promise<void>;\n}\n\n/** A JSON-RPC error answered by the stream. */\nexport class GdexStreamError extends Error {\n readonly code: number;\n readonly data?: unknown;\n\n constructor(code: number, message: string, data?: unknown) {\n super(message);\n this.name = 'GdexStreamError';\n this.code = code;\n if (data !== undefined) this.data = data;\n }\n}\n\ninterface WebSocketLike {\n readonly readyState: number;\n send(data: string): void;\n close(code?: number, reason?: string): void;\n onopen: ((event: unknown) => void) | null;\n onmessage: ((event: { data: unknown }) => void) | null;\n onclose: ((event: { code?: number; reason?: string }) => void) | null;\n onerror: ((event: unknown) => void) | null;\n}\n\ninterface WebSocketConstructor {\n new (\n url: string,\n protocols?: string | string[],\n options?: { headers: Record<string, string> },\n ): WebSocketLike;\n}\n\ninterface PendingRequest {\n resolve(value: unknown): void;\n reject(error: Error): void;\n timeout: ReturnType<typeof setTimeout>;\n /**\n * Runs SYNCHRONOUSLY when the response frame is handled, before any frame that follows it in\n * the same read. Subscriptions register here: the server writes a snapshot right after the\n * subscribe response, and a runtime that delivers both frames without yielding to microtasks\n * (Node's `ws` does, for frames in one chunk) would otherwise drop the snapshot because the\n * awaiting caller has not registered the subscription yet.\n */\n onResult?: ((value: unknown) => void) | undefined;\n}\n\ntype EventCallback = (event: Record<string, unknown>) => void;\n\ninterface ActiveSubscription {\n serverId: string;\n readonly params: Record<string, unknown>;\n readonly callback: EventCallback;\n readonly handle: MutableGdexSubscription;\n active: boolean;\n}\n\ninterface MutableGdexSubscription extends GdexSubscription {\n subscriptionId: string;\n}\n\nfunction readSubscriptionId(value: unknown): string {\n const id =\n typeof value === 'object' && value !== null\n ? (value as { subscriptionId?: unknown }).subscriptionId\n : undefined;\n if (typeof id !== 'string') {\n throw new GdexStreamError(-32603, 'GDEX subscribe response is missing its subscriptionId');\n }\n return id;\n}\n\ninterface JsonRpcResponse {\n id?: unknown;\n method?: unknown;\n result?: unknown;\n error?: { code?: unknown; message?: unknown; data?: unknown };\n params?: unknown;\n}\n\n/**\n * Client for the gSwap realtime stream (gdex WebSocket Protocol v1): JSON-RPC 2.0 over one raw\n * WebSocket, with automatic reconnect and resubscribe.\n *\n * @example\n * ```typescript\n * const stream = new GdexStreamService();\n * await stream.connect();\n * await stream.subscribeLiquidity(event => console.log(event.action, event.owner), {\n * owner: 'client|618ae395c1c653111d3315be',\n * });\n * ```\n */\nexport class GdexStreamService {\n private readonly url: string;\n private readonly requestTimeoutMs: number;\n private readonly apiKey: string | undefined;\n private readonly webSocketConstructor: WebSocketConstructor;\n private readonly backoff: ReconnectionBackoff;\n private socket: WebSocketLike | undefined;\n private connectPromise: Promise<void> | undefined;\n private nextRequestId = 0;\n private readonly pending = new Map<number, PendingRequest>();\n private readonly subscriptionsByServerId = new Map<string, ActiveSubscription>();\n private readonly activeSubscriptions = new Set<ActiveSubscription>();\n private intentionalClose = false;\n private reconnecting = false;\n private reconnectTimer: ReturnType<typeof setTimeout> | undefined;\n private reconnectDelayResolve: (() => void) | undefined;\n\n constructor(config: GdexStreamConfig = {}) {\n this.url = config.url ?? GSWAP_ENVIRONMENTS.prod.dexBackendWsUrl;\n this.requestTimeoutMs = config.requestTimeoutMs ?? 15_000;\n this.backoff = new ReconnectionBackoff({\n maxAttempts: config.reconnectAttempts ?? 5,\n baseDelayMs: config.reconnectDelayMs ?? 2_000,\n });\n\n const implementation =\n config.webSocketImpl ?? (globalThis as { WebSocket?: unknown }).WebSocket;\n if (typeof implementation !== 'function') {\n throw new GSwapSDKError('STREAM_WEBSOCKET_UNAVAILABLE', {});\n }\n this.webSocketConstructor = implementation as WebSocketConstructor;\n if (config.apiKey !== undefined) {\n if (config.apiKey === '' || config.webSocketImpl === undefined) {\n throw new GSwapSDKError('STREAM_API_KEY_UNUSABLE', {});\n }\n this.apiKey = config.apiKey;\n }\n }\n\n /** Open the connection. Resolves at once when already connected. */\n connect(): Promise<void> {\n if (this.isConnected()) return Promise.resolve();\n if (this.connectPromise) return this.connectPromise;\n this.intentionalClose = false;\n this.clearReconnectTimer();\n this.connectPromise = this.openSocket();\n return this.connectPromise;\n }\n\n /** Close the connection and stop reconnecting. */\n disconnect(): void {\n this.intentionalClose = true;\n this.reconnecting = false;\n this.clearReconnectTimer();\n this.rejectPending(new GSwapSDKError('STREAM_DISCONNECTED', {}));\n const socket = this.socket;\n this.socket = undefined;\n this.connectPromise = undefined;\n socket?.close(1000, 'client disconnect');\n }\n\n isConnected(): boolean {\n return this.socket?.readyState === WEBSOCKET_OPEN;\n }\n\n /** Raw JSON-RPC escape hatch. */\n request<T = unknown>(method: string, params?: Record<string, unknown>): Promise<T> {\n return this.send<T>(method, params);\n }\n\n private send<T>(\n method: string,\n params: Record<string, unknown> | undefined,\n onResult?: (value: unknown) => void,\n ): Promise<T> {\n if (!this.isConnected() || !this.socket) {\n return Promise.reject(new GSwapSDKError('STREAM_NOT_CONNECTED', {}));\n }\n\n const id = ++this.nextRequestId;\n const frame: Record<string, unknown> = { jsonrpc: '2.0', id, method };\n if (params !== undefined) frame['params'] = params;\n\n return new Promise<T>((resolve, reject) => {\n const timeout = setTimeout(() => {\n this.pending.delete(id);\n reject(\n new GSwapSDKError('STREAM_REQUEST_TIMEOUT', { method, timeoutMs: this.requestTimeoutMs }),\n );\n }, this.requestTimeoutMs);\n this.pending.set(id, { resolve: (value) => resolve(value as T), reject, timeout, onResult });\n\n try {\n this.socket?.send(JSON.stringify(frame));\n } catch (error) {\n clearTimeout(timeout);\n this.pending.delete(id);\n reject(error instanceof Error ? error : new Error(String(error)));\n }\n });\n }\n\n serverInfo(): Promise<Record<string, unknown>> {\n return this.request('gdex_serverInfo');\n }\n\n ping(): Promise<{ pong: boolean; serverTime: string }> {\n return this.request('gdex_ping');\n }\n\n getPool(selector: GdexPoolSelector): Promise<Record<string, unknown>> {\n return this.request('gdex_getPool', selector);\n }\n\n getBalance(owner: string, tokenClassKey?: string): Promise<Record<string, unknown>> {\n return this.request(\n 'gdex_getBalance',\n tokenClassKey === undefined ? { owner } : { owner, tokenClassKey },\n );\n }\n\n getPositions(opts: {\n owner: string;\n pool?: GdexPoolKey;\n poolHash?: string;\n limit?: number;\n bookmark?: string;\n }): Promise<{ positions: Record<string, unknown>[]; bookmark: string }> {\n return this.request('gdex_getPositions', opts);\n }\n\n getBlockHeight(): Promise<{ blockNumber: number; timestamp: string }> {\n return this.request('gdex_getBlockHeight');\n }\n\n quote(params: {\n tokenIn: string;\n tokenOut: string;\n fee?: number;\n amountIn?: string;\n amountOut?: string;\n }): Promise<Record<string, unknown>> {\n return this.request('gdex_quote', params);\n }\n\n getToken(tokenClassKey: string): Promise<Record<string, unknown>> {\n return this.request('gdex_getToken', { tokenClassKey });\n }\n\n /** One event per committed swap. Omit the pool to receive every pool's swaps. */\n subscribeSwaps(\n cb: (event: Record<string, unknown>) => void,\n opts: GdexSubscribeOptions & {\n pool?: GdexPoolKey;\n poolHash?: string;\n transactionId?: string;\n } = {},\n ): Promise<GdexSubscription> {\n return this.subscribe('swap', cb, opts);\n }\n\n /**\n * State changes of one pool (price, tick, reserves, liquidity). These events carry no acting wallet:\n * on v2 `creator` is whoever created the pool, not whoever just traded or added liquidity. For\n * \"who did it\", use {@link subscribeLiquidity} (`owner`) or {@link subscribeSwaps} (`sender`).\n */\n subscribePool(\n selector: GdexPoolSelector,\n cb: (event: Record<string, unknown>) => void,\n opts: GdexSubscribeOptions = {},\n ): Promise<GdexSubscription> {\n return this.subscribe('pool', cb, { ...selector, ...opts });\n }\n\n /** Balance changes of one wallet. */\n subscribeBalances(\n owner: string,\n cb: (event: Record<string, unknown>) => void,\n opts: GdexSubscribeOptions & { tokenClassKey?: string } = {},\n ): Promise<GdexSubscription> {\n return this.subscribe('balance', cb, { owner, ...opts });\n }\n\n /** Each newly created pool. */\n subscribePoolCreated(\n cb: (event: Record<string, unknown>) => void,\n opts: GdexSubscribeOptions & { tokenClassKey?: string } = {},\n ): Promise<GdexSubscription> {\n return this.subscribe('poolCreated', cb, opts);\n }\n\n /**\n * One event per committed AddLiquidity, RemoveLiquidity and CollectPositionFees, on both contract\n * versions. Every filter is optional; with none, every liquidity event arrives. These events never\n * appear on {@link subscribeSwaps}. There is no snapshot: read positions with {@link getPositions}.\n * Deduplicate by `transactionId` plus `positionId`: a v1 BatchSubmit can hold several operations\n * under one transaction id.\n */\n subscribeLiquidity(\n cb: (event: GdexLiquidityEvent) => void,\n opts: GdexSubscribeOptions & GdexLiquidityFilter = {},\n ): Promise<GdexSubscription> {\n return this.subscribe(\n 'liquidity',\n (event) => {\n if (isGdexLiquidityEvent(event)) cb(event);\n else debugLog('Ignoring a malformed liquidity event from the GDEX stream');\n },\n opts,\n );\n }\n\n /** Each block-height advance. */\n subscribeBlocks(\n cb: (event: Record<string, unknown>) => void,\n opts: GdexSubscribeOptions = {},\n ): Promise<GdexSubscription> {\n return this.subscribe('blocks', cb, opts);\n }\n\n private openSocket(): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n let opened = false;\n const socket =\n this.apiKey === undefined\n ? new this.webSocketConstructor(this.url, GDEX_SUBPROTOCOL)\n : new this.webSocketConstructor(this.url, GDEX_SUBPROTOCOL, {\n headers: { 'x-api-key': this.apiKey },\n });\n this.socket = socket;\n\n socket.onopen = () => {\n if (this.socket !== socket) return;\n opened = true;\n this.connectPromise = undefined;\n if (!this.reconnecting) this.backoff.reset();\n debugLog('Connected to GDEX stream');\n resolve();\n };\n socket.onmessage = (event) => this.handleMessage(event.data);\n socket.onerror = () => {\n if (!opened) reject(new GSwapSDKError('STREAM_CONNECT_FAILED', { url: this.url }));\n };\n socket.onclose = (event) => {\n if (this.socket !== socket) return;\n this.socket = undefined;\n this.connectPromise = undefined;\n if (!opened) {\n reject(new GSwapSDKError('STREAM_CLOSED_BEFORE_CONNECT', { closeCode: event.code ?? 0 }));\n }\n this.rejectPending(\n new GSwapSDKError('STREAM_CONNECTION_CLOSED', { closeCode: event.code ?? 0 }),\n );\n if (!this.intentionalClose && !this.reconnecting) this.startReconnectLoop();\n };\n });\n }\n\n private handleMessage(data: unknown): void {\n if (typeof data !== 'string') {\n debugLog('Ignoring non-text GDEX WebSocket frame');\n return;\n }\n\n let message: JsonRpcResponse;\n try {\n message = JSON.parse(data) as JsonRpcResponse;\n } catch {\n debugLog('Ignoring invalid JSON from GDEX stream');\n return;\n }\n\n if (typeof message.id === 'number') {\n const pending = this.pending.get(message.id);\n if (!pending) return;\n clearTimeout(pending.timeout);\n this.pending.delete(message.id);\n if (message.error) {\n const code = typeof message.error.code === 'number' ? message.error.code : -32603;\n const errorMessage =\n typeof message.error.message === 'string' ? message.error.message : 'GDEX request failed';\n pending.reject(new GdexStreamError(code, errorMessage, message.error.data));\n } else {\n try {\n pending.onResult?.(message.result);\n } catch (error) {\n pending.reject(error instanceof Error ? error : new Error(String(error)));\n return;\n }\n pending.resolve(message.result);\n }\n return;\n }\n\n if (message.method !== 'gdex_subscription' || !this.isRecord(message.params)) return;\n const subscriptionId = message.params['subscription'];\n const result = message.params['result'];\n if (typeof subscriptionId !== 'string' || !this.isRecord(result)) return;\n const subscription = this.subscriptionsByServerId.get(subscriptionId);\n if (!subscription?.active) return;\n try {\n subscription.callback(result);\n } catch (error) {\n debugLog('GDEX subscription callback failed', error);\n }\n }\n\n private async subscribe(\n channel: GdexChannel,\n callback: EventCallback,\n options: object,\n ): Promise<GdexSubscription> {\n const params: Record<string, unknown> = { channel, ...options };\n const handle: MutableGdexSubscription = {\n subscriptionId: '',\n unsubscribe: async () => this.unsubscribe(subscription),\n };\n const subscription: ActiveSubscription = {\n serverId: '',\n params,\n callback,\n handle,\n active: true,\n };\n // Registered inside the response handler, not after the await: see PendingRequest.onResult.\n await this.send('gdex_subscribe', params, (value) => {\n const serverId = readSubscriptionId(value);\n subscription.serverId = serverId;\n handle.subscriptionId = serverId;\n this.activeSubscriptions.add(subscription);\n this.subscriptionsByServerId.set(serverId, subscription);\n });\n return handle;\n }\n\n private async unsubscribe(subscription: ActiveSubscription): Promise<void> {\n if (!subscription.active) return;\n subscription.active = false;\n this.activeSubscriptions.delete(subscription);\n this.subscriptionsByServerId.delete(subscription.serverId);\n if (this.isConnected()) {\n await this.request('gdex_unsubscribe', { subscriptionId: subscription.serverId });\n }\n }\n\n private startReconnectLoop(): void {\n if (this.reconnecting || this.intentionalClose) return;\n this.reconnecting = true;\n void this.reconnectLoop();\n }\n\n private async reconnectLoop(): Promise<void> {\n while (!this.intentionalClose && this.backoff.shouldRetry()) {\n const delay = this.backoff.nextDelayMs();\n this.backoff.recordAttempt();\n debugLog(\n `GDEX reconnect attempt ${this.backoff.attemptCount}/${this.backoff.maxAttempts} in ${delay}ms`,\n );\n await this.waitForReconnectDelay(delay);\n // `disconnect()` can run while the asynchronous delay is pending.\n if (this.intentionalClose) break;\n\n try {\n await this.openSocket();\n await this.resubscribeAll();\n this.backoff.reset();\n this.reconnecting = false;\n return;\n } catch (error) {\n debugLog(\n `GDEX reconnect failed: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n this.reconnecting = false;\n if (!this.intentionalClose) debugLog('GDEX reconnection attempts exhausted');\n }\n\n private async resubscribeAll(): Promise<void> {\n this.subscriptionsByServerId.clear();\n for (const subscription of this.activeSubscriptions) {\n if (!subscription.active) continue;\n const params = { ...subscription.params, snapshot: true };\n const result = await this.resubscribeWithRateLimitRetry(params, (value) => {\n const serverId = readSubscriptionId(value);\n // The consumer can unsubscribe while the asynchronous subscribe request is pending.\n if (!subscription.active) return;\n subscription.serverId = serverId;\n subscription.handle.subscriptionId = serverId;\n this.subscriptionsByServerId.set(serverId, subscription);\n });\n if (!subscription.active) {\n await this.request('gdex_unsubscribe', { subscriptionId: result.subscriptionId });\n }\n }\n }\n\n private async resubscribeWithRateLimitRetry(\n params: Record<string, unknown>,\n onResult: (value: unknown) => void,\n ): Promise<{ subscriptionId: string }> {\n for (let attempt = 0; ; attempt++) {\n try {\n return await this.send('gdex_subscribe', params, onResult);\n } catch (error) {\n if (\n !(error instanceof GdexStreamError) ||\n error.code !== RATE_LIMITED ||\n attempt >= MAX_RESUBSCRIBE_RATE_RETRIES\n ) {\n throw error;\n }\n const retryAfterMs = this.readRetryAfterMs(error.data);\n debugLog(`GDEX resubscribe rate limited; retrying in ${retryAfterMs}ms`);\n await new Promise<void>((resolve) => setTimeout(resolve, retryAfterMs));\n }\n }\n }\n\n private waitForReconnectDelay(delay: number): Promise<void> {\n return new Promise((resolve) => {\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = undefined;\n this.reconnectDelayResolve = undefined;\n resolve();\n }, delay);\n this.reconnectDelayResolve = resolve;\n });\n }\n\n private clearReconnectTimer(): void {\n if (this.reconnectTimer !== undefined) clearTimeout(this.reconnectTimer);\n this.reconnectTimer = undefined;\n const resolve = this.reconnectDelayResolve;\n this.reconnectDelayResolve = undefined;\n resolve?.();\n }\n\n private rejectPending(error: Error): void {\n for (const pending of this.pending.values()) {\n clearTimeout(pending.timeout);\n pending.reject(error);\n }\n this.pending.clear();\n }\n\n private readRetryAfterMs(data: unknown): number {\n if (\n !this.isRecord(data) ||\n typeof data['retryAfterMs'] !== 'number' ||\n data['retryAfterMs'] < 0\n )\n return 1_000;\n return data['retryAfterMs'];\n }\n\n private isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"gdex_stream.js","names":[],"sources":["../../../src/classes/gdex_stream.ts"],"sourcesContent":["import { GSWAP_ENVIRONMENTS } from '../types/env.js';\nimport { debugLog } from '../utils/debug.js';\nimport { ReconnectionBackoff } from '../utils/reconnection.js';\nimport { GSwapSDKError } from './gswap_sdk_error.js';\n\nconst GDEX_SUBPROTOCOL = 'gdex.jsonrpc.v1';\nconst RATE_LIMITED = -32003;\nconst MAX_RESUBSCRIBE_RATE_RETRIES = 5;\nconst WEBSOCKET_OPEN = 1;\n\n/** Options for {@link GdexStreamService}. */\nexport interface GdexStreamConfig {\n /** Stream endpoint. Defaults to production (`wss://dex-backend-prod1.defi.gala.com/ws`). */\n url?: string | undefined;\n /** Reconnect attempts after an unexpected close. Default 5. */\n reconnectAttempts?: number | undefined;\n /** Delay before the first reconnect attempt; doubles each attempt. Default 2000 ms. */\n reconnectDelayMs?: number | undefined;\n /** How long a request waits for its response. Default 15000 ms. */\n requestTimeoutMs?: number | undefined;\n /**\n * A standards-compatible WebSocket constructor. Required on Node.js older than 22, which has no\n * global `WebSocket` (pass the one from the `ws` package).\n */\n webSocketImpl?: unknown;\n /**\n * Presents `x-api-key` on the WebSocket upgrade request, which defi-backend uses to serve the\n * PRIVILEGED view (shadow swaps with the wallet unredacted). Without it, or with a wrong key, the\n * connection is simply public: the server never says whether a privileged mode exists.\n *\n * It must travel as an HTTP header, because the server reads no other credential, and a browser\n * `WebSocket` cannot set headers. So this option needs a constructor that accepts them as its\n * third argument (`{ headers }`): the `ws` package on Node.js, or React Native's built-in\n * `WebSocket`. It therefore REQUIRES `webSocketImpl` (the platform default is never assumed to\n * honor headers, and a key that is silently dropped would look like a working privileged\n * connection); the constructor throws otherwise. Use it from server-side code only: a key\n * shipped inside a browser or app bundle is not a secret. Browser clients stay public.\n */\n apiKey?: string | undefined;\n}\n\n/** The channels of the realtime stream. */\nexport type GdexChannel = 'pool' | 'balance' | 'poolCreated' | 'swap' | 'liquidity' | 'blocks';\n\n/** A pool named by its two token class keys (pipe form) and fee tier. */\nexport interface GdexPoolKey {\n token0ClassKey: string;\n token1ClassKey: string;\n fee: number;\n}\n\n/** A pool named either by its key or by its hash (`v2:<poolRef>` for a v2 pool). */\nexport type GdexPoolSelector = { pool: GdexPoolKey } | { poolHash: string };\n\n/** Options common to every subscription. */\nexport interface GdexSubscribeOptions {\n /** Send the current state right after the subscribe response. */\n snapshot?: boolean | undefined;\n /** Trim each event to these fields; identity and envelope fields always stay. */\n fields?: string[] | undefined;\n}\n\n/** Filters for {@link GdexStreamService.subscribeLiquidity}; all optional. */\nexport interface GdexLiquidityFilter {\n /** Only this pool, named by its key. */\n pool?: GdexPoolKey | undefined;\n /** Only this pool, named by its hash (`v2:<poolRef>` for a v2 pool). */\n poolHash?: string | undefined;\n /** Only events for this LP wallet (the position's owner). */\n owner?: string | undefined;\n /** Only this transaction. */\n transactionId?: string | undefined;\n /** Contract version filter. */\n version?: 'v1' | 'v2' | 'all' | undefined;\n}\n\n/** One committed add, remove or collect, as the `liquidity` channel reports it. */\nexport interface GdexLiquidityEvent {\n version: 'v1' | 'v2';\n pool: GdexPoolKey;\n poolHash: string;\n transactionId: string;\n /** `add`, `remove` or `collect`. */\n action: 'add' | 'remove' | 'collect';\n /** The position's owner (the LP wallet). */\n owner: string;\n /** v1: the position id. v2: the chain position key `<pool>|<tickLow>|<tickHigh>|<owner>`. */\n positionId?: string;\n tickLower?: number;\n tickUpper?: number;\n /** v2 only: the position's liquidity after the operation; `\"0\"` means it is closed. */\n liquidityAfter?: string;\n /** Principal moved: deposited by an add, withdrawn by a remove, `\"0\"` for a collect. */\n amount0: string;\n amount1: string;\n /** Fees paid out. v2: always present (`\"0\"` for an add). v1: present on a collect only. */\n fees0?: string;\n fees1?: string;\n /** v2 only. */\n token0Symbol?: string;\n token1Symbol?: string;\n blockNumber: number;\n timestamp: string;\n}\n\nconst LIQUIDITY_ACTIONS: readonly unknown[] = ['add', 'remove', 'collect'];\n\n/** Whether a stream payload carries every field of a {@link GdexLiquidityEvent}. */\nexport function isGdexLiquidityEvent(\n value: Record<string, unknown>,\n): value is Record<string, unknown> & GdexLiquidityEvent {\n const { pool } = value;\n return (\n (value['version'] === 'v1' || value['version'] === 'v2') &&\n typeof pool === 'object' &&\n pool !== null &&\n typeof value['poolHash'] === 'string' &&\n typeof value['transactionId'] === 'string' &&\n LIQUIDITY_ACTIONS.includes(value['action']) &&\n typeof value['owner'] === 'string' &&\n typeof value['amount0'] === 'string' &&\n typeof value['amount1'] === 'string' &&\n typeof value['blockNumber'] === 'number' &&\n typeof value['timestamp'] === 'string'\n );\n}\n\n/** A live subscription. */\nexport interface GdexSubscription {\n subscriptionId: string;\n unsubscribe(): Promise<void>;\n}\n\n/** A JSON-RPC error answered by the stream. */\nexport class GdexStreamError extends Error {\n /** The JSON-RPC error code. */\n readonly code: number;\n /** The error's optional `data` member, as the server sent it. */\n readonly data?: unknown;\n\n constructor(code: number, message: string, data?: unknown) {\n super(message);\n this.name = 'GdexStreamError';\n this.code = code;\n if (data !== undefined) this.data = data;\n }\n}\n\ninterface WebSocketLike {\n readonly readyState: number;\n send(data: string): void;\n close(code?: number, reason?: string): void;\n onopen: ((event: unknown) => void) | null;\n onmessage: ((event: { data: unknown }) => void) | null;\n onclose: ((event: { code?: number; reason?: string }) => void) | null;\n onerror: ((event: unknown) => void) | null;\n}\n\ninterface WebSocketConstructor {\n new (\n url: string,\n protocols?: string | string[],\n options?: { headers: Record<string, string> },\n ): WebSocketLike;\n}\n\ninterface PendingRequest {\n resolve(value: unknown): void;\n reject(error: Error): void;\n timeout: ReturnType<typeof setTimeout>;\n /**\n * Runs SYNCHRONOUSLY when the response frame is handled, before any frame that follows it in\n * the same read. Subscriptions register here: the server writes a snapshot right after the\n * subscribe response, and a runtime that delivers both frames without yielding to microtasks\n * (Node's `ws` does, for frames in one chunk) would otherwise drop the snapshot because the\n * awaiting caller has not registered the subscription yet.\n */\n onResult?: ((value: unknown) => void) | undefined;\n}\n\ntype EventCallback = (event: Record<string, unknown>) => void;\n\ninterface ActiveSubscription {\n serverId: string;\n readonly params: Record<string, unknown>;\n readonly callback: EventCallback;\n readonly handle: MutableGdexSubscription;\n active: boolean;\n}\n\ninterface MutableGdexSubscription extends GdexSubscription {\n subscriptionId: string;\n}\n\nfunction readSubscriptionId(value: unknown): string {\n const id =\n typeof value === 'object' && value !== null\n ? (value as { subscriptionId?: unknown }).subscriptionId\n : undefined;\n if (typeof id !== 'string') {\n throw new GdexStreamError(-32603, 'GDEX subscribe response is missing its subscriptionId');\n }\n return id;\n}\n\ninterface JsonRpcResponse {\n id?: unknown;\n method?: unknown;\n result?: unknown;\n error?: { code?: unknown; message?: unknown; data?: unknown };\n params?: unknown;\n}\n\n/**\n * Client for the gSwap realtime stream (gdex WebSocket Protocol v1): JSON-RPC 2.0 over one raw\n * WebSocket, with automatic reconnect and resubscribe.\n *\n * @example\n * ```typescript\n * const stream = new GdexStreamService();\n * await stream.connect();\n * await stream.subscribeLiquidity(event => console.log(event.action, event.owner), {\n * owner: 'client|618ae395c1c653111d3315be',\n * });\n * ```\n */\nexport class GdexStreamService {\n private readonly url: string;\n private readonly requestTimeoutMs: number;\n private readonly apiKey: string | undefined;\n private readonly webSocketConstructor: WebSocketConstructor;\n private readonly backoff: ReconnectionBackoff;\n private socket: WebSocketLike | undefined;\n private connectPromise: Promise<void> | undefined;\n private nextRequestId = 0;\n private readonly pending = new Map<number, PendingRequest>();\n private readonly subscriptionsByServerId = new Map<string, ActiveSubscription>();\n private readonly activeSubscriptions = new Set<ActiveSubscription>();\n private intentionalClose = false;\n private reconnecting = false;\n private reconnectTimer: ReturnType<typeof setTimeout> | undefined;\n private reconnectDelayResolve: (() => void) | undefined;\n\n constructor(config: GdexStreamConfig = {}) {\n this.url = config.url ?? GSWAP_ENVIRONMENTS.prod.dexBackendWsUrl;\n this.requestTimeoutMs = config.requestTimeoutMs ?? 15_000;\n this.backoff = new ReconnectionBackoff({\n maxAttempts: config.reconnectAttempts ?? 5,\n baseDelayMs: config.reconnectDelayMs ?? 2_000,\n });\n\n const implementation =\n config.webSocketImpl ?? (globalThis as { WebSocket?: unknown }).WebSocket;\n if (typeof implementation !== 'function') {\n throw new GSwapSDKError('STREAM_WEBSOCKET_UNAVAILABLE', {});\n }\n this.webSocketConstructor = implementation as WebSocketConstructor;\n if (config.apiKey !== undefined) {\n if (config.apiKey === '' || config.webSocketImpl === undefined) {\n throw new GSwapSDKError('STREAM_API_KEY_UNUSABLE', {});\n }\n this.apiKey = config.apiKey;\n }\n }\n\n /** Open the connection. Resolves at once when already connected. */\n connect(): Promise<void> {\n if (this.isConnected()) return Promise.resolve();\n if (this.connectPromise) return this.connectPromise;\n this.intentionalClose = false;\n this.clearReconnectTimer();\n this.connectPromise = this.openSocket();\n return this.connectPromise;\n }\n\n /** Close the connection and stop reconnecting. */\n disconnect(): void {\n this.intentionalClose = true;\n this.reconnecting = false;\n this.clearReconnectTimer();\n this.rejectPending(new GSwapSDKError('STREAM_DISCONNECTED', {}));\n const socket = this.socket;\n this.socket = undefined;\n this.connectPromise = undefined;\n socket?.close(1000, 'client disconnect');\n }\n\n /** `true` while the socket is open (false while connecting, reconnecting or after `disconnect()`). */\n isConnected(): boolean {\n return this.socket?.readyState === WEBSOCKET_OPEN;\n }\n\n /** Raw JSON-RPC escape hatch. */\n request<T = unknown>(method: string, params?: Record<string, unknown>): Promise<T> {\n return this.send<T>(method, params);\n }\n\n private send<T>(\n method: string,\n params: Record<string, unknown> | undefined,\n onResult?: (value: unknown) => void,\n ): Promise<T> {\n if (!this.isConnected() || !this.socket) {\n return Promise.reject(new GSwapSDKError('STREAM_NOT_CONNECTED', {}));\n }\n\n const id = ++this.nextRequestId;\n const frame: Record<string, unknown> = { jsonrpc: '2.0', id, method };\n if (params !== undefined) frame['params'] = params;\n\n return new Promise<T>((resolve, reject) => {\n const timeout = setTimeout(() => {\n this.pending.delete(id);\n reject(\n new GSwapSDKError('STREAM_REQUEST_TIMEOUT', { method, timeoutMs: this.requestTimeoutMs }),\n );\n }, this.requestTimeoutMs);\n this.pending.set(id, { resolve: (value) => resolve(value as T), reject, timeout, onResult });\n\n try {\n this.socket?.send(JSON.stringify(frame));\n } catch (error) {\n clearTimeout(timeout);\n this.pending.delete(id);\n reject(error instanceof Error ? error : new Error(String(error)));\n }\n });\n }\n\n /** `gdex_serverInfo`: the server's own description of itself, as it sends it. */\n serverInfo(): Promise<Record<string, unknown>> {\n return this.request('gdex_serverInfo');\n }\n\n /** `gdex_ping`: a round trip that answers `{ pong, serverTime }`. */\n ping(): Promise<{ pong: boolean; serverTime: string }> {\n return this.request('gdex_ping');\n }\n\n /** `gdex_getPool`: the current state of one pool, selected by its key or hash. */\n getPool(selector: GdexPoolSelector): Promise<Record<string, unknown>> {\n return this.request('gdex_getPool', selector);\n }\n\n /** `gdex_getBalance`: an owner's balances, or only the one of `tokenClassKey` when given. */\n getBalance(owner: string, tokenClassKey?: string): Promise<Record<string, unknown>> {\n return this.request(\n 'gdex_getBalance',\n tokenClassKey === undefined ? { owner } : { owner, tokenClassKey },\n );\n }\n\n /**\n * `gdex_getPositions`: an owner's positions, optionally in one pool, a page at a time. Pass the\n * returned `bookmark` back in as `bookmark` for the next page.\n */\n getPositions(opts: {\n owner: string;\n pool?: GdexPoolKey;\n poolHash?: string;\n limit?: number;\n bookmark?: string;\n }): Promise<{ positions: Record<string, unknown>[]; bookmark: string }> {\n return this.request('gdex_getPositions', opts);\n }\n\n /** `gdex_getBlockHeight`: the block number the server reports, with its timestamp. */\n getBlockHeight(): Promise<{ blockNumber: number; timestamp: string }> {\n return this.request('gdex_getBlockHeight');\n }\n\n /** `gdex_quote`: a quote over the stream; give exactly one of `amountIn` or `amountOut`. */\n quote(params: {\n tokenIn: string;\n tokenOut: string;\n fee?: number;\n amountIn?: string;\n amountOut?: string;\n }): Promise<Record<string, unknown>> {\n return this.request('gdex_quote', params);\n }\n\n /** `gdex_getToken`: the server's record of one token class, as it sends it. */\n getToken(tokenClassKey: string): Promise<Record<string, unknown>> {\n return this.request('gdex_getToken', { tokenClassKey });\n }\n\n /** One event per committed swap. Omit the pool to receive every pool's swaps. */\n subscribeSwaps(\n cb: (event: Record<string, unknown>) => void,\n opts: GdexSubscribeOptions & {\n pool?: GdexPoolKey;\n poolHash?: string;\n transactionId?: string;\n } = {},\n ): Promise<GdexSubscription> {\n return this.subscribe('swap', cb, opts);\n }\n\n /**\n * State changes of one pool (price, tick, reserves, liquidity). These events carry no acting wallet:\n * on v2 `creator` is whoever created the pool, not whoever just traded or added liquidity. For\n * \"who did it\", use {@link subscribeLiquidity} (`owner`) or {@link subscribeSwaps} (`sender`).\n */\n subscribePool(\n selector: GdexPoolSelector,\n cb: (event: Record<string, unknown>) => void,\n opts: GdexSubscribeOptions = {},\n ): Promise<GdexSubscription> {\n return this.subscribe('pool', cb, { ...selector, ...opts });\n }\n\n /** Balance changes of one wallet. */\n subscribeBalances(\n owner: string,\n cb: (event: Record<string, unknown>) => void,\n opts: GdexSubscribeOptions & { tokenClassKey?: string } = {},\n ): Promise<GdexSubscription> {\n return this.subscribe('balance', cb, { owner, ...opts });\n }\n\n /** Each newly created pool. */\n subscribePoolCreated(\n cb: (event: Record<string, unknown>) => void,\n opts: GdexSubscribeOptions & { tokenClassKey?: string } = {},\n ): Promise<GdexSubscription> {\n return this.subscribe('poolCreated', cb, opts);\n }\n\n /**\n * One event per committed AddLiquidity, RemoveLiquidity and CollectPositionFees, on both contract\n * versions. Every filter is optional; with none, every liquidity event arrives. These events never\n * appear on {@link subscribeSwaps}. There is no snapshot: read positions with {@link getPositions}.\n * Deduplicate by `transactionId` plus `positionId`: a v1 BatchSubmit can hold several operations\n * under one transaction id.\n */\n subscribeLiquidity(\n cb: (event: GdexLiquidityEvent) => void,\n opts: GdexSubscribeOptions & GdexLiquidityFilter = {},\n ): Promise<GdexSubscription> {\n return this.subscribe(\n 'liquidity',\n (event) => {\n if (isGdexLiquidityEvent(event)) cb(event);\n else debugLog('Ignoring a malformed liquidity event from the GDEX stream');\n },\n opts,\n );\n }\n\n /** Each block-height advance. */\n subscribeBlocks(\n cb: (event: Record<string, unknown>) => void,\n opts: GdexSubscribeOptions = {},\n ): Promise<GdexSubscription> {\n return this.subscribe('blocks', cb, opts);\n }\n\n private openSocket(): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n let opened = false;\n const socket =\n this.apiKey === undefined\n ? new this.webSocketConstructor(this.url, GDEX_SUBPROTOCOL)\n : new this.webSocketConstructor(this.url, GDEX_SUBPROTOCOL, {\n headers: { 'x-api-key': this.apiKey },\n });\n this.socket = socket;\n\n socket.onopen = () => {\n if (this.socket !== socket) return;\n opened = true;\n this.connectPromise = undefined;\n if (!this.reconnecting) this.backoff.reset();\n debugLog('Connected to GDEX stream');\n resolve();\n };\n socket.onmessage = (event) => this.handleMessage(event.data);\n socket.onerror = () => {\n if (!opened) reject(new GSwapSDKError('STREAM_CONNECT_FAILED', { url: this.url }));\n };\n socket.onclose = (event) => {\n if (this.socket !== socket) return;\n this.socket = undefined;\n this.connectPromise = undefined;\n if (!opened) {\n reject(new GSwapSDKError('STREAM_CLOSED_BEFORE_CONNECT', { closeCode: event.code ?? 0 }));\n }\n this.rejectPending(\n new GSwapSDKError('STREAM_CONNECTION_CLOSED', { closeCode: event.code ?? 0 }),\n );\n if (!this.intentionalClose && !this.reconnecting) this.startReconnectLoop();\n };\n });\n }\n\n private handleMessage(data: unknown): void {\n if (typeof data !== 'string') {\n debugLog('Ignoring non-text GDEX WebSocket frame');\n return;\n }\n\n let message: JsonRpcResponse;\n try {\n message = JSON.parse(data) as JsonRpcResponse;\n } catch {\n debugLog('Ignoring invalid JSON from GDEX stream');\n return;\n }\n\n if (typeof message.id === 'number') {\n const pending = this.pending.get(message.id);\n if (!pending) return;\n clearTimeout(pending.timeout);\n this.pending.delete(message.id);\n if (message.error) {\n const code = typeof message.error.code === 'number' ? message.error.code : -32603;\n const errorMessage =\n typeof message.error.message === 'string' ? message.error.message : 'GDEX request failed';\n pending.reject(new GdexStreamError(code, errorMessage, message.error.data));\n } else {\n try {\n pending.onResult?.(message.result);\n } catch (error) {\n pending.reject(error instanceof Error ? error : new Error(String(error)));\n return;\n }\n pending.resolve(message.result);\n }\n return;\n }\n\n if (message.method !== 'gdex_subscription' || !this.isRecord(message.params)) return;\n const subscriptionId = message.params['subscription'];\n const result = message.params['result'];\n if (typeof subscriptionId !== 'string' || !this.isRecord(result)) return;\n const subscription = this.subscriptionsByServerId.get(subscriptionId);\n if (!subscription?.active) return;\n try {\n subscription.callback(result);\n } catch (error) {\n debugLog('GDEX subscription callback failed', error);\n }\n }\n\n private async subscribe(\n channel: GdexChannel,\n callback: EventCallback,\n options: object,\n ): Promise<GdexSubscription> {\n const params: Record<string, unknown> = { channel, ...options };\n const handle: MutableGdexSubscription = {\n subscriptionId: '',\n unsubscribe: async () => this.unsubscribe(subscription),\n };\n const subscription: ActiveSubscription = {\n serverId: '',\n params,\n callback,\n handle,\n active: true,\n };\n // Registered inside the response handler, not after the await: see PendingRequest.onResult.\n await this.send('gdex_subscribe', params, (value) => {\n const serverId = readSubscriptionId(value);\n subscription.serverId = serverId;\n handle.subscriptionId = serverId;\n this.activeSubscriptions.add(subscription);\n this.subscriptionsByServerId.set(serverId, subscription);\n });\n return handle;\n }\n\n private async unsubscribe(subscription: ActiveSubscription): Promise<void> {\n if (!subscription.active) return;\n subscription.active = false;\n this.activeSubscriptions.delete(subscription);\n this.subscriptionsByServerId.delete(subscription.serverId);\n if (this.isConnected()) {\n await this.request('gdex_unsubscribe', { subscriptionId: subscription.serverId });\n }\n }\n\n private startReconnectLoop(): void {\n if (this.reconnecting || this.intentionalClose) return;\n this.reconnecting = true;\n void this.reconnectLoop();\n }\n\n private async reconnectLoop(): Promise<void> {\n while (!this.intentionalClose && this.backoff.shouldRetry()) {\n const delay = this.backoff.nextDelayMs();\n this.backoff.recordAttempt();\n debugLog(\n `GDEX reconnect attempt ${this.backoff.attemptCount}/${this.backoff.maxAttempts} in ${delay}ms`,\n );\n await this.waitForReconnectDelay(delay);\n // `disconnect()` can run while the asynchronous delay is pending.\n if (this.intentionalClose) break;\n\n try {\n await this.openSocket();\n await this.resubscribeAll();\n this.backoff.reset();\n this.reconnecting = false;\n return;\n } catch (error) {\n debugLog(\n `GDEX reconnect failed: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n this.reconnecting = false;\n if (!this.intentionalClose) debugLog('GDEX reconnection attempts exhausted');\n }\n\n private async resubscribeAll(): Promise<void> {\n this.subscriptionsByServerId.clear();\n for (const subscription of this.activeSubscriptions) {\n if (!subscription.active) continue;\n const params = { ...subscription.params, snapshot: true };\n const result = await this.resubscribeWithRateLimitRetry(params, (value) => {\n const serverId = readSubscriptionId(value);\n // The consumer can unsubscribe while the asynchronous subscribe request is pending.\n if (!subscription.active) return;\n subscription.serverId = serverId;\n subscription.handle.subscriptionId = serverId;\n this.subscriptionsByServerId.set(serverId, subscription);\n });\n if (!subscription.active) {\n await this.request('gdex_unsubscribe', { subscriptionId: result.subscriptionId });\n }\n }\n }\n\n private async resubscribeWithRateLimitRetry(\n params: Record<string, unknown>,\n onResult: (value: unknown) => void,\n ): Promise<{ subscriptionId: string }> {\n for (let attempt = 0; ; attempt++) {\n try {\n return await this.send('gdex_subscribe', params, onResult);\n } catch (error) {\n if (\n !(error instanceof GdexStreamError) ||\n error.code !== RATE_LIMITED ||\n attempt >= MAX_RESUBSCRIBE_RATE_RETRIES\n ) {\n throw error;\n }\n const retryAfterMs = this.readRetryAfterMs(error.data);\n debugLog(`GDEX resubscribe rate limited; retrying in ${retryAfterMs}ms`);\n await new Promise<void>((resolve) => setTimeout(resolve, retryAfterMs));\n }\n }\n }\n\n private waitForReconnectDelay(delay: number): Promise<void> {\n return new Promise((resolve) => {\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = undefined;\n this.reconnectDelayResolve = undefined;\n resolve();\n }, delay);\n this.reconnectDelayResolve = resolve;\n });\n }\n\n private clearReconnectTimer(): void {\n if (this.reconnectTimer !== undefined) clearTimeout(this.reconnectTimer);\n this.reconnectTimer = undefined;\n const resolve = this.reconnectDelayResolve;\n this.reconnectDelayResolve = undefined;\n resolve?.();\n }\n\n private rejectPending(error: Error): void {\n for (const pending of this.pending.values()) {\n clearTimeout(pending.timeout);\n pending.reject(error);\n }\n this.pending.clear();\n }\n\n private readRetryAfterMs(data: unknown): number {\n if (\n !this.isRecord(data) ||\n typeof data['retryAfterMs'] !== 'number' ||\n data['retryAfterMs'] < 0\n )\n return 1_000;\n return data['retryAfterMs'];\n }\n\n private isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"gswap.js","names":[],"sources":["../../../src/classes/gswap.ts"],"sourcesContent":["import { Assets } from './assets.js';\nimport { DexFees } from './dex_fees.js';\nimport { ChainGateway } from './gateway.js';\nimport { GdexStreamService } from './gdex_stream.js';\nimport { HttpClient } from './http_client.js';\nimport { Pools } from './pools.js';\nimport { Positions } from './positions.js';\nimport { Quoting } from './quoting.js';\nimport type { GalaChainSigner } from './signers.js';\nimport { CanonicalSymbols, Symbols } from './symbols.js';\nimport type { InvalidEntryHandler } from '../utils/lenient.js';\nimport { Swaps } from './swaps.js';\nimport type { HttpRequestor } from '../types/http_requestor.js';\nimport type { GSwapEnv } from '../types/env.js';\nimport type { WireOptions } from '../types/wire.js';\nimport { GSWAP_ENVIRONMENTS } from '../types/env.js';\n\n/** Options for constructing a backend-routed SDK client. */\nexport interface GSwapOptions {\n signer?: GalaChainSigner | undefined;\n walletAddress?: string | undefined;\n env?: GSwapEnv | undefined;\n dexBackendBaseUrl?: string | undefined;\n httpRequestor?: HttpRequestor | undefined;\n /** Request timeout in milliseconds (default 30 000); `Infinity` arms no timer. */\n chainCallTimeoutMs?: number | undefined;\n /** Query encoding and `User-Agent` of the read requests (see {@link WireOptions}). */\n wire?: WireOptions | undefined;\n /**\n * How tokens are turned into trading symbols. `'registry'` (default) reads the symbol registry\n * once (`GET /v2/trade/symbols`, cached) and accepts symbols or class keys. `'canonical'` makes\n * no registry request: the caller passes the backend's canonical trading symbols (from a quote,\n * an estimate or a position), reads send tokens and ticks exactly as given (composite keys\n * included) and the backend orients the pair; see {@link CanonicalSymbols}.\n */\n symbolResolution?: 'registry' | 'canonical' | undefined;\n /**\n * Told about every row of a pool, symbol or position list that was skipped as unusable (see\n * {@link InvalidEntryHandler}); without it such rows are skipped silently.\n */\n onInvalidEntry?: InvalidEntryHandler | undefined;\n /** Realtime stream endpoint. Defaults to the environment's, or is derived from `dexBackendBaseUrl`. */\n dexBackendWsUrl?: string | undefined;\n /** WebSocket constructor for the realtime stream; required on Node.js older than 22. */\n webSocketImpl?: unknown;\n /**\n * `x-api-key` for the privileged stream view; server-side only, and it requires `webSocketImpl`\n * (see `GdexStreamConfig.apiKey`).\n */\n streamApiKey?: string | undefined;\n /**\n * Prefix of every write's `uniqueKey` (`<prefix><uuid>`). Defaults to `galaswap-operation-`,\n * what frontend_v2 and wallet_extension send; defi-backend does not depend on it.\n */\n uniqueKeyPrefix?: string | undefined;\n}\n\n/** Main entry point for the current GalaChainDex SDK. */\nexport class GSwap {\n public readonly dexBackendBaseUrl: string;\n public readonly chainCallTimeoutMs: number;\n public readonly signer: GalaChainSigner | undefined;\n public readonly walletAddress: string | undefined;\n public readonly gateway: ChainGateway;\n public readonly symbols: Symbols;\n public readonly quoting: Quoting;\n public readonly pools: Pools;\n public readonly positions: Positions;\n public readonly swaps: Swaps;\n public readonly assets: Assets;\n /** Network-fee quotes (DryRun) signed with this client's signer. */\n public readonly fees: DexFees;\n public readonly dexBackendWsUrl: string;\n private readonly webSocketImpl: unknown;\n private readonly streamApiKey: string | undefined;\n private streamClient: GdexStreamService | undefined;\n\n /**\n * Create an SDK client for prod or stage.\n *\n * @example\n * ```typescript\n * const gSwap = new GSwap({ env: 'stage' });\n * const symbols = await gSwap.symbols.list();\n * ```\n */\n constructor(options?: GSwapOptions) {\n const environment = GSWAP_ENVIRONMENTS[options?.env ?? 'prod'];\n this.dexBackendBaseUrl = trimTrailingSlash(\n options?.dexBackendBaseUrl ?? environment.dexBackendBaseUrl,\n );\n this.chainCallTimeoutMs = options?.chainCallTimeoutMs ?? 30_000;\n this.signer = options?.signer;\n this.walletAddress = options?.walletAddress;\n\n const httpClient = new HttpClient(\n options?.httpRequestor,\n this.chainCallTimeoutMs,\n options?.wire,\n );\n this.gateway = new ChainGateway({\n dexBackendBaseUrl: this.dexBackendBaseUrl,\n chainCallTimeoutMs: this.chainCallTimeoutMs,\n ...(options?.wire === undefined ? {} : { wire: options.wire }),\n ...(options?.httpRequestor === undefined ? {} : { httpRequestor: options.httpRequestor }),\n ...(this.walletAddress === undefined ? {} : { walletAddress: this.walletAddress }),\n });\n const lenient = { onInvalidEntry: options?.onInvalidEntry };\n this.symbols = new Symbols(this.gateway, lenient);\n // The services resolve tokens through the registry by default; the offline resolver sends the\n // caller's canonical symbols and keys as they are and never reads the registry.\n const resolver =\n options?.symbolResolution === 'canonical' ? new CanonicalSymbols() : this.symbols;\n this.quoting = new Quoting(this.gateway, resolver);\n this.pools = new Pools(this.gateway, resolver, lenient);\n const keyOptions = { uniqueKeyPrefix: options?.uniqueKeyPrefix, ...lenient };\n this.positions = new Positions(\n this.gateway,\n resolver,\n this.signer,\n this.walletAddress,\n keyOptions,\n );\n this.swaps = new Swaps(this.gateway, resolver, this.signer, this.walletAddress, keyOptions);\n this.assets = new Assets(this.dexBackendBaseUrl, httpClient);\n this.fees = new DexFees(this.gateway, this.signer);\n this.dexBackendWsUrl =\n options?.dexBackendWsUrl ??\n (options?.dexBackendBaseUrl === undefined\n ? environment.dexBackendWsUrl\n : `${this.dexBackendBaseUrl.replace(/^http/u, 'ws')}/ws`);\n this.webSocketImpl = options?.webSocketImpl;\n this.streamApiKey = options?.streamApiKey;\n }\n\n /**\n * The realtime stream client for this environment (created on first use, not connected until you\n * call `connect()`).\n *\n * @example\n * ```typescript\n * const gSwap = new GSwap({ env: 'stage' });\n * await gSwap.stream.connect();\n * await gSwap.stream.subscribeLiquidity((event) => console.log(event.action, event.amount0));\n * ```\n */\n get stream(): GdexStreamService {\n this.streamClient ??= new GdexStreamService({\n url: this.dexBackendWsUrl,\n ...(this.webSocketImpl === undefined ? {} : { webSocketImpl: this.webSocketImpl }),\n ...(this.streamApiKey === undefined ? {} : { apiKey: this.streamApiKey }),\n });\n return this.streamClient;\n }\n}\n\nfunction trimTrailingSlash(value: string): string {\n return value.replace(/\\/$/u, '');\n}\n"],"mappings":"6jBA0DA,IAAa,EAAb,KAAmB,CA4BjB,YAAY,EAAwB,CA3BpB,EAAA,KAAA,oBAAA,IAAA,EAAA,EACA,EAAA,KAAA,qBAAA,IAAA,EAAA,EACA,EAAA,KAAA,SAAA,IAAA,EAAA,EACA,EAAA,KAAA,gBAAA,IAAA,EAAA,EACA,EAAA,KAAA,UAAA,IAAA,EAAA,EACA,EAAA,KAAA,UAAA,IAAA,EAAA,EACA,EAAA,KAAA,UAAA,IAAA,EAAA,EACA,EAAA,KAAA,QAAA,IAAA,EAAA,EACA,EAAA,KAAA,YAAA,IAAA,EAAA,EACA,EAAA,KAAA,QAAA,IAAA,EAAA,EACA,EAAA,KAAA,SAAA,IAAA,EAAA,EAEA,EAAA,KAAA,OAAA,IAAA,EAAA,EACA,EAAA,KAAA,kBAAA,IAAA,EAAA,EACC,EAAA,KAAA,gBAAA,IAAA,EAAA,EACA,EAAA,KAAA,eAAA,IAAA,EAAA,EACT,EAAA,KAAA,eAAA,IAAA,EAAA,EAYN,IAAM,EAAc,EAAmB,GAAS,KAAO,QACvD,KAAK,kBAAoB,EACvB,GAAS,mBAAqB,EAAY,iBAC5C,EACA,KAAK,mBAAqB,GAAS,oBAAsB,IACzD,KAAK,OAAS,GAAS,OACvB,KAAK,cAAgB,GAAS,cAE9B,IAAM,EAAa,IAAI,EACrB,GAAS,cACT,KAAK,mBACL,GAAS,IACX,EACA,KAAK,QAAU,IAAI,EAAa,CAC9B,kBAAmB,KAAK,kBACxB,mBAAoB,KAAK,mBACzB,GAAI,GAAS,OAAS,IAAA,GAAY,CAAC,EAAI,CAAE,KAAM,EAAQ,IAAK,EAC5D,GAAI,GAAS,gBAAkB,IAAA,GAAY,CAAC,EAAI,CAAE,cAAe,EAAQ,aAAc,EACvF,GAAI,KAAK,gBAAkB,IAAA,GAAY,CAAC,EAAI,CAAE,cAAe,KAAK,aAAc,CAClF,CAAC,EACD,IAAM,EAAU,CAAE,eAAgB,GAAS,cAAe,EAC1D,KAAK,QAAU,IAAI,EAAQ,KAAK,QAAS,CAAO,EAGhD,IAAM,EACJ,GAAS,mBAAqB,YAAc,IAAI,EAAqB,KAAK,QAC5E,KAAK,QAAU,IAAI,EAAQ,KAAK,QAAS,CAAQ,EACjD,KAAK,MAAQ,IAAI,EAAM,KAAK,QAAS,EAAU,CAAO,EACtD,IAAM,EAAa,CAAE,gBAAiB,GAAS,gBAAiB,GAAG,CAAQ,EAC3E,KAAK,UAAY,IAAI,EACnB,KAAK,QACL,EACA,KAAK,OACL,KAAK,cACL,CACF,EACA,KAAK,MAAQ,IAAI,EAAM,KAAK,QAAS,EAAU,KAAK,OAAQ,KAAK,cAAe,CAAU,EAC1F,KAAK,OAAS,IAAI,EAAO,KAAK,kBAAmB,CAAU,EAC3D,KAAK,KAAO,IAAI,EAAQ,KAAK,QAAS,KAAK,MAAM,EACjD,KAAK,gBACH,GAAS,kBACR,GAAS,oBAAsB,IAAA,GAC5B,EAAY,gBACZ,GAAG,KAAK,kBAAkB,QAAQ,SAAU,IAAI,EAAE,MACxD,KAAK,cAAgB,GAAS,cAC9B,KAAK,aAAe,GAAS,YAC/B,CAaA,IAAI,QAA4B,CAM9B,OALA,KAAK,eAAL,KAAK,aAAiB,IAAI,EAAkB,CAC1C,IAAK,KAAK,gBACV,GAAI,KAAK,gBAAkB,IAAA,GAAY,CAAC,EAAI,CAAE,cAAe,KAAK,aAAc,EAChF,GAAI,KAAK,eAAiB,IAAA,GAAY,CAAC,EAAI,CAAE,OAAQ,KAAK,YAAa,CACzE,CAAC,GACM,KAAK,YACd,CACF,EAEA,SAAS,EAAkB,EAAuB,CAChD,OAAO,EAAM,QAAQ,OAAQ,EAAE,CACjC"}
1
+ {"version":3,"file":"gswap.js","names":[],"sources":["../../../src/classes/gswap.ts"],"sourcesContent":["import { Assets } from './assets.js';\nimport { DexFees } from './dex_fees.js';\nimport { ChainGateway } from './gateway.js';\nimport { GdexStreamService } from './gdex_stream.js';\nimport { HttpClient } from './http_client.js';\nimport { Pools } from './pools.js';\nimport { Positions } from './positions.js';\nimport { Quoting } from './quoting.js';\nimport type { GalaChainSigner } from './signers.js';\nimport { CanonicalSymbols, Symbols } from './symbols.js';\nimport type { InvalidEntryHandler } from '../utils/lenient.js';\nimport { Swaps } from './swaps.js';\nimport type { HttpRequestor } from '../types/http_requestor.js';\nimport type { GSwapEnv } from '../types/env.js';\nimport type { WireOptions } from '../types/wire.js';\nimport { GSWAP_ENVIRONMENTS } from '../types/env.js';\n\n/** Options for constructing a backend-routed SDK client. */\nexport interface GSwapOptions {\n /** Signs every write. Without one, reads work and any write throws `NO_SIGNER`. */\n signer?: GalaChainSigner | undefined;\n /**\n * The selected account, sent as `X-Wallet-Address` on writes (attribution only; the chain\n * recovers the signer from the signature). Required for shadow writes.\n */\n walletAddress?: string | undefined;\n /** `'stage'` or `'prod'` swap backend preset; defaults to `'prod'`. */\n env?: GSwapEnv | undefined;\n /** Overrides the preset's swap backend base URL (a test or private deployment). */\n dexBackendBaseUrl?: string | undefined;\n /** HTTP transport for every request; defaults to the global `fetch`. */\n httpRequestor?: HttpRequestor | undefined;\n /** Request timeout in milliseconds (default 30 000); `Infinity` arms no timer. */\n chainCallTimeoutMs?: number | undefined;\n /** Query encoding and `User-Agent` of the read requests (see {@link WireOptions}). */\n wire?: WireOptions | undefined;\n /**\n * How tokens are turned into trading symbols. `'registry'` (default) reads the symbol registry\n * once (`GET /v2/trade/symbols`, cached) and accepts symbols or class keys. `'canonical'` makes\n * no registry request: the caller passes the backend's canonical trading symbols (from a quote,\n * an estimate or a position), reads send tokens and ticks exactly as given (composite keys\n * included) and the backend orients the pair; see {@link CanonicalSymbols}.\n */\n symbolResolution?: 'registry' | 'canonical' | undefined;\n /**\n * Told about every row of a pool, symbol or position list that was skipped as unusable (see\n * {@link InvalidEntryHandler}); without it such rows are skipped silently.\n */\n onInvalidEntry?: InvalidEntryHandler | undefined;\n /** Realtime stream endpoint. Defaults to the environment's, or is derived from `dexBackendBaseUrl`. */\n dexBackendWsUrl?: string | undefined;\n /** WebSocket constructor for the realtime stream; required on Node.js older than 22. */\n webSocketImpl?: unknown;\n /**\n * `x-api-key` for the privileged stream view; server-side only, and it requires `webSocketImpl`\n * (see `GdexStreamConfig.apiKey`).\n */\n streamApiKey?: string | undefined;\n /**\n * Prefix of every write's `uniqueKey` (`<prefix><uuid>`). Defaults to `galaswap-operation-`,\n * what frontend_v2 and wallet_extension send; defi-backend does not depend on it.\n */\n uniqueKeyPrefix?: string | undefined;\n}\n\n/**\n * Main entry point for the current GalaChainDex SDK: one client bundling the services that read\n * and write through the swap backend (`quoting`, `swaps`, `pools`, `positions`, `assets`,\n * `symbols`, `fees`, `stream`). See {@link GSwapOptions} for configuration.\n */\nexport class GSwap {\n /** The swap backend base URL every request goes to, without a trailing slash. */\n public readonly dexBackendBaseUrl: string;\n /** The request timeout in milliseconds (default 30 000; `Infinity` arms no timer). */\n public readonly chainCallTimeoutMs: number;\n /** The signer given at construction, if any. */\n public readonly signer: GalaChainSigner | undefined;\n /** The account given at construction, if any. */\n public readonly walletAddress: string | undefined;\n /** The chain-gateway client every other service reads and writes through. */\n public readonly gateway: ChainGateway;\n /** The trading-symbol registry. */\n public readonly symbols: Symbols;\n /** Read-only exact-input and exact-output quotes. */\n public readonly quoting: Quoting;\n /** Pool, slot0 and composite-pool reads. */\n public readonly pools: Pools;\n /** Position reads, liquidity estimates and liquidity / pool-creation writes. */\n public readonly positions: Positions;\n /** Queued swaps (`RequestTrade`), dark swaps and swap confirmation. */\n public readonly swaps: Swaps;\n /** User token balances. */\n public readonly assets: Assets;\n /** Network-fee quotes (DryRun) signed with this client's signer. */\n public readonly fees: DexFees;\n /** The realtime stream endpoint {@link GSwap.stream} connects to. */\n public readonly dexBackendWsUrl: string;\n private readonly webSocketImpl: unknown;\n private readonly streamApiKey: string | undefined;\n private streamClient: GdexStreamService | undefined;\n\n /**\n * Create an SDK client for prod (the default) or stage, or for a `dexBackendBaseUrl` of your own.\n *\n * @example\n * ```typescript\n * const gSwap = new GSwap({ env: 'stage' });\n * const symbols = await gSwap.symbols.list();\n * ```\n */\n constructor(options?: GSwapOptions) {\n const environment = GSWAP_ENVIRONMENTS[options?.env ?? 'prod'];\n this.dexBackendBaseUrl = trimTrailingSlash(\n options?.dexBackendBaseUrl ?? environment.dexBackendBaseUrl,\n );\n this.chainCallTimeoutMs = options?.chainCallTimeoutMs ?? 30_000;\n this.signer = options?.signer;\n this.walletAddress = options?.walletAddress;\n\n const httpClient = new HttpClient(\n options?.httpRequestor,\n this.chainCallTimeoutMs,\n options?.wire,\n );\n this.gateway = new ChainGateway({\n dexBackendBaseUrl: this.dexBackendBaseUrl,\n chainCallTimeoutMs: this.chainCallTimeoutMs,\n ...(options?.wire === undefined ? {} : { wire: options.wire }),\n ...(options?.httpRequestor === undefined ? {} : { httpRequestor: options.httpRequestor }),\n ...(this.walletAddress === undefined ? {} : { walletAddress: this.walletAddress }),\n });\n const lenient = { onInvalidEntry: options?.onInvalidEntry };\n this.symbols = new Symbols(this.gateway, lenient);\n // The services resolve tokens through the registry by default; the offline resolver sends the\n // caller's canonical symbols and keys as they are and never reads the registry.\n const resolver =\n options?.symbolResolution === 'canonical' ? new CanonicalSymbols() : this.symbols;\n this.quoting = new Quoting(this.gateway, resolver);\n this.pools = new Pools(this.gateway, resolver, lenient);\n const keyOptions = { uniqueKeyPrefix: options?.uniqueKeyPrefix, ...lenient };\n this.positions = new Positions(\n this.gateway,\n resolver,\n this.signer,\n this.walletAddress,\n keyOptions,\n );\n this.swaps = new Swaps(this.gateway, resolver, this.signer, this.walletAddress, keyOptions);\n this.assets = new Assets(this.dexBackendBaseUrl, httpClient);\n this.fees = new DexFees(this.gateway, this.signer);\n this.dexBackendWsUrl =\n options?.dexBackendWsUrl ??\n (options?.dexBackendBaseUrl === undefined\n ? environment.dexBackendWsUrl\n : `${this.dexBackendBaseUrl.replace(/^http/u, 'ws')}/ws`);\n this.webSocketImpl = options?.webSocketImpl;\n this.streamApiKey = options?.streamApiKey;\n }\n\n /**\n * The realtime stream client for this environment (created on first use, not connected until you\n * call `connect()`).\n *\n * @example\n * ```typescript\n * const gSwap = new GSwap({ env: 'stage' });\n * await gSwap.stream.connect();\n * await gSwap.stream.subscribeLiquidity((event) => console.log(event.action, event.amount0));\n * ```\n */\n get stream(): GdexStreamService {\n this.streamClient ??= new GdexStreamService({\n url: this.dexBackendWsUrl,\n ...(this.webSocketImpl === undefined ? {} : { webSocketImpl: this.webSocketImpl }),\n ...(this.streamApiKey === undefined ? {} : { apiKey: this.streamApiKey }),\n });\n return this.streamClient;\n }\n}\n\nfunction trimTrailingSlash(value: string): string {\n return value.replace(/\\/$/u, '');\n}\n"],"mappings":"6jBAsEA,IAAa,EAAb,KAAmB,CAwCjB,YAAY,EAAwB,CAtCpB,EAAA,KAAA,oBAAA,IAAA,EAAA,EAEA,EAAA,KAAA,qBAAA,IAAA,EAAA,EAEA,EAAA,KAAA,SAAA,IAAA,EAAA,EAEA,EAAA,KAAA,gBAAA,IAAA,EAAA,EAEA,EAAA,KAAA,UAAA,IAAA,EAAA,EAEA,EAAA,KAAA,UAAA,IAAA,EAAA,EAEA,EAAA,KAAA,UAAA,IAAA,EAAA,EAEA,EAAA,KAAA,QAAA,IAAA,EAAA,EAEA,EAAA,KAAA,YAAA,IAAA,EAAA,EAEA,EAAA,KAAA,QAAA,IAAA,EAAA,EAEA,EAAA,KAAA,SAAA,IAAA,EAAA,EAEA,EAAA,KAAA,OAAA,IAAA,EAAA,EAEA,EAAA,KAAA,kBAAA,IAAA,EAAA,EACC,EAAA,KAAA,gBAAA,IAAA,EAAA,EACA,EAAA,KAAA,eAAA,IAAA,EAAA,EACT,EAAA,KAAA,eAAA,IAAA,EAAA,EAYN,IAAM,EAAc,EAAmB,GAAS,KAAO,QACvD,KAAK,kBAAoB,EACvB,GAAS,mBAAqB,EAAY,iBAC5C,EACA,KAAK,mBAAqB,GAAS,oBAAsB,IACzD,KAAK,OAAS,GAAS,OACvB,KAAK,cAAgB,GAAS,cAE9B,IAAM,EAAa,IAAI,EACrB,GAAS,cACT,KAAK,mBACL,GAAS,IACX,EACA,KAAK,QAAU,IAAI,EAAa,CAC9B,kBAAmB,KAAK,kBACxB,mBAAoB,KAAK,mBACzB,GAAI,GAAS,OAAS,IAAA,GAAY,CAAC,EAAI,CAAE,KAAM,EAAQ,IAAK,EAC5D,GAAI,GAAS,gBAAkB,IAAA,GAAY,CAAC,EAAI,CAAE,cAAe,EAAQ,aAAc,EACvF,GAAI,KAAK,gBAAkB,IAAA,GAAY,CAAC,EAAI,CAAE,cAAe,KAAK,aAAc,CAClF,CAAC,EACD,IAAM,EAAU,CAAE,eAAgB,GAAS,cAAe,EAC1D,KAAK,QAAU,IAAI,EAAQ,KAAK,QAAS,CAAO,EAGhD,IAAM,EACJ,GAAS,mBAAqB,YAAc,IAAI,EAAqB,KAAK,QAC5E,KAAK,QAAU,IAAI,EAAQ,KAAK,QAAS,CAAQ,EACjD,KAAK,MAAQ,IAAI,EAAM,KAAK,QAAS,EAAU,CAAO,EACtD,IAAM,EAAa,CAAE,gBAAiB,GAAS,gBAAiB,GAAG,CAAQ,EAC3E,KAAK,UAAY,IAAI,EACnB,KAAK,QACL,EACA,KAAK,OACL,KAAK,cACL,CACF,EACA,KAAK,MAAQ,IAAI,EAAM,KAAK,QAAS,EAAU,KAAK,OAAQ,KAAK,cAAe,CAAU,EAC1F,KAAK,OAAS,IAAI,EAAO,KAAK,kBAAmB,CAAU,EAC3D,KAAK,KAAO,IAAI,EAAQ,KAAK,QAAS,KAAK,MAAM,EACjD,KAAK,gBACH,GAAS,kBACR,GAAS,oBAAsB,IAAA,GAC5B,EAAY,gBACZ,GAAG,KAAK,kBAAkB,QAAQ,SAAU,IAAI,EAAE,MACxD,KAAK,cAAgB,GAAS,cAC9B,KAAK,aAAe,GAAS,YAC/B,CAaA,IAAI,QAA4B,CAM9B,OALA,KAAK,eAAL,KAAK,aAAiB,IAAI,EAAkB,CAC1C,IAAK,KAAK,gBACV,GAAI,KAAK,gBAAkB,IAAA,GAAY,CAAC,EAAI,CAAE,cAAe,KAAK,aAAc,EAChF,GAAI,KAAK,eAAiB,IAAA,GAAY,CAAC,EAAI,CAAE,OAAQ,KAAK,YAAa,CACzE,CAAC,GACM,KAAK,YACd,CACF,EAEA,SAAS,EAAkB,EAAuB,CAChD,OAAO,EAAM,QAAQ,OAAQ,EAAE,CACjC"}