@gala-chain/gswap-sdk 2.0.0-rc.2 → 2.0.0-rc.4

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.
@@ -1 +1 @@
1
- {"version":3,"file":"pending_swaps.js","names":["_defineProperty","PENDING_SWAPS_STORAGE_KEY","PENDING_SWAP_EXPIRY_MS","PENDING_SWAP_PRUNE_REASONS","PendingSwapStore","storage","swap","options","isValidUniqueKey","others","entry","stored","current","uniqueKey","fate","pendingSwapFateFor","error","removed","now","pruned","kept","reason","mine","wallet","right","left","run","operation","value","entries","item","flag","requestTransactionId","token0Symbol","token1Symbol","createdAt"],"sources":["../../../src/classes/pending_swaps.ts"],"sourcesContent":["import { isValidUniqueKey } from '@gala-chain/gala-core-sdk';\nimport { pendingSwapFateFor, type PendingSwapFate } from './pending_swap_fate.js';\nimport type { StorageAdapter } from './storage_adapters.js';\n\n/** Key the wallet_extension has always used; its storage value is a plain array of entries. */\nexport const PENDING_SWAPS_STORAGE_KEY = 'pendingSwaps.v1';\n\n/** Entries older than this are dropped the next time a wallet's swaps are listed. */\nexport const PENDING_SWAP_EXPIRY_MS = 7 * 24 * 60 * 60 * 1000;\n\n/** A RequestTrade that was accepted by the gateway but whose outcome has not been read yet. */\nexport interface PendingSwap {\n uniqueKey: string;\n requestTransactionId: string | null;\n /** The wallet (account) that submitted the swap; `list` filters by it. */\n wallet: string;\n token0Symbol: string;\n token1Symbol: string;\n /** Whether the swap was a shadow (\"dark\") swap. */\n isDarkSwap: boolean;\n /** Epoch milliseconds when the swap was queued. */\n createdAt: number;\n}\n\n/**\n * Why the store dropped a pending swap (the `reason` of {@link PrunedPendingSwap}).\n *\n * - `expired`: older than {@link PENDING_SWAP_EXPIRY_MS}.\n * - `invalid_unique_key`: its `uniqueKey` is not a valid apply-results key (1 to 128 characters\n * of `A-Za-z0-9._:-`). `confirmAppliedTrade` refuses such a key with `INVALID_UNIQUE_KEY`\n * before reading anything and always will, so keeping the entry would retry it forever. The\n * SDK never signs such a key; an entry like this is corrupt or foreign.\n * - `chain_rejected`: {@link PendingSwapStore.settle} dropped it because a confirmation proved the\n * chain REFUSED the trade (`pendingSwapFateFor` is `forget`): it did not happen.\n * - `unconfirmable`: {@link PendingSwapStore.settle} dropped it because a confirmation failed in a\n * way that never changes (`forget_quietly`: the key itself is refused): nothing can be learned.\n */\nexport const PENDING_SWAP_PRUNE_REASONS = [\n 'expired',\n 'invalid_unique_key',\n 'chain_rejected',\n 'unconfirmable',\n] as const;\n\n/** One member of {@link PENDING_SWAP_PRUNE_REASONS}. */\nexport type PendingSwapPruneReason = (typeof PENDING_SWAP_PRUNE_REASONS)[number];\n\n/** A pending swap the store dropped, and why. */\nexport interface PrunedPendingSwap {\n swap: PendingSwap;\n reason: PendingSwapPruneReason;\n}\n\n/** Options for {@link PendingSwapStore}. */\nexport interface PendingSwapStoreOptions {\n /**\n * Called once per dropped entry (after the drop is persisted, for `list`; at once for a\n * `remember` that refuses an invalid key), so the app can tell the user or log why a swap it\n * was tracking is gone. Diagnostics only: what it does, or throws, never changes the store.\n */\n onPrune?: ((pruned: PrunedPendingSwap) => void) | undefined;\n /** Storage key. Default {@link PENDING_SWAPS_STORAGE_KEY}. */\n storageKey?: string | undefined;\n /**\n * The stored name of the dark-swap flag. `isDarkSwap` (default) is wallet_extension's shape;\n * `shadow` is frontend_v2's. Entries are always exposed to callers as `isDarkSwap`.\n */\n darkFlagField?: 'isDarkSwap' | 'shadow' | undefined;\n /**\n * Where `remember` puts a new entry in the stored array: `end` (default, wallet_extension) or\n * `start` (frontend_v2).\n */\n insertAt?: 'end' | 'start' | undefined;\n /**\n * The order of `list`: `stored` (default, wallet_extension: oldest first for `insertAt: 'end'`)\n * or `newest-first` (frontend_v2).\n */\n listOrder?: 'stored' | 'newest-first' | undefined;\n /** Clock, in epoch milliseconds. Defaults to `Date.now`. */\n now?: (() => number) | undefined;\n}\n\n/**\n * Remembers queued swaps so their outcome can be confirmed after the UI, popup or service worker\n * that submitted them is gone (`confirmAppliedTrade` with the stored `uniqueKey`).\n *\n * Guarantees:\n * - Persistence is best effort and NEVER throws: a failing or missing storage must not turn an\n * accepted request into a client-side resubmission. `list` resolves `[]` when storage fails.\n * - Operations of one store instance are serialized, so concurrent `remember` / `forget` calls\n * cannot lose each other's write (read-modify-write over an async adapter). Two store instances\n * over the same storage (for example popup and service worker) are not coordinated.\n * - Entries that do not match the expected shape are ignored and dropped on the next write.\n * - Entries expire after seven days (an entry exactly seven days old is expired; frontend_v2\n * kept it for that one millisecond); `list` removes them from storage, which frontend_v2 never\n * did on read.\n * - An entry whose `uniqueKey` is not a valid apply-results key is terminal, not pending:\n * `confirmAppliedTrade` fails it with `INVALID_UNIQUE_KEY` every time, so a resume loop would\n * retry it forever. `list` prunes it like an expired one, and `remember` refuses to save it;\n * both tell `onPrune` the reason (`PENDING_SWAP_PRUNE_REASONS`).\n * - New entries go to the end (or `insertAt: 'start'`); `list` returns the wallet's entries in\n * stored order (or `listOrder: 'newest-first'`).\n * - The stored shape is wallet_extension's by default; `storageKey`, `darkFlagField`, `insertAt`\n * and `listOrder` reproduce frontend_v2's. JSON key order inside an entry is not preserved.\n *\n * Passing no `storage` yields a store that remembers nothing.\n *\n * @example\n * ```ts\n * const store = new PendingSwapStore({\n * get: async (key) => (await chrome.storage.local.get(key))[key],\n * set: (key, value) => chrome.storage.local.set({ [key]: value }),\n * });\n * await store.remember({ uniqueKey, requestTransactionId, wallet, token0Symbol: 'GALA',\n * token1Symbol: 'GUSDC', isDarkSwap: false, createdAt: Date.now() });\n * for (const swap of await store.list(wallet)) await confirmAppliedTrade(client, swap);\n * ```\n */\nexport class PendingSwapStore {\n private readonly storageKey: string;\n private readonly darkFlagField: 'isDarkSwap' | 'shadow';\n private readonly insertAt: 'end' | 'start';\n private readonly listOrder: 'stored' | 'newest-first';\n private readonly now: () => number;\n private readonly onPrune: ((pruned: PrunedPendingSwap) => void) | undefined;\n private queue: Promise<unknown> = Promise.resolve();\n\n constructor(\n private readonly storage: StorageAdapter | undefined,\n options: PendingSwapStoreOptions = {},\n ) {\n this.storageKey = options.storageKey ?? PENDING_SWAPS_STORAGE_KEY;\n this.darkFlagField = options.darkFlagField ?? 'isDarkSwap';\n this.insertAt = options.insertAt ?? 'end';\n this.listOrder = options.listOrder ?? 'stored';\n this.now = options.now ?? Date.now;\n this.onPrune = options.onPrune;\n }\n\n /**\n * Saves (or replaces, by `uniqueKey`) a pending swap. A swap whose `uniqueKey` is not a valid\n * apply-results key is not saved (it could never be confirmed); it is reported to `onPrune` as\n * `invalid_unique_key`.\n */\n public remember(swap: PendingSwap): Promise<void> {\n if (!isValidUniqueKey(swap.uniqueKey)) {\n this.report([{ swap, reason: 'invalid_unique_key' }]);\n return Promise.resolve();\n }\n return this.enqueue(async (storage) => {\n const current = await this.read(storage);\n const others = current\n .filter((entry) => entry.uniqueKey !== swap.uniqueKey)\n .map(this.toStored);\n const stored = this.toStored(swap);\n await storage.set(\n this.storageKey,\n this.insertAt === 'end' ? [...others, stored] : [stored, ...others],\n );\n });\n }\n\n /** Removes a pending swap once its outcome is known. */\n public forget(uniqueKey: string): Promise<void> {\n return this.enqueue(async (storage) => {\n const current = await this.read(storage);\n await storage.set(\n this.storageKey,\n current.filter((entry) => entry.uniqueKey !== uniqueKey).map(this.toStored),\n );\n });\n }\n\n /**\n * Applies {@link pendingSwapFateFor} to a swap whose confirmation failed with `error`, and\n * returns the fate so the app can word it (tell the user on `forget`, say nothing on\n * `forget_quietly`, keep polling on `keep`):\n *\n * - `forget`: the entry is removed and `onPrune` hears `chain_rejected`.\n * - `forget_quietly`: the entry is removed and `onPrune` hears `unconfirmable`.\n * - `keep`: nothing changes and `onPrune` is not called.\n *\n * Like every store operation it is serialized and best effort: if storage fails the entry stays\n * (a later `settle` or `list` handles it) and the fate is still returned. A key that is not\n * stored (already forgotten) is not an error and `onPrune` is not called for it.\n */\n public async settle(uniqueKey: string, error: unknown): Promise<PendingSwapFate> {\n const fate = pendingSwapFateFor(error);\n if (fate === 'keep') return fate;\n const removed = await this.enqueue(async (storage) => {\n const current = await this.read(storage);\n const swap = current.find((entry) => entry.uniqueKey === uniqueKey);\n if (swap === undefined) return undefined;\n await storage.set(\n this.storageKey,\n current.filter((entry) => entry.uniqueKey !== uniqueKey).map(this.toStored),\n );\n return swap;\n });\n if (removed !== undefined) {\n this.report([\n { swap: removed, reason: fate === 'forget' ? 'chain_rejected' : 'unconfirmable' },\n ]);\n }\n return fate;\n }\n\n /**\n * The wallet's pending swaps that can still be confirmed (see `listOrder`). Entries that never\n * can be are pruned from storage, for every wallet, and reported to `onPrune`: expired ones\n * (`expired`) and ones whose `uniqueKey` is not a valid apply-results key\n * (`invalid_unique_key`: `confirmAppliedTrade` would fail `INVALID_UNIQUE_KEY` on it forever).\n */\n public async list(wallet: string): Promise<PendingSwap[]> {\n const result = await this.enqueue(async (storage) => {\n const current = await this.read(storage);\n const now = this.now();\n const pruned: PrunedPendingSwap[] = [];\n const kept: PendingSwap[] = [];\n for (const entry of current) {\n const reason = this.pruneReasonOf(entry, now);\n if (reason === undefined) kept.push(entry);\n else pruned.push({ swap: entry, reason });\n }\n if (pruned.length > 0) {\n await storage.set(this.storageKey, kept.map(this.toStored));\n this.report(pruned);\n }\n const mine = kept.filter((entry) => entry.wallet === wallet);\n return this.listOrder === 'newest-first'\n ? mine.sort((left, right) => right.createdAt - left.createdAt)\n : mine;\n });\n return result ?? [];\n }\n\n private pruneReasonOf(entry: PendingSwap, now: number): PendingSwapPruneReason | undefined {\n if (!isValidUniqueKey(entry.uniqueKey)) return 'invalid_unique_key';\n return now - entry.createdAt < PENDING_SWAP_EXPIRY_MS ? undefined : 'expired';\n }\n\n /** Tells `onPrune` about each dropped entry; the hook is diagnostics and cannot break the store. */\n private report(pruned: readonly PrunedPendingSwap[]): void {\n for (const entry of pruned) {\n try {\n this.onPrune?.(entry);\n } catch {\n // A diagnostics hook must not turn a store operation into a failure.\n }\n }\n }\n\n private enqueue<T>(operation: (storage: StorageAdapter) => Promise<T>): Promise<T | undefined> {\n const storage = this.storage;\n if (storage === undefined) return Promise.resolve(undefined);\n const run = this.queue.then(async () => {\n try {\n return await operation(storage);\n } catch {\n // Storage can be missing, full, or disappear while a context is closing. Persistence is\n // best effort by contract.\n return undefined;\n }\n });\n this.queue = run;\n return run;\n }\n\n private async read(storage: StorageAdapter): Promise<PendingSwap[]> {\n const value = await storage.get(this.storageKey);\n if (!Array.isArray(value)) return [];\n const entries: PendingSwap[] = [];\n for (const item of value as unknown[]) {\n const entry = this.fromStored(item);\n if (entry !== undefined) entries.push(entry);\n }\n return entries;\n }\n\n private readonly toStored = (swap: PendingSwap): Record<string, unknown> => ({\n uniqueKey: swap.uniqueKey,\n requestTransactionId: swap.requestTransactionId,\n wallet: swap.wallet,\n token0Symbol: swap.token0Symbol,\n token1Symbol: swap.token1Symbol,\n [this.darkFlagField]: swap.isDarkSwap,\n createdAt: swap.createdAt,\n });\n\n private fromStored(value: unknown): PendingSwap | undefined {\n if (value === null || typeof value !== 'object') return undefined;\n const entry = value as Record<string, unknown>;\n const flag = entry[this.darkFlagField];\n const { uniqueKey, requestTransactionId, wallet, token0Symbol, token1Symbol, createdAt } =\n entry;\n if (\n typeof uniqueKey !== 'string' ||\n !(typeof requestTransactionId === 'string' || requestTransactionId === null) ||\n typeof wallet !== 'string' ||\n typeof token0Symbol !== 'string' ||\n typeof token1Symbol !== 'string' ||\n typeof flag !== 'boolean' ||\n typeof createdAt !== 'number' ||\n !Number.isFinite(createdAt)\n ) {\n return undefined;\n }\n return {\n uniqueKey,\n requestTransactionId,\n wallet,\n token0Symbol,\n token1Symbol,\n isDarkSwap: flag,\n createdAt,\n };\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"pending_swaps.js","names":["_defineProperty","PENDING_SWAPS_STORAGE_KEY","PENDING_SWAP_EXPIRY_MS","PENDING_SWAP_PRUNE_REASONS","PendingSwapStore","storage","swap","options","isValidUniqueKey","others","entry","stored","current","uniqueKey","fate","pendingSwapFateFor","error","removed","now","pruned","kept","reason","mine","wallet","right","left","run","operation","value","entries","item","flag","requestTransactionId","token0Symbol","token1Symbol","createdAt"],"sources":["../../../src/classes/pending_swaps.ts"],"sourcesContent":["import { isValidUniqueKey } from '@gala-chain/gala-core-sdk';\nimport { pendingSwapFateFor, type PendingSwapFate } from './pending_swap_fate.js';\nimport type { StorageAdapter } from './storage_adapters.js';\n\n/** Default storage key; the stored value is a plain array of entries. */\nexport const PENDING_SWAPS_STORAGE_KEY = 'pendingSwaps.v1';\n\n/** Entries older than this are dropped the next time a wallet's swaps are listed. */\nexport const PENDING_SWAP_EXPIRY_MS = 7 * 24 * 60 * 60 * 1000;\n\n/** A RequestTrade that was accepted by the gateway but whose outcome has not been read yet. */\nexport interface PendingSwap {\n uniqueKey: string;\n requestTransactionId: string | null;\n /** The wallet (account) that submitted the swap; `list` filters by it. */\n wallet: string;\n token0Symbol: string;\n token1Symbol: string;\n /** Whether the swap was a shadow (\"dark\") swap. */\n isDarkSwap: boolean;\n /** Epoch milliseconds when the swap was queued. */\n createdAt: number;\n}\n\n/**\n * Why the store dropped a pending swap (the `reason` of {@link PrunedPendingSwap}).\n *\n * - `expired`: older than {@link PENDING_SWAP_EXPIRY_MS}.\n * - `invalid_unique_key`: its `uniqueKey` is not a valid apply-results key (1 to 128 characters\n * of `A-Za-z0-9._:-`). `confirmAppliedTrade` refuses such a key with `INVALID_UNIQUE_KEY`\n * before reading anything and always will, so keeping the entry would retry it forever. The\n * SDK never signs such a key; an entry like this is corrupt or foreign.\n * - `chain_rejected`: {@link PendingSwapStore.settle} dropped it because a confirmation proved the\n * chain REFUSED the trade (`pendingSwapFateFor` is `forget`): it did not happen.\n * - `unconfirmable`: {@link PendingSwapStore.settle} dropped it because a confirmation failed in a\n * way that never changes (`forget_quietly`: the key itself is refused): nothing can be learned.\n */\nexport const PENDING_SWAP_PRUNE_REASONS = [\n 'expired',\n 'invalid_unique_key',\n 'chain_rejected',\n 'unconfirmable',\n] as const;\n\n/** One member of {@link PENDING_SWAP_PRUNE_REASONS}. */\nexport type PendingSwapPruneReason = (typeof PENDING_SWAP_PRUNE_REASONS)[number];\n\n/** A pending swap the store dropped, and why. */\nexport interface PrunedPendingSwap {\n swap: PendingSwap;\n reason: PendingSwapPruneReason;\n}\n\n/** Options for {@link PendingSwapStore}. */\nexport interface PendingSwapStoreOptions {\n /**\n * Called once per dropped entry (after the drop is persisted, for `list`; at once for a\n * `remember` that refuses an invalid key), so the app can tell the user or log why a swap it\n * was tracking is gone. Diagnostics only: what it does, or throws, never changes the store.\n */\n onPrune?: ((pruned: PrunedPendingSwap) => void) | undefined;\n /** Storage key. Default {@link PENDING_SWAPS_STORAGE_KEY}. */\n storageKey?: string | undefined;\n /**\n * The stored name of the dark-swap flag: `isDarkSwap` (default) or `shadow`, for storage written\n * by an older web app. Entries are always exposed to callers as `isDarkSwap`.\n */\n darkFlagField?: 'isDarkSwap' | 'shadow' | undefined;\n /**\n * Where `remember` puts a new entry in the stored array: `end` (default) or `start`.\n */\n insertAt?: 'end' | 'start' | undefined;\n /**\n * The order of `list`: `stored` (default: oldest first for `insertAt: 'end'`) or\n * `newest-first`.\n */\n listOrder?: 'stored' | 'newest-first' | undefined;\n /** Clock, in epoch milliseconds. Defaults to `Date.now`. */\n now?: (() => number) | undefined;\n}\n\n/**\n * Remembers queued swaps so their outcome can be confirmed after the UI, popup or service worker\n * that submitted them is gone (`confirmAppliedTrade` with the stored `uniqueKey`).\n *\n * Guarantees:\n * - Persistence is best effort and NEVER throws: a failing or missing storage must not turn an\n * accepted request into a client-side resubmission. `list` resolves `[]` when storage fails.\n * - Operations of one store instance are serialized, so concurrent `remember` / `forget` calls\n * cannot lose each other's write (read-modify-write over an async adapter). Two store instances\n * over the same storage (for example popup and service worker) are not coordinated.\n * - Entries that do not match the expected shape are ignored and dropped on the next write.\n * - Entries expire after seven days (an entry exactly seven days old is expired); `list` removes\n * them from storage.\n * - An entry whose `uniqueKey` is not a valid apply-results key is terminal, not pending:\n * `confirmAppliedTrade` fails it with `INVALID_UNIQUE_KEY` every time, so a resume loop would\n * retry it forever. `list` prunes it like an expired one, and `remember` refuses to save it;\n * both tell `onPrune` the reason (`PENDING_SWAP_PRUNE_REASONS`).\n * - New entries go to the end (or `insertAt: 'start'`); `list` returns the wallet's entries in\n * stored order (or `listOrder: 'newest-first'`).\n * - `storageKey`, `darkFlagField`, `insertAt` and `listOrder` adapt the stored layout to an\n * existing storage. JSON key order inside an entry is not preserved.\n *\n * Passing no `storage` yields a store that remembers nothing.\n *\n * @example\n * ```ts\n * const store = new PendingSwapStore({\n * get: async (key) => (await chrome.storage.local.get(key))[key],\n * set: (key, value) => chrome.storage.local.set({ [key]: value }),\n * });\n * await store.remember({ uniqueKey, requestTransactionId, wallet, token0Symbol: 'GALA',\n * token1Symbol: 'GUSDC', isDarkSwap: false, createdAt: Date.now() });\n * for (const swap of await store.list(wallet)) await confirmAppliedTrade(client, swap);\n * ```\n */\nexport class PendingSwapStore {\n private readonly storageKey: string;\n private readonly darkFlagField: 'isDarkSwap' | 'shadow';\n private readonly insertAt: 'end' | 'start';\n private readonly listOrder: 'stored' | 'newest-first';\n private readonly now: () => number;\n private readonly onPrune: ((pruned: PrunedPendingSwap) => void) | undefined;\n private queue: Promise<unknown> = Promise.resolve();\n\n constructor(\n private readonly storage: StorageAdapter | undefined,\n options: PendingSwapStoreOptions = {},\n ) {\n this.storageKey = options.storageKey ?? PENDING_SWAPS_STORAGE_KEY;\n this.darkFlagField = options.darkFlagField ?? 'isDarkSwap';\n this.insertAt = options.insertAt ?? 'end';\n this.listOrder = options.listOrder ?? 'stored';\n this.now = options.now ?? Date.now;\n this.onPrune = options.onPrune;\n }\n\n /**\n * Saves (or replaces, by `uniqueKey`) a pending swap. A swap whose `uniqueKey` is not a valid\n * apply-results key is not saved (it could never be confirmed); it is reported to `onPrune` as\n * `invalid_unique_key`.\n */\n public remember(swap: PendingSwap): Promise<void> {\n if (!isValidUniqueKey(swap.uniqueKey)) {\n this.report([{ swap, reason: 'invalid_unique_key' }]);\n return Promise.resolve();\n }\n return this.enqueue(async (storage) => {\n const current = await this.read(storage);\n const others = current\n .filter((entry) => entry.uniqueKey !== swap.uniqueKey)\n .map(this.toStored);\n const stored = this.toStored(swap);\n await storage.set(\n this.storageKey,\n this.insertAt === 'end' ? [...others, stored] : [stored, ...others],\n );\n });\n }\n\n /** Removes a pending swap once its outcome is known. */\n public forget(uniqueKey: string): Promise<void> {\n return this.enqueue(async (storage) => {\n const current = await this.read(storage);\n await storage.set(\n this.storageKey,\n current.filter((entry) => entry.uniqueKey !== uniqueKey).map(this.toStored),\n );\n });\n }\n\n /**\n * Applies {@link pendingSwapFateFor} to a swap whose confirmation failed with `error`, and\n * returns the fate so the app can word it (tell the user on `forget`, say nothing on\n * `forget_quietly`, keep polling on `keep`):\n *\n * - `forget`: the entry is removed and `onPrune` hears `chain_rejected`.\n * - `forget_quietly`: the entry is removed and `onPrune` hears `unconfirmable`.\n * - `keep`: nothing changes and `onPrune` is not called.\n *\n * Like every store operation it is serialized and best effort: if storage fails the entry stays\n * (a later `settle` or `list` handles it) and the fate is still returned. A key that is not\n * stored (already forgotten) is not an error and `onPrune` is not called for it.\n */\n public async settle(uniqueKey: string, error: unknown): Promise<PendingSwapFate> {\n const fate = pendingSwapFateFor(error);\n if (fate === 'keep') return fate;\n const removed = await this.enqueue(async (storage) => {\n const current = await this.read(storage);\n const swap = current.find((entry) => entry.uniqueKey === uniqueKey);\n if (swap === undefined) return undefined;\n await storage.set(\n this.storageKey,\n current.filter((entry) => entry.uniqueKey !== uniqueKey).map(this.toStored),\n );\n return swap;\n });\n if (removed !== undefined) {\n this.report([\n { swap: removed, reason: fate === 'forget' ? 'chain_rejected' : 'unconfirmable' },\n ]);\n }\n return fate;\n }\n\n /**\n * The wallet's pending swaps that can still be confirmed (see `listOrder`). Entries that never\n * can be are pruned from storage, for every wallet, and reported to `onPrune`: expired ones\n * (`expired`) and ones whose `uniqueKey` is not a valid apply-results key\n * (`invalid_unique_key`: `confirmAppliedTrade` would fail `INVALID_UNIQUE_KEY` on it forever).\n */\n public async list(wallet: string): Promise<PendingSwap[]> {\n const result = await this.enqueue(async (storage) => {\n const current = await this.read(storage);\n const now = this.now();\n const pruned: PrunedPendingSwap[] = [];\n const kept: PendingSwap[] = [];\n for (const entry of current) {\n const reason = this.pruneReasonOf(entry, now);\n if (reason === undefined) kept.push(entry);\n else pruned.push({ swap: entry, reason });\n }\n if (pruned.length > 0) {\n await storage.set(this.storageKey, kept.map(this.toStored));\n this.report(pruned);\n }\n const mine = kept.filter((entry) => entry.wallet === wallet);\n return this.listOrder === 'newest-first'\n ? mine.sort((left, right) => right.createdAt - left.createdAt)\n : mine;\n });\n return result ?? [];\n }\n\n private pruneReasonOf(entry: PendingSwap, now: number): PendingSwapPruneReason | undefined {\n if (!isValidUniqueKey(entry.uniqueKey)) return 'invalid_unique_key';\n return now - entry.createdAt < PENDING_SWAP_EXPIRY_MS ? undefined : 'expired';\n }\n\n /** Tells `onPrune` about each dropped entry; the hook is diagnostics and cannot break the store. */\n private report(pruned: readonly PrunedPendingSwap[]): void {\n for (const entry of pruned) {\n try {\n this.onPrune?.(entry);\n } catch {\n // A diagnostics hook must not turn a store operation into a failure.\n }\n }\n }\n\n private enqueue<T>(operation: (storage: StorageAdapter) => Promise<T>): Promise<T | undefined> {\n const storage = this.storage;\n if (storage === undefined) return Promise.resolve(undefined);\n const run = this.queue.then(async () => {\n try {\n return await operation(storage);\n } catch {\n // Storage can be missing, full, or disappear while a context is closing. Persistence is\n // best effort by contract.\n return undefined;\n }\n });\n this.queue = run;\n return run;\n }\n\n private async read(storage: StorageAdapter): Promise<PendingSwap[]> {\n const value = await storage.get(this.storageKey);\n if (!Array.isArray(value)) return [];\n const entries: PendingSwap[] = [];\n for (const item of value as unknown[]) {\n const entry = this.fromStored(item);\n if (entry !== undefined) entries.push(entry);\n }\n return entries;\n }\n\n private readonly toStored = (swap: PendingSwap): Record<string, unknown> => ({\n uniqueKey: swap.uniqueKey,\n requestTransactionId: swap.requestTransactionId,\n wallet: swap.wallet,\n token0Symbol: swap.token0Symbol,\n token1Symbol: swap.token1Symbol,\n [this.darkFlagField]: swap.isDarkSwap,\n createdAt: swap.createdAt,\n });\n\n private fromStored(value: unknown): PendingSwap | undefined {\n if (value === null || typeof value !== 'object') return undefined;\n const entry = value as Record<string, unknown>;\n const flag = entry[this.darkFlagField];\n const { uniqueKey, requestTransactionId, wallet, token0Symbol, token1Symbol, createdAt } =\n entry;\n if (\n typeof uniqueKey !== 'string' ||\n !(typeof requestTransactionId === 'string' || requestTransactionId === null) ||\n typeof wallet !== 'string' ||\n typeof token0Symbol !== 'string' ||\n typeof token1Symbol !== 'string' ||\n typeof flag !== 'boolean' ||\n typeof createdAt !== 'number' ||\n !Number.isFinite(createdAt)\n ) {\n return undefined;\n }\n return {\n uniqueKey,\n requestTransactionId,\n wallet,\n token0Symbol,\n token1Symbol,\n isDarkSwap: flag,\n createdAt,\n };\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"shadow_submit.js","names":["SHADOW_SUBMIT_CODES","SHADOW_FAILURE_KINDS","classifyShadowSubmitStatus","status","classifyShadowSubmitError","code","readShadowSubmitOutcome","result","envelope","asRecord","body","httpStatus","\"error\"","data","\"data\"","rawTransactionId","\"transactionId\"","method","requestedKey","signedBody","\"uniqueKey\"","\"mode\"","GSwapSDKError","kind","getStringProperty","value"],"sources":["../../../src/classes/shadow_submit.ts"],"sourcesContent":["import { getStringProperty, GSwapSDKError } from './gswap_sdk_error.js';\n\n/** The raw answer to a shadow (dark) write: every HTTP status resolves. */\nexport interface ShadowSubmitResult {\n httpStatus: number;\n /** Parsed JSON body, or the raw text when the body was not JSON. */\n body: unknown;\n}\n\n/**\n * Machine-readable codes defi-backend returns in a shadow failure body (`{ code, message }`, with\n * no `statusCode`; the HTTP status carries the number).\n *\n * - `SHADOW_UNAVAILABLE` (503): marking failed or infra not configured; retryable. Never fall back\n * silently to a public swap.\n * - `SHADOW_UNSUPPORTED_TYPE` (400): shadow requested on a non-swap method.\n * - `SHADOW_RELAY_REJECTED`: terminal business rejection (slippage, balance, ...), not retryable;\n * `message` is the gateway's verbatim string.\n */\nexport const SHADOW_SUBMIT_CODES = [\n 'SHADOW_UNAVAILABLE',\n 'SHADOW_UNSUPPORTED_TYPE',\n 'SHADOW_RELAY_REJECTED',\n] as const;\n\n/** One member of {@link SHADOW_SUBMIT_CODES}. */\nexport type ShadowSubmitCode = (typeof SHADOW_SUBMIT_CODES)[number];\n\n/**\n * Every `kind` of a failed {@link ShadowSubmitOutcome}, for an app that maps each to its own text\n * (`Record<ShadowFailureKind, string>`): `unavailable` (retryable), `disabled` (the shadow route\n * answered 400: the request is not a dark-swappable one, e.g. `SHADOW_UNSUPPORTED_TYPE`; Shadow\n * Swaps themselves are enabled in every environment), `rejected` (carries the gateway's `message`)\n * and `unknown`.\n */\nexport const SHADOW_FAILURE_KINDS = ['unavailable', 'disabled', 'rejected', 'unknown'] as const;\n\n/** One member of {@link SHADOW_FAILURE_KINDS}. */\nexport type ShadowFailureKind = (typeof SHADOW_FAILURE_KINDS)[number];\n\n/** A RequestTrade the gateway queued: the handle to confirm it with `confirmAppliedTrade`. */\nexport interface ShadowQueuedRequest {\n uniqueKey: string;\n requestTransactionId: string | null;\n}\n\n/** A shadow write the gateway refused or did not answer: `kind` says why (see {@link SHADOW_FAILURE_KINDS}). */\nexport type ShadowSubmitFailure =\n | { ok: false; kind: 'unavailable'; retryable: true }\n | { ok: false; kind: 'disabled'; retryable: false }\n | { ok: false; kind: 'rejected'; retryable: false; message: string }\n | { ok: false; kind: 'unknown'; retryable: false };\n\n/**\n * The normalized outcome of a shadow RequestTrade (a dark swap). A success (`ok: true`) ALWAYS\n * carries `queued`, the exact key the gateway acknowledged: a RequestTrade answer that does not\n * queue the signed key is not an outcome but a thrown `SHADOW_NOT_QUEUED`. So after `if (outcome.ok)`\n * the app reads `outcome.queued` with no `undefined` branch.\n */\nexport type ShadowSubmitOutcome =\n | {\n ok: true;\n /** The request transaction id (`''` when the gateway gave none). */\n bundleId: string;\n /** Confirm with `confirmAppliedTrade`. */\n queued: ShadowQueuedRequest;\n }\n | ShadowSubmitFailure;\n\n/**\n * The success of a shadow write that is NOT a RequestTrade and executed synchronously\n * (`mode: 'sync'`, a liquidity write): there is nothing queued to confirm, so `queued` is absent.\n */\nexport interface ShadowSyncSuccess {\n ok: true;\n /** The transaction id (`''` when the gateway gave none). */\n bundleId: string;\n queued?: undefined;\n}\n\n/** Maps an HTTP status to a failure kind: 503 retryable, 400 disabled, anything else unknown. */\nexport function classifyShadowSubmitStatus(status: number): ShadowFailureKind {\n if (status === 503) return 'unavailable';\n if (status === 400) return 'disabled';\n return 'unknown';\n}\n\n/**\n * Prefers the body `code` (stable across status remaps) and falls back to the HTTP status.\n * `SHADOW_UNAVAILABLE` is the only retryable case; `SHADOW_RELAY_REJECTED` is a terminal gateway\n * rejection; `SHADOW_UNSUPPORTED_TYPE` and any other 400 map to \"disabled\".\n */\nexport function classifyShadowSubmitError(status: number, code?: string): ShadowFailureKind {\n if (code === 'SHADOW_UNAVAILABLE') return 'unavailable';\n if (code === 'SHADOW_RELAY_REJECTED') return 'rejected';\n if (code === 'SHADOW_UNSUPPORTED_TYPE') return 'disabled';\n return classifyShadowSubmitStatus(status);\n}\n\n/**\n * Normalizes a shadow write's raw answer. For `'RequestTrade'` the result is a\n * {@link ShadowSubmitOutcome}, whose success always has `queued`; any other method may also be a\n * {@link ShadowSyncSuccess} (no `queued`).\n *\n * A 2xx envelope with `error === false` is a success when it is a queued RequestTrade that\n * acknowledges the EXACT signed `uniqueKey`, or a synchronous (`mode: 'sync'`) liquidity write.\n * Everything else is classified as a failure from the status and the body's `code`.\n *\n * @throws GSwapSDKError SHADOW_NOT_QUEUED when a RequestTrade success does not queue the\n * signed `uniqueKey` (a synchronous or mismatched answer must never be accepted as a swap).\n */\nexport function readShadowSubmitOutcome(\n method: 'RequestTrade',\n signedBody: Record<string, unknown>,\n result: ShadowSubmitResult,\n): ShadowSubmitOutcome;\nexport function readShadowSubmitOutcome(\n method: string,\n signedBody: Record<string, unknown>,\n result: ShadowSubmitResult,\n): ShadowSubmitOutcome | ShadowSyncSuccess;\nexport function readShadowSubmitOutcome(\n method: string,\n signedBody: Record<string, unknown>,\n result: ShadowSubmitResult,\n): ShadowSubmitOutcome | ShadowSyncSuccess {\n const { httpStatus, body } = result;\n const envelope = asRecord(body);\n if (httpStatus >= 200 && httpStatus < 300 && envelope?.['error'] === false) {\n const data = asRecord(envelope['data']);\n const rawTransactionId =\n typeof data?.['transactionId'] === 'string' ? data['transactionId'] : undefined;\n if (method === 'RequestTrade') {\n const requestedKey = signedBody['uniqueKey'];\n if (\n data?.['mode'] !== 'queued' ||\n typeof requestedKey !== 'string' ||\n data['uniqueKey'] !== requestedKey\n ) {\n throw new GSwapSDKError(\n 'SHADOW_NOT_QUEUED',\n {},\n {\n status: httpStatus,\n body,\n shadow: true,\n ...(typeof requestedKey === 'string' ? { uniqueKey: requestedKey } : {}),\n },\n );\n }\n return {\n ok: true,\n bundleId: rawTransactionId ?? '',\n queued: { uniqueKey: requestedKey, requestTransactionId: rawTransactionId ?? null },\n };\n }\n if (data?.['mode'] === 'sync') return { ok: true, bundleId: rawTransactionId ?? '' };\n }\n\n const kind = classifyShadowSubmitError(httpStatus, getStringProperty(body, 'code'));\n if (kind === 'rejected') {\n return {\n ok: false,\n kind: 'rejected',\n retryable: false,\n message: getStringProperty(body, 'message') ?? '',\n };\n }\n return kind === 'unavailable'\n ? { ok: false, kind, retryable: true }\n : { ok: false, kind, retryable: false };\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"],"mappings":"4EAmBA,MAAaA,EAAsB,CACjC,qBACA,0BACA,uBACF,EAYaC,EAAuB,CAAC,cAAe,WAAY,WAAY,SAAS,EA8CrF,SAAgBC,EAA2B,EAAmC,CAG5E,OAFIC,IAAW,IAAY,cACvBA,IAAW,IAAY,WACpB,SACT,CAOA,SAAgBC,EAA0B,EAAgB,EAAkC,CAI1F,OAHIC,IAAS,qBAA6B,cACtCA,IAAS,wBAAgC,WACzCA,IAAS,0BAAkC,WACxCH,EAA2BC,CAAM,CAC1C,CAwBA,SAAgBG,EACd,EACA,EACA,EACyC,CACzC,GAAM,CAAE,aAAY,QAASC,EACvBC,EAAWC,EAASC,CAAI,EAC9B,GAAIC,GAAc,KAAOA,EAAa,KAAOH,GAAWI,QAAa,GAAO,CAC1E,IAAMC,EAAOJ,EAASD,EAASM,IAAO,EAChCC,EACJ,OAAOF,GAAOG,eAAqB,SAAWH,EAAKG,cAAmB,IAAA,GACxE,GAAIC,IAAW,eAAgB,CAC7B,IAAMC,EAAeC,EAAWC,UAChC,GACEP,GAAOQ,OAAY,UACnB,OAAOH,GAAiB,UACxBL,EAAKO,YAAiBF,EAEtB,MAAM,IAAII,EACR,oBACA,CAAC,EACD,CACE,OAAQX,EACR,OACA,OAAQ,GACR,GAAI,OAAOO,GAAiB,SAAW,CAAE,UAAWA,CAAa,EAAI,CAAC,CACxE,CACF,EAEF,MAAO,CACL,GAAI,GACJ,SAAUH,GAAoB,GAC9B,OAAQ,CAAE,UAAWG,EAAc,qBAAsBH,GAAoB,IAAK,CACpF,CACF,CACA,GAAIF,GAAOQ,OAAY,OAAQ,MAAO,CAAE,GAAI,GAAM,SAAUN,GAAoB,EAAG,CACrF,CAEA,IAAMQ,EAAOnB,EAA0BO,EAAYa,EAAkBd,EAAM,MAAM,CAAC,EASlF,OARIa,IAAS,WACJ,CACL,GAAI,GACJ,KAAM,WACN,UAAW,GACX,QAASC,EAAkBd,EAAM,SAAS,GAAK,EACjD,EAEKa,IAAS,cACZ,CAAE,GAAI,GAAO,OAAM,UAAW,EAAK,EACnC,CAAE,GAAI,GAAO,OAAM,UAAW,EAAM,CAC1C,CAEA,SAASd,EAAS,EAAqD,CACrE,OAAO,OAAOgB,GAAU,UAAYA,EAC/BA,EACD,IAAA,EACN"}
1
+ {"version":3,"file":"shadow_submit.js","names":["SHADOW_SUBMIT_CODES","SHADOW_FAILURE_KINDS","classifyShadowSubmitStatus","status","classifyShadowSubmitError","code","readShadowSubmitOutcome","result","envelope","asRecord","body","httpStatus","\"error\"","data","\"data\"","rawTransactionId","\"transactionId\"","method","requestedKey","signedBody","\"uniqueKey\"","\"mode\"","GSwapSDKError","kind","getStringProperty","value"],"sources":["../../../src/classes/shadow_submit.ts"],"sourcesContent":["import { getStringProperty, GSwapSDKError } from './gswap_sdk_error.js';\n\n/** The raw answer to a shadow (dark) write: every HTTP status resolves. */\nexport interface ShadowSubmitResult {\n httpStatus: number;\n /** Parsed JSON body, or the raw text when the body was not JSON. */\n body: unknown;\n}\n\n/**\n * Machine-readable codes the swap backend returns in a shadow failure body (`{ code, message }`, with\n * no `statusCode`; the HTTP status carries the number).\n *\n * - `SHADOW_UNAVAILABLE` (503): marking failed or infra not configured; retryable. Never fall back\n * silently to a public swap.\n * - `SHADOW_UNSUPPORTED_TYPE` (400): shadow requested on a non-swap method.\n * - `SHADOW_RELAY_REJECTED`: terminal business rejection (slippage, balance, ...), not retryable;\n * `message` is the gateway's verbatim string.\n */\nexport const SHADOW_SUBMIT_CODES = [\n 'SHADOW_UNAVAILABLE',\n 'SHADOW_UNSUPPORTED_TYPE',\n 'SHADOW_RELAY_REJECTED',\n] as const;\n\n/** One member of {@link SHADOW_SUBMIT_CODES}. */\nexport type ShadowSubmitCode = (typeof SHADOW_SUBMIT_CODES)[number];\n\n/**\n * Every `kind` of a failed {@link ShadowSubmitOutcome}, for an app that maps each to its own text\n * (`Record<ShadowFailureKind, string>`): `unavailable` (retryable), `disabled` (the shadow route\n * answered 400: the request is not a dark-swappable one, e.g. `SHADOW_UNSUPPORTED_TYPE`; Shadow\n * Swaps themselves are enabled in every environment), `rejected` (carries the gateway's `message`)\n * and `unknown`.\n */\nexport const SHADOW_FAILURE_KINDS = ['unavailable', 'disabled', 'rejected', 'unknown'] as const;\n\n/** One member of {@link SHADOW_FAILURE_KINDS}. */\nexport type ShadowFailureKind = (typeof SHADOW_FAILURE_KINDS)[number];\n\n/** A RequestTrade the gateway queued: the handle to confirm it with `confirmAppliedTrade`. */\nexport interface ShadowQueuedRequest {\n uniqueKey: string;\n requestTransactionId: string | null;\n}\n\n/** A shadow write the gateway refused or did not answer: `kind` says why (see {@link SHADOW_FAILURE_KINDS}). */\nexport type ShadowSubmitFailure =\n | { ok: false; kind: 'unavailable'; retryable: true }\n | { ok: false; kind: 'disabled'; retryable: false }\n | { ok: false; kind: 'rejected'; retryable: false; message: string }\n | { ok: false; kind: 'unknown'; retryable: false };\n\n/**\n * The normalized outcome of a shadow RequestTrade (a dark swap). A success (`ok: true`) ALWAYS\n * carries `queued`, the exact key the gateway acknowledged: a RequestTrade answer that does not\n * queue the signed key is not an outcome but a thrown `SHADOW_NOT_QUEUED`. So after `if (outcome.ok)`\n * the app reads `outcome.queued` with no `undefined` branch.\n */\nexport type ShadowSubmitOutcome =\n | {\n ok: true;\n /** The request transaction id (`''` when the gateway gave none). */\n bundleId: string;\n /** Confirm with `confirmAppliedTrade`. */\n queued: ShadowQueuedRequest;\n }\n | ShadowSubmitFailure;\n\n/**\n * The success of a shadow write that is NOT a RequestTrade and executed synchronously\n * (`mode: 'sync'`, a liquidity write): there is nothing queued to confirm, so `queued` is absent.\n */\nexport interface ShadowSyncSuccess {\n ok: true;\n /** The transaction id (`''` when the gateway gave none). */\n bundleId: string;\n queued?: undefined;\n}\n\n/** Maps an HTTP status to a failure kind: 503 retryable, 400 disabled, anything else unknown. */\nexport function classifyShadowSubmitStatus(status: number): ShadowFailureKind {\n if (status === 503) return 'unavailable';\n if (status === 400) return 'disabled';\n return 'unknown';\n}\n\n/**\n * Prefers the body `code` (stable across status remaps) and falls back to the HTTP status.\n * `SHADOW_UNAVAILABLE` is the only retryable case; `SHADOW_RELAY_REJECTED` is a terminal gateway\n * rejection; `SHADOW_UNSUPPORTED_TYPE` and any other 400 map to \"disabled\".\n */\nexport function classifyShadowSubmitError(status: number, code?: string): ShadowFailureKind {\n if (code === 'SHADOW_UNAVAILABLE') return 'unavailable';\n if (code === 'SHADOW_RELAY_REJECTED') return 'rejected';\n if (code === 'SHADOW_UNSUPPORTED_TYPE') return 'disabled';\n return classifyShadowSubmitStatus(status);\n}\n\n/**\n * Normalizes a shadow write's raw answer. For `'RequestTrade'` the result is a\n * {@link ShadowSubmitOutcome}, whose success always has `queued`; any other method may also be a\n * {@link ShadowSyncSuccess} (no `queued`).\n *\n * A 2xx envelope with `error === false` is a success when it is a queued RequestTrade that\n * acknowledges the EXACT signed `uniqueKey`, or a synchronous (`mode: 'sync'`) liquidity write.\n * Everything else is classified as a failure from the status and the body's `code`.\n *\n * @throws GSwapSDKError SHADOW_NOT_QUEUED when a RequestTrade success does not queue the\n * signed `uniqueKey` (a synchronous or mismatched answer must never be accepted as a swap).\n */\nexport function readShadowSubmitOutcome(\n method: 'RequestTrade',\n signedBody: Record<string, unknown>,\n result: ShadowSubmitResult,\n): ShadowSubmitOutcome;\nexport function readShadowSubmitOutcome(\n method: string,\n signedBody: Record<string, unknown>,\n result: ShadowSubmitResult,\n): ShadowSubmitOutcome | ShadowSyncSuccess;\nexport function readShadowSubmitOutcome(\n method: string,\n signedBody: Record<string, unknown>,\n result: ShadowSubmitResult,\n): ShadowSubmitOutcome | ShadowSyncSuccess {\n const { httpStatus, body } = result;\n const envelope = asRecord(body);\n if (httpStatus >= 200 && httpStatus < 300 && envelope?.['error'] === false) {\n const data = asRecord(envelope['data']);\n const rawTransactionId =\n typeof data?.['transactionId'] === 'string' ? data['transactionId'] : undefined;\n if (method === 'RequestTrade') {\n const requestedKey = signedBody['uniqueKey'];\n if (\n data?.['mode'] !== 'queued' ||\n typeof requestedKey !== 'string' ||\n data['uniqueKey'] !== requestedKey\n ) {\n throw new GSwapSDKError(\n 'SHADOW_NOT_QUEUED',\n {},\n {\n status: httpStatus,\n body,\n shadow: true,\n ...(typeof requestedKey === 'string' ? { uniqueKey: requestedKey } : {}),\n },\n );\n }\n return {\n ok: true,\n bundleId: rawTransactionId ?? '',\n queued: { uniqueKey: requestedKey, requestTransactionId: rawTransactionId ?? null },\n };\n }\n if (data?.['mode'] === 'sync') return { ok: true, bundleId: rawTransactionId ?? '' };\n }\n\n const kind = classifyShadowSubmitError(httpStatus, getStringProperty(body, 'code'));\n if (kind === 'rejected') {\n return {\n ok: false,\n kind: 'rejected',\n retryable: false,\n message: getStringProperty(body, 'message') ?? '',\n };\n }\n return kind === 'unavailable'\n ? { ok: false, kind, retryable: true }\n : { ok: false, kind, retryable: false };\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"],"mappings":"4EAmBA,MAAaA,EAAsB,CACjC,qBACA,0BACA,uBACF,EAYaC,EAAuB,CAAC,cAAe,WAAY,WAAY,SAAS,EA8CrF,SAAgBC,EAA2B,EAAmC,CAG5E,OAFIC,IAAW,IAAY,cACvBA,IAAW,IAAY,WACpB,SACT,CAOA,SAAgBC,EAA0B,EAAgB,EAAkC,CAI1F,OAHIC,IAAS,qBAA6B,cACtCA,IAAS,wBAAgC,WACzCA,IAAS,0BAAkC,WACxCH,EAA2BC,CAAM,CAC1C,CAwBA,SAAgBG,EACd,EACA,EACA,EACyC,CACzC,GAAM,CAAE,aAAY,QAASC,EACvBC,EAAWC,EAASC,CAAI,EAC9B,GAAIC,GAAc,KAAOA,EAAa,KAAOH,GAAWI,QAAa,GAAO,CAC1E,IAAMC,EAAOJ,EAASD,EAASM,IAAO,EAChCC,EACJ,OAAOF,GAAOG,eAAqB,SAAWH,EAAKG,cAAmB,IAAA,GACxE,GAAIC,IAAW,eAAgB,CAC7B,IAAMC,EAAeC,EAAWC,UAChC,GACEP,GAAOQ,OAAY,UACnB,OAAOH,GAAiB,UACxBL,EAAKO,YAAiBF,EAEtB,MAAM,IAAII,EACR,oBACA,CAAC,EACD,CACE,OAAQX,EACR,OACA,OAAQ,GACR,GAAI,OAAOO,GAAiB,SAAW,CAAE,UAAWA,CAAa,EAAI,CAAC,CACxE,CACF,EAEF,MAAO,CACL,GAAI,GACJ,SAAUH,GAAoB,GAC9B,OAAQ,CAAE,UAAWG,EAAc,qBAAsBH,GAAoB,IAAK,CACpF,CACF,CACA,GAAIF,GAAOQ,OAAY,OAAQ,MAAO,CAAE,GAAI,GAAM,SAAUN,GAAoB,EAAG,CACrF,CAEA,IAAMQ,EAAOnB,EAA0BO,EAAYa,EAAkBd,EAAM,MAAM,CAAC,EASlF,OARIa,IAAS,WACJ,CACL,GAAI,GACJ,KAAM,WACN,UAAW,GACX,QAASC,EAAkBd,EAAM,SAAS,GAAK,EACjD,EAEKa,IAAS,cACZ,CAAE,GAAI,GAAO,OAAM,UAAW,EAAK,EACnC,CAAE,GAAI,GAAO,OAAM,UAAW,EAAM,CAC1C,CAEA,SAASd,EAAS,EAAqD,CACrE,OAAO,OAAOgB,GAAU,UAAYA,EAC/BA,EACD,IAAA,EACN"}
@@ -1 +1 @@
1
- {"version":3,"file":"storage_adapters.js","names":["storageAdapterFromChromeStorage","areaNow","area","items","key","value","webStorageAdapter","raw"],"sources":["../../../src/classes/storage_adapters.ts"],"sourcesContent":["/**\n * Async key/value storage the pending-swap store persists through. It stores STRUCTURED VALUES\n * (the store never serializes), so each platform decides how a value lands on disk:\n *\n * - `chrome.storage.local`: `get: async (k) => (await chrome.storage.local.get(k))[k]`,\n * `set: (k, v) => chrome.storage.local.set({ [k]: v })`. This is the wallet's stored shape.\n * {@link storageAdapterFromChromeStorage} builds it.\n * - `localStorage` (frontend_v2): JSON text under the key. {@link webStorageAdapter} builds it,\n * and returns CORRUPT JSON as a value (the raw string), not as a rejection: a value that is not\n * an array reads as \"no entries\" and the next write repairs it, whereas a `get` that rejects\n * forever makes every `remember` a no-op.\n * - React Native: AsyncStorage with the same JSON wrapping and the same corrupt-JSON rule.\n *\n * `get` resolves `undefined` for a missing key. Either method may reject; the store treats that\n * as \"storage unavailable\" and never lets it surface (see `PendingSwapStore`).\n *\n * This module imports nothing, so an app shell that only builds an adapter pays for the adapter.\n */\nexport interface StorageAdapter {\n get(key: string): Promise<unknown>;\n set(key: string, value: unknown): Promise<void>;\n}\n\n/**\n * The part of a `chrome.storage` area ({@link storageAdapterFromChromeStorage}) the adapter uses,\n * written structurally so the SDK needs no `chrome` types: `chrome.storage.local`,\n * `chrome.storage.session`, `browser.storage.local` (webextension-polyfill) and any object with\n * these two promise-returning methods satisfy it. It is the Manifest V3 promise form: the methods\n * are called WITHOUT a callback.\n */\nexport interface ChromeStorageAreaLike {\n get(key: string): Promise<Record<string, unknown>>;\n set(items: Record<string, unknown>): Promise<void>;\n}\n\n/**\n * A {@link StorageAdapter} over a `chrome.storage` area, the shape the wallet stores pending swaps\n * in: the value lands under the key as the structured value it is (the browser clones it; nothing\n * is serialized here). A missing key resolves `undefined`; a rejection of the area reaches the\n * store, which treats it as \"storage unavailable\".\n *\n * Pass a FUNCTION that returns the area to look it up at each call instead of when the adapter is\n * built: a module that is imported where `chrome` does not exist yet (a test, a closing extension\n * context) then fails only when the store touches storage, and that failure (a throw inside the\n * function) is the same \"storage unavailable\" rejection.\n *\n * @example\n * ```ts\n * const store = new PendingSwapStore(storageAdapterFromChromeStorage(chrome.storage.local));\n * const lazy = new PendingSwapStore(storageAdapterFromChromeStorage(() => chrome.storage.local));\n * ```\n */\nexport function storageAdapterFromChromeStorage(\n area: ChromeStorageAreaLike | (() => ChromeStorageAreaLike),\n): StorageAdapter {\n const areaNow = (): ChromeStorageAreaLike => (typeof area === 'function' ? area() : area);\n return {\n async get(key) {\n const items = await areaNow().get(key);\n return Object.hasOwn(items, key) ? items[key] : undefined;\n },\n async set(key, value) {\n await areaNow().set({ [key]: value });\n },\n };\n}\n\n/**\n * The part of a Web Storage area ({@link webStorageAdapter}) the adapter uses, written\n * structurally so the SDK needs no DOM types: `localStorage`, `sessionStorage` and any object with\n * these two synchronous methods (a React Native MMKV wrapper, a test double) satisfy it.\n */\nexport interface WebStorageLike {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n}\n\n/**\n * A {@link StorageAdapter} over a Web Storage area (`localStorage`, `sessionStorage`), the shape\n * frontend_v2 stores pending swaps in: the value lands under the key as JSON text. A missing key\n * resolves `undefined`. CORRUPT JSON resolves as the raw string, never a rejection (the store\n * reads a value that is not an array as \"no entries\" and the next write repairs it, whereas a\n * `get` that rejects forever would make every `remember` a no-op). A throwing area (storage blocked\n * by the browser, quota exceeded on `setItem`) rejects, which the store treats as \"storage\n * unavailable\".\n *\n * Pass a FUNCTION that returns the area to look it up at each call instead of when the adapter is\n * built: where `localStorage` does not exist (server rendering, a test, a blocked context)\n * the access throws inside the function, which is the same \"storage unavailable\" rejection, and\n * the module that builds the adapter still loads.\n *\n * @example\n * ```ts\n * const store = new PendingSwapStore(webStorageAdapter(() => localStorage));\n * ```\n */\nexport function webStorageAdapter(area: WebStorageLike | (() => WebStorageLike)): StorageAdapter {\n const areaNow = (): WebStorageLike => (typeof area === 'function' ? area() : area);\n return {\n async get(key) {\n const raw = areaNow().getItem(key);\n if (raw === null) return undefined;\n try {\n return JSON.parse(raw) as unknown;\n } catch {\n return raw;\n }\n },\n async set(key, value) {\n areaNow().setItem(key, JSON.stringify(value));\n },\n };\n}\n"],"mappings":"AAoDA,SAAgBA,EACd,EACgB,CAChB,IAAMC,MAAwC,OAAOC,GAAS,WAAaA,EAAK,EAAIA,EACpF,MAAO,CACL,MAAM,IAAI,EAAK,CACb,IAAMC,EAAQ,MAAMF,EAAQ,CAAC,CAAC,IAAIG,CAAG,EACrC,OAAO,OAAO,OAAOD,EAAOC,CAAG,EAAID,EAAMC,GAAO,IAAA,EAClD,EACA,MAAM,IAAI,EAAK,EAAO,CACpB,MAAMH,EAAQ,CAAC,CAAC,IAAI,EAAGG,GAAMC,CAAM,CAAC,CACtC,CACF,CACF,CA+BA,SAAgBC,EAAkB,EAA+D,CAC/F,IAAML,MAAiC,OAAOC,GAAS,WAAaA,EAAK,EAAIA,EAC7E,MAAO,CACL,MAAM,IAAI,EAAK,CACb,IAAMK,EAAMN,EAAQ,CAAC,CAAC,QAAQG,CAAG,EACjC,GAAIG,IAAQ,KACZ,GAAI,CACF,OAAO,KAAK,MAAMA,CAAG,CACvB,MAAQ,CACN,OAAOA,CACT,CACF,EACA,MAAM,IAAI,EAAK,EAAO,CACpBN,EAAQ,CAAC,CAAC,QAAQG,EAAK,KAAK,UAAUC,CAAK,CAAC,CAC9C,CACF,CACF"}
1
+ {"version":3,"file":"storage_adapters.js","names":["storageAdapterFromChromeStorage","areaNow","area","items","key","value","webStorageAdapter","raw"],"sources":["../../../src/classes/storage_adapters.ts"],"sourcesContent":["/**\n * Async key/value storage the pending-swap store persists through. It stores STRUCTURED VALUES\n * (the store never serializes), so each platform decides how a value lands on disk:\n *\n * - `chrome.storage.local`: `get: async (k) => (await chrome.storage.local.get(k))[k]`,\n * `set: (k, v) => chrome.storage.local.set({ [k]: v })`. This is the wallet's stored shape.\n * {@link storageAdapterFromChromeStorage} builds it.\n * - `localStorage` (a web app): JSON text under the key. {@link webStorageAdapter} builds it,\n * and returns CORRUPT JSON as a value (the raw string), not as a rejection: a value that is not\n * an array reads as \"no entries\" and the next write repairs it, whereas a `get` that rejects\n * forever makes every `remember` a no-op.\n * - React Native: AsyncStorage with the same JSON wrapping and the same corrupt-JSON rule.\n *\n * `get` resolves `undefined` for a missing key. Either method may reject; the store treats that\n * as \"storage unavailable\" and never lets it surface (see `PendingSwapStore`).\n *\n * This module imports nothing, so an app shell that only builds an adapter pays for the adapter.\n */\nexport interface StorageAdapter {\n get(key: string): Promise<unknown>;\n set(key: string, value: unknown): Promise<void>;\n}\n\n/**\n * The part of a `chrome.storage` area ({@link storageAdapterFromChromeStorage}) the adapter uses,\n * written structurally so the SDK needs no `chrome` types: `chrome.storage.local`,\n * `chrome.storage.session`, `browser.storage.local` (webextension-polyfill) and any object with\n * these two promise-returning methods satisfy it. It is the Manifest V3 promise form: the methods\n * are called WITHOUT a callback.\n */\nexport interface ChromeStorageAreaLike {\n get(key: string): Promise<Record<string, unknown>>;\n set(items: Record<string, unknown>): Promise<void>;\n}\n\n/**\n * A {@link StorageAdapter} over a `chrome.storage` area, the shape the wallet stores pending swaps\n * in: the value lands under the key as the structured value it is (the browser clones it; nothing\n * is serialized here). A missing key resolves `undefined`; a rejection of the area reaches the\n * store, which treats it as \"storage unavailable\".\n *\n * Pass a FUNCTION that returns the area to look it up at each call instead of when the adapter is\n * built: a module that is imported where `chrome` does not exist yet (a test, a closing extension\n * context) then fails only when the store touches storage, and that failure (a throw inside the\n * function) is the same \"storage unavailable\" rejection.\n *\n * @example\n * ```ts\n * const store = new PendingSwapStore(storageAdapterFromChromeStorage(chrome.storage.local));\n * const lazy = new PendingSwapStore(storageAdapterFromChromeStorage(() => chrome.storage.local));\n * ```\n */\nexport function storageAdapterFromChromeStorage(\n area: ChromeStorageAreaLike | (() => ChromeStorageAreaLike),\n): StorageAdapter {\n const areaNow = (): ChromeStorageAreaLike => (typeof area === 'function' ? area() : area);\n return {\n async get(key) {\n const items = await areaNow().get(key);\n return Object.hasOwn(items, key) ? items[key] : undefined;\n },\n async set(key, value) {\n await areaNow().set({ [key]: value });\n },\n };\n}\n\n/**\n * The part of a Web Storage area ({@link webStorageAdapter}) the adapter uses, written\n * structurally so the SDK needs no DOM types: `localStorage`, `sessionStorage` and any object with\n * these two synchronous methods (a React Native MMKV wrapper, a test double) satisfy it.\n */\nexport interface WebStorageLike {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n}\n\n/**\n * A {@link StorageAdapter} over a Web Storage area (`localStorage`, `sessionStorage`), the shape\n * a web app stores pending swaps in: the value lands under the key as JSON text. A missing key\n * resolves `undefined`. CORRUPT JSON resolves as the raw string, never a rejection (the store\n * reads a value that is not an array as \"no entries\" and the next write repairs it, whereas a\n * `get` that rejects forever would make every `remember` a no-op). A throwing area (storage blocked\n * by the browser, quota exceeded on `setItem`) rejects, which the store treats as \"storage\n * unavailable\".\n *\n * Pass a FUNCTION that returns the area to look it up at each call instead of when the adapter is\n * built: where `localStorage` does not exist (server rendering, a test, a blocked context)\n * the access throws inside the function, which is the same \"storage unavailable\" rejection, and\n * the module that builds the adapter still loads.\n *\n * @example\n * ```ts\n * const store = new PendingSwapStore(webStorageAdapter(() => localStorage));\n * ```\n */\nexport function webStorageAdapter(area: WebStorageLike | (() => WebStorageLike)): StorageAdapter {\n const areaNow = (): WebStorageLike => (typeof area === 'function' ? area() : area);\n return {\n async get(key) {\n const raw = areaNow().getItem(key);\n if (raw === null) return undefined;\n try {\n return JSON.parse(raw) as unknown;\n } catch {\n return raw;\n }\n },\n async set(key, value) {\n areaNow().setItem(key, JSON.stringify(value));\n },\n };\n}\n"],"mappings":"AAoDA,SAAgBA,EACd,EACgB,CAChB,IAAMC,MAAwC,OAAOC,GAAS,WAAaA,EAAK,EAAIA,EACpF,MAAO,CACL,MAAM,IAAI,EAAK,CACb,IAAMC,EAAQ,MAAMF,EAAQ,CAAC,CAAC,IAAIG,CAAG,EACrC,OAAO,OAAO,OAAOD,EAAOC,CAAG,EAAID,EAAMC,GAAO,IAAA,EAClD,EACA,MAAM,IAAI,EAAK,EAAO,CACpB,MAAMH,EAAQ,CAAC,CAAC,IAAI,EAAGG,GAAMC,CAAM,CAAC,CACtC,CACF,CACF,CA+BA,SAAgBC,EAAkB,EAA+D,CAC/F,IAAML,MAAiC,OAAOC,GAAS,WAAaA,EAAK,EAAIA,EAC7E,MAAO,CACL,MAAM,IAAI,EAAK,CACb,IAAMK,EAAMN,EAAQ,CAAC,CAAC,QAAQG,CAAG,EACjC,GAAIG,IAAQ,KACZ,GAAI,CACF,OAAO,KAAK,MAAMA,CAAG,CACvB,MAAQ,CACN,OAAOA,CACT,CACF,EACA,MAAM,IAAI,EAAK,EAAO,CACpBN,EAAQ,CAAC,CAAC,QAAQG,EAAK,KAAK,UAAUC,CAAK,CAAC,CAC9C,CACF,CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"swaps.js","names":["_defineProperty","swapRouteFromQuote","validateFee","quote","assertCanonicalPair","tokenIn","tokenOut","Swaps","gateway","symbols","signer","walletAddress","userRef","resolveUniqueKeyPrefix","options","fee","amount","shadowFailures","uniqueKey","createUniqueKey","assertUniqueKey","tokenInSymbol","tradingSymbolOf","tokenOutSymbol","ordered","buildRequestTradeDto","signed","dto","result","readShadowSubmitOutcome","assertCanonicalV2Dto","GSwapSDKError","signPreSend","pool","readOrderedSymbols","orderSymbols","SubmittedTransaction"],"sources":["../../../src/classes/swaps.ts"],"sourcesContent":["import {\n assertUniqueKey,\n createUniqueKey,\n resolveUniqueKeyPrefix,\n signPreSend,\n type GalaChainSigner,\n type HttpRequestor,\n type UniqueKeyOptions,\n type WireOptions,\n} from '@gala-chain/gala-core-sdk';\nimport type { SwapAmount } from '../types/amounts.js';\nimport type { FEE_TIER } from '../types/fees.js';\nimport { orderSymbols, readOrderedSymbols, type TokenRef } from '../utils/ordering.js';\nimport { assertCanonicalV2Dto, buildRequestTradeDto } from '../utils/dto_builders.js';\nimport { shadowFailures } from '../utils/shadow_errors.js';\nimport { assertCanonicalPair, userRef, validateFee } from '../utils/validation.js';\nimport type { ChainSubmitOptions } from './gateway.js';\nimport { GSwapSDKError } from './gswap_sdk_error.js';\nimport {\n readShadowSubmitOutcome,\n type ShadowSubmitOutcome,\n type ShadowSubmitResult,\n} from './shadow_submit.js';\nimport { SubmittedTransaction } from './submitted_transaction.js';\nimport type { ConfirmOptions } from '../types/confirm_options.js';\nimport type { RequestTradeDTO } from '../types/v2_dtos.js';\nimport type { AppliedTrade, QuoteResult } from '../types/v2_results.js';\nimport { tradingSymbolOf, type TokenResolver } from './symbols.js';\n\ninterface SwapGateway {\n submit: (\n method: 'RequestTrade',\n signedBody: Record<string, unknown>,\n options?: ChainSubmitOptions,\n ) => Promise<SubmittedTransaction<AppliedTrade>>;\n submitShadow: (\n method: 'RequestTrade',\n signedBody: Record<string, unknown>,\n options?: ChainSubmitOptions,\n ) => Promise<ShadowSubmitResult>;\n readonly dexBackendBaseUrl: string;\n readonly httpRequestor: HttpRequestor;\n readonly requestTimeoutMs: number;\n readonly wire?: WireOptions | undefined;\n}\n\n/** The canonical pool a swap trades in, as a quote names it. */\nexport interface SwapPool {\n token0: string;\n token1: string;\n}\n\n/** Options of a swap. */\nexport interface SwapOptions {\n /**\n * The attempt's `uniqueKey` (1-128 characters of `A-Za-z0-9._:-`). Default: a fresh\n * `<uniqueKeyPrefix><uuid>`. Supply one to know the key before signing (to remember it, to\n * show it), or to retry the SAME attempt after a lost response; never reuse it for another trade.\n */\n uniqueKey?: string | undefined;\n /**\n * Pin the pool: take `token0` / `token1` verbatim from the quote the user saw, instead of\n * ordering `tokenIn` / `tokenOut` by string comparison. `tokenIn` and `tokenOut` must be exactly\n * these two symbols, and `token0` must sort before `token1`. See {@link swapRouteFromQuote}.\n */\n pool?: SwapPool | undefined;\n}\n\n/** Everything {@link Swaps.swap} needs to trade in the pool a quote priced. */\nexport interface SwapRoute {\n tokenIn: string;\n tokenOut: string;\n fee: FEE_TIER;\n pool: SwapPool;\n}\n\n/**\n * The route of a quote: its fee tier and pool, with `tokenIn` / `tokenOut` taken from\n * `tokenInIsToken0`, nothing re-derived. Use it to swap in exactly the pool a quote priced:\n *\n * @example\n * ```ts\n * const route = swapRouteFromQuote(quote);\n * await swaps.swap(route.tokenIn, route.tokenOut, route.fee, { exactIn: '100' }, { pool: route.pool });\n * ```\n */\nexport function swapRouteFromQuote(\n quote: Pick<QuoteResult, 'token0Symbol' | 'token1Symbol' | 'tokenInIsToken0' | 'fee'>,\n): SwapRoute {\n validateFee(quote.fee);\n assertCanonicalPair(quote.token0Symbol, quote.token1Symbol);\n const [tokenIn, tokenOut] = quote.tokenInIsToken0\n ? [quote.token0Symbol, quote.token1Symbol]\n : [quote.token1Symbol, quote.token0Symbol];\n return {\n tokenIn,\n tokenOut,\n fee: quote.fee,\n pool: { token0: quote.token0Symbol, token1: quote.token1Symbol },\n };\n}\n\n/** Executes queued current-contract RequestTrade operations through the Chain Gateway. */\nexport class Swaps {\n /**\n * Creates a swap service.\n *\n * @example\n * ```ts\n * const swaps = new Swaps(gateway, symbols, signer, 'client|alice');\n * ```\n */\n constructor(\n private readonly gateway: SwapGateway,\n private readonly symbols: TokenResolver,\n private readonly signer?: GalaChainSigner,\n walletAddress?: string,\n options: UniqueKeyOptions = {},\n ) {\n this.walletAddress =\n walletAddress === undefined ? undefined : userRef(walletAddress, 'walletAddress');\n this.uniqueKeyPrefix = resolveUniqueKeyPrefix(options.uniqueKeyPrefix);\n }\n\n /** The signing wallet in the gateway's spelling (`eth|<EIP-55>` or `client|<id>`). */\n private readonly walletAddress: string | undefined;\n private readonly uniqueKeyPrefix: string;\n\n /**\n * Signs and submits an exact-input or exact-output RequestTrade DTO. The returned transaction\n * is QUEUED; call `await tx.confirm()` to obtain the executed trade or chain rejection.\n *\n * @example\n * ```ts\n * const submitted = await swaps.swap('GALA', 'GUSDC', 3000, {\n * exactIn: '100',\n * amountOutMinimum: '14',\n * });\n * ```\n */\n public async swap(\n tokenIn: TokenRef,\n tokenOut: TokenRef,\n fee: FEE_TIER,\n amount: SwapAmount,\n options?: SwapOptions,\n ): Promise<SubmittedTransaction<AppliedTrade>> {\n return this.submitSwap(await this.buildSwap(tokenIn, tokenOut, fee, amount, options));\n }\n\n /**\n * Signs and submits a SHADOW (dark) swap: the same RequestTrade as {@link swap}, relayed with\n * `X-Shadow: true` so defi-backend marks it private before it reaches the contract. Requires a\n * `walletAddress`. Resolves the normalized outcome instead of throwing on a gateway refusal:\n * `ok: false` carries `unavailable` (retryable), `disabled`, `rejected` (with the gateway's\n * message) or `unknown`. A success carries `queued`, to be confirmed with\n * `confirmSwap(queued.uniqueKey)`; never fall back to a public swap on failure. Whatever this\n * THROWS (a validation error, the signer, a lost connection, `SHADOW_NOT_QUEUED`) has\n * `error.shadow === true` (`isShadowError`), so an app can apply its dark-swap policy to it.\n *\n * @example\n * ```ts\n * const outcome = await swaps.swapShadow('GALA', 'GUSDC', 3000, { exactIn: '100' });\n * if (outcome.ok) await swaps.confirmSwap(outcome.queued.uniqueKey);\n * ```\n */\n public async swapShadow(\n tokenIn: TokenRef,\n tokenOut: TokenRef,\n fee: FEE_TIER,\n amount: SwapAmount,\n options?: SwapOptions,\n ): Promise<ShadowSubmitOutcome> {\n return shadowFailures(async () =>\n this.submitSwapShadow(await this.buildSwap(tokenIn, tokenOut, fee, amount, options)),\n );\n }\n\n /**\n * Builds the RequestTrade DTO that {@link swap} would sign, without signing or sending it: to\n * show in an approval popup, to remember its `uniqueKey`, or to sign elsewhere. Submit exactly\n * this DTO with {@link submitSwap}. The pure counterpart, with canonical inputs and no\n * resolution, is `buildRequestTradeDto`.\n *\n * @example\n * ```ts\n * const dto = await swaps.buildSwap('GALA', 'GUSDC', 3000, { exactIn: '100' });\n * showApproval(dto);\n * const submitted = await swaps.submitSwap(dto);\n * ```\n */\n public async buildSwap(\n tokenIn: TokenRef,\n tokenOut: TokenRef,\n fee: FEE_TIER,\n amount: SwapAmount,\n options?: SwapOptions,\n ): Promise<RequestTradeDTO> {\n validateFee(fee);\n const uniqueKey = options?.uniqueKey ?? createUniqueKey(this.uniqueKeyPrefix);\n assertUniqueKey(uniqueKey);\n const tokenInSymbol = await tradingSymbolOf(this.symbols, tokenIn);\n const tokenOutSymbol = await tradingSymbolOf(this.symbols, tokenOut);\n const ordered = this.orientPool(tokenInSymbol, tokenOutSymbol, options?.pool);\n return buildRequestTradeDto({\n token0: ordered.token0,\n token1: ordered.token1,\n fee,\n tokenInIsToken0: tokenInSymbol === ordered.token0,\n uniqueKey,\n ...amount,\n });\n }\n\n /**\n * Signs and submits a DTO built by {@link buildSwap} or `buildRequestTradeDto`. The DTO must be\n * exactly what the builder produces (no signature, canonical decimals, builder key order), so\n * what an app showed is what gets signed.\n *\n * @example\n * ```ts\n * const submitted = await swaps.submitSwap(dto);\n * const applied = await submitted.confirm();\n * ```\n */\n public async submitSwap(dto: RequestTradeDTO): Promise<SubmittedTransaction<AppliedTrade>> {\n const signed = await this.signRequestTrade(dto);\n return this.gateway.submit('RequestTrade', signed, this.submitOptions());\n }\n\n /** Signs and shadow-submits a DTO built by {@link buildSwap}; see {@link swapShadow}. */\n public async submitSwapShadow(dto: RequestTradeDTO): Promise<ShadowSubmitOutcome> {\n return shadowFailures(async () => {\n const signed = await this.signRequestTrade(dto);\n const result = await this.gateway.submitShadow('RequestTrade', signed, this.submitOptions());\n return readShadowSubmitOutcome('RequestTrade', signed, result);\n });\n }\n\n private submitOptions(): ChainSubmitOptions {\n return this.walletAddress === undefined ? {} : { walletAddress: this.walletAddress };\n }\n\n private async signRequestTrade(dto: RequestTradeDTO): Promise<Record<string, unknown>> {\n assertCanonicalV2Dto('RequestTrade', dto);\n if (this.signer === undefined) throw GSwapSDKError.noSignerError();\n return signPreSend(this.signer, 'RequestTrade', dto);\n }\n\n /** The pool's two symbols in contract order: the quote's own when pinned, else string order. */\n private orientPool(\n tokenInSymbol: string,\n tokenOutSymbol: string,\n pool: SwapPool | undefined,\n ): SwapPool {\n if (pool === undefined) {\n return readOrderedSymbols(orderSymbols(tokenInSymbol, tokenOutSymbol));\n }\n const sameSet =\n (tokenInSymbol === pool.token0 && tokenOutSymbol === pool.token1) ||\n (tokenInSymbol === pool.token1 && tokenOutSymbol === pool.token0);\n if (!sameSet) {\n throw new GSwapSDKError('POOL_MISMATCH', {\n tokenIn: tokenInSymbol,\n tokenOut: tokenOutSymbol,\n poolToken0: pool.token0,\n poolToken1: pool.token1,\n });\n }\n return { token0: pool.token0, token1: pool.token1 };\n }\n\n /**\n * Confirm a swap by its durable `uniqueKey` without resubmitting it. This is the recovery\n * path after a `CONFIRMATION_TIMEOUT`, a `SUBMISSION_OUTCOME_UNKNOWN`, a `409` replay\n * conflict, or a process restart: it reads the same apply-results record that\n * `SubmittedTransaction.confirm()` reads (retained for 7 days) and resolves the applied trade\n * or throws the chain's rejection. The first read happens immediately unless\n * `initialDelayMs` is given.\n *\n * @example\n * ```ts\n * const applied = await swaps.confirmSwap('galaswap-operation-2f1c…');\n * ```\n */\n public async confirmSwap(uniqueKey: string, options?: ConfirmOptions): Promise<AppliedTrade> {\n assertUniqueKey(uniqueKey);\n const transaction = new SubmittedTransaction<AppliedTrade>({\n method: 'RequestTrade',\n uniqueKey,\n transactionId: null,\n mode: 'queued',\n result: undefined,\n dexBackendBaseUrl: this.gateway.dexBackendBaseUrl,\n httpRequestor: this.gateway.httpRequestor,\n requestTimeoutMs: this.gateway.requestTimeoutMs,\n wire: this.gateway.wire,\n });\n return transaction.confirm({ initialDelayMs: 0, ...options });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"swaps.js","names":["_defineProperty","swapRouteFromQuote","validateFee","quote","assertCanonicalPair","tokenIn","tokenOut","Swaps","gateway","symbols","signer","walletAddress","userRef","resolveUniqueKeyPrefix","options","fee","amount","shadowFailures","uniqueKey","createUniqueKey","assertUniqueKey","tokenInSymbol","tradingSymbolOf","tokenOutSymbol","ordered","buildRequestTradeDto","signed","dto","result","readShadowSubmitOutcome","assertCanonicalV2Dto","GSwapSDKError","signPreSend","pool","readOrderedSymbols","orderSymbols","SubmittedTransaction"],"sources":["../../../src/classes/swaps.ts"],"sourcesContent":["import {\n assertUniqueKey,\n createUniqueKey,\n resolveUniqueKeyPrefix,\n signPreSend,\n type GalaChainSigner,\n type HttpRequestor,\n type UniqueKeyOptions,\n type WireOptions,\n} from '@gala-chain/gala-core-sdk';\nimport type { SwapAmount } from '../types/amounts.js';\nimport type { FEE_TIER } from '../types/fees.js';\nimport { orderSymbols, readOrderedSymbols, type TokenRef } from '../utils/ordering.js';\nimport { assertCanonicalV2Dto, buildRequestTradeDto } from '../utils/dto_builders.js';\nimport { shadowFailures } from '../utils/shadow_errors.js';\nimport { assertCanonicalPair, userRef, validateFee } from '../utils/validation.js';\nimport type { ChainSubmitOptions } from './gateway.js';\nimport { GSwapSDKError } from './gswap_sdk_error.js';\nimport {\n readShadowSubmitOutcome,\n type ShadowSubmitOutcome,\n type ShadowSubmitResult,\n} from './shadow_submit.js';\nimport { SubmittedTransaction } from './submitted_transaction.js';\nimport type { ConfirmOptions } from '../types/confirm_options.js';\nimport type { RequestTradeDTO } from '../types/v2_dtos.js';\nimport type { AppliedTrade, QuoteResult } from '../types/v2_results.js';\nimport { tradingSymbolOf, type TokenResolver } from './symbols.js';\n\ninterface SwapGateway {\n submit: (\n method: 'RequestTrade',\n signedBody: Record<string, unknown>,\n options?: ChainSubmitOptions,\n ) => Promise<SubmittedTransaction<AppliedTrade>>;\n submitShadow: (\n method: 'RequestTrade',\n signedBody: Record<string, unknown>,\n options?: ChainSubmitOptions,\n ) => Promise<ShadowSubmitResult>;\n readonly dexBackendBaseUrl: string;\n readonly httpRequestor: HttpRequestor;\n readonly requestTimeoutMs: number;\n readonly wire?: WireOptions | undefined;\n}\n\n/** The canonical pool a swap trades in, as a quote names it. */\nexport interface SwapPool {\n token0: string;\n token1: string;\n}\n\n/** Options of a swap. */\nexport interface SwapOptions {\n /**\n * The attempt's `uniqueKey` (1-128 characters of `A-Za-z0-9._:-`). Default: a fresh\n * `<uniqueKeyPrefix><uuid>`. Supply one to know the key before signing (to remember it, to\n * show it), or to retry the SAME attempt after a lost response; never reuse it for another trade.\n */\n uniqueKey?: string | undefined;\n /**\n * Pin the pool: take `token0` / `token1` verbatim from the quote the user saw, instead of\n * ordering `tokenIn` / `tokenOut` by string comparison. `tokenIn` and `tokenOut` must be exactly\n * these two symbols, and `token0` must sort before `token1`. See {@link swapRouteFromQuote}.\n */\n pool?: SwapPool | undefined;\n}\n\n/** Everything {@link Swaps.swap} needs to trade in the pool a quote priced. */\nexport interface SwapRoute {\n tokenIn: string;\n tokenOut: string;\n fee: FEE_TIER;\n pool: SwapPool;\n}\n\n/**\n * The route of a quote: its fee tier and pool, with `tokenIn` / `tokenOut` taken from\n * `tokenInIsToken0`, nothing re-derived. Use it to swap in exactly the pool a quote priced:\n *\n * @example\n * ```ts\n * const route = swapRouteFromQuote(quote);\n * await swaps.swap(route.tokenIn, route.tokenOut, route.fee, { exactIn: '100' }, { pool: route.pool });\n * ```\n */\nexport function swapRouteFromQuote(\n quote: Pick<QuoteResult, 'token0Symbol' | 'token1Symbol' | 'tokenInIsToken0' | 'fee'>,\n): SwapRoute {\n validateFee(quote.fee);\n assertCanonicalPair(quote.token0Symbol, quote.token1Symbol);\n const [tokenIn, tokenOut] = quote.tokenInIsToken0\n ? [quote.token0Symbol, quote.token1Symbol]\n : [quote.token1Symbol, quote.token0Symbol];\n return {\n tokenIn,\n tokenOut,\n fee: quote.fee,\n pool: { token0: quote.token0Symbol, token1: quote.token1Symbol },\n };\n}\n\n/** Executes queued current-contract RequestTrade operations through the Chain Gateway. */\nexport class Swaps {\n /**\n * Creates a swap service.\n *\n * @example\n * ```ts\n * const swaps = new Swaps(gateway, symbols, signer, 'client|alice');\n * ```\n */\n constructor(\n private readonly gateway: SwapGateway,\n private readonly symbols: TokenResolver,\n private readonly signer?: GalaChainSigner,\n walletAddress?: string,\n options: UniqueKeyOptions = {},\n ) {\n this.walletAddress =\n walletAddress === undefined ? undefined : userRef(walletAddress, 'walletAddress');\n this.uniqueKeyPrefix = resolveUniqueKeyPrefix(options.uniqueKeyPrefix);\n }\n\n /** The signing wallet in the gateway's spelling (`eth|<EIP-55>` or `client|<id>`). */\n private readonly walletAddress: string | undefined;\n private readonly uniqueKeyPrefix: string;\n\n /**\n * Signs and submits an exact-input or exact-output RequestTrade DTO. The returned transaction\n * is QUEUED; call `await tx.confirm()` to obtain the executed trade or chain rejection.\n *\n * @example\n * ```ts\n * const submitted = await swaps.swap('GALA', 'GUSDC', 3000, {\n * exactIn: '100',\n * amountOutMinimum: '14',\n * });\n * ```\n */\n public async swap(\n tokenIn: TokenRef,\n tokenOut: TokenRef,\n fee: FEE_TIER,\n amount: SwapAmount,\n options?: SwapOptions,\n ): Promise<SubmittedTransaction<AppliedTrade>> {\n return this.submitSwap(await this.buildSwap(tokenIn, tokenOut, fee, amount, options));\n }\n\n /**\n * Signs and submits a SHADOW (dark) swap: the same RequestTrade as {@link swap}, relayed with\n * `X-Shadow: true` so the swap backend marks it private before it reaches the contract. Requires a\n * `walletAddress`. Resolves the normalized outcome instead of throwing on a gateway refusal:\n * `ok: false` carries `unavailable` (retryable), `disabled`, `rejected` (with the gateway's\n * message) or `unknown`. A success carries `queued`, to be confirmed with\n * `confirmSwap(queued.uniqueKey)`; never fall back to a public swap on failure. Whatever this\n * THROWS (a validation error, the signer, a lost connection, `SHADOW_NOT_QUEUED`) has\n * `error.shadow === true` (`isShadowError`), so an app can apply its dark-swap policy to it.\n *\n * @example\n * ```ts\n * const outcome = await swaps.swapShadow('GALA', 'GUSDC', 3000, { exactIn: '100' });\n * if (outcome.ok) await swaps.confirmSwap(outcome.queued.uniqueKey);\n * ```\n */\n public async swapShadow(\n tokenIn: TokenRef,\n tokenOut: TokenRef,\n fee: FEE_TIER,\n amount: SwapAmount,\n options?: SwapOptions,\n ): Promise<ShadowSubmitOutcome> {\n return shadowFailures(async () =>\n this.submitSwapShadow(await this.buildSwap(tokenIn, tokenOut, fee, amount, options)),\n );\n }\n\n /**\n * Builds the RequestTrade DTO that {@link swap} would sign, without signing or sending it: to\n * show in an approval popup, to remember its `uniqueKey`, or to sign elsewhere. Submit exactly\n * this DTO with {@link submitSwap}. The pure counterpart, with canonical inputs and no\n * resolution, is `buildRequestTradeDto`.\n *\n * @example\n * ```ts\n * const dto = await swaps.buildSwap('GALA', 'GUSDC', 3000, { exactIn: '100' });\n * showApproval(dto);\n * const submitted = await swaps.submitSwap(dto);\n * ```\n */\n public async buildSwap(\n tokenIn: TokenRef,\n tokenOut: TokenRef,\n fee: FEE_TIER,\n amount: SwapAmount,\n options?: SwapOptions,\n ): Promise<RequestTradeDTO> {\n validateFee(fee);\n const uniqueKey = options?.uniqueKey ?? createUniqueKey(this.uniqueKeyPrefix);\n assertUniqueKey(uniqueKey);\n const tokenInSymbol = await tradingSymbolOf(this.symbols, tokenIn);\n const tokenOutSymbol = await tradingSymbolOf(this.symbols, tokenOut);\n const ordered = this.orientPool(tokenInSymbol, tokenOutSymbol, options?.pool);\n return buildRequestTradeDto({\n token0: ordered.token0,\n token1: ordered.token1,\n fee,\n tokenInIsToken0: tokenInSymbol === ordered.token0,\n uniqueKey,\n ...amount,\n });\n }\n\n /**\n * Signs and submits a DTO built by {@link buildSwap} or `buildRequestTradeDto`. The DTO must be\n * exactly what the builder produces (no signature, canonical decimals, builder key order), so\n * what an app showed is what gets signed.\n *\n * @example\n * ```ts\n * const submitted = await swaps.submitSwap(dto);\n * const applied = await submitted.confirm();\n * ```\n */\n public async submitSwap(dto: RequestTradeDTO): Promise<SubmittedTransaction<AppliedTrade>> {\n const signed = await this.signRequestTrade(dto);\n return this.gateway.submit('RequestTrade', signed, this.submitOptions());\n }\n\n /** Signs and shadow-submits a DTO built by {@link buildSwap}; see {@link swapShadow}. */\n public async submitSwapShadow(dto: RequestTradeDTO): Promise<ShadowSubmitOutcome> {\n return shadowFailures(async () => {\n const signed = await this.signRequestTrade(dto);\n const result = await this.gateway.submitShadow('RequestTrade', signed, this.submitOptions());\n return readShadowSubmitOutcome('RequestTrade', signed, result);\n });\n }\n\n private submitOptions(): ChainSubmitOptions {\n return this.walletAddress === undefined ? {} : { walletAddress: this.walletAddress };\n }\n\n private async signRequestTrade(dto: RequestTradeDTO): Promise<Record<string, unknown>> {\n assertCanonicalV2Dto('RequestTrade', dto);\n if (this.signer === undefined) throw GSwapSDKError.noSignerError();\n return signPreSend(this.signer, 'RequestTrade', dto);\n }\n\n /** The pool's two symbols in contract order: the quote's own when pinned, else string order. */\n private orientPool(\n tokenInSymbol: string,\n tokenOutSymbol: string,\n pool: SwapPool | undefined,\n ): SwapPool {\n if (pool === undefined) {\n return readOrderedSymbols(orderSymbols(tokenInSymbol, tokenOutSymbol));\n }\n const sameSet =\n (tokenInSymbol === pool.token0 && tokenOutSymbol === pool.token1) ||\n (tokenInSymbol === pool.token1 && tokenOutSymbol === pool.token0);\n if (!sameSet) {\n throw new GSwapSDKError('POOL_MISMATCH', {\n tokenIn: tokenInSymbol,\n tokenOut: tokenOutSymbol,\n poolToken0: pool.token0,\n poolToken1: pool.token1,\n });\n }\n return { token0: pool.token0, token1: pool.token1 };\n }\n\n /**\n * Confirm a swap by its durable `uniqueKey` without resubmitting it. This is the recovery\n * path after a `CONFIRMATION_TIMEOUT`, a `SUBMISSION_OUTCOME_UNKNOWN`, a `409` replay\n * conflict, or a process restart: it reads the same apply-results record that\n * `SubmittedTransaction.confirm()` reads (retained for 7 days) and resolves the applied trade\n * or throws the chain's rejection. The first read happens immediately unless\n * `initialDelayMs` is given.\n *\n * @example\n * ```ts\n * const applied = await swaps.confirmSwap('galaswap-operation-2f1c…');\n * ```\n */\n public async confirmSwap(uniqueKey: string, options?: ConfirmOptions): Promise<AppliedTrade> {\n assertUniqueKey(uniqueKey);\n const transaction = new SubmittedTransaction<AppliedTrade>({\n method: 'RequestTrade',\n uniqueKey,\n transactionId: null,\n mode: 'queued',\n result: undefined,\n dexBackendBaseUrl: this.gateway.dexBackendBaseUrl,\n httpRequestor: this.gateway.httpRequestor,\n requestTimeoutMs: this.gateway.requestTimeoutMs,\n wire: this.gateway.wire,\n });\n return transaction.confirm({ initialDelayMs: 0, ...options });\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"classification.js","names":["DEFAULT_RETRYABLE","classification","options","outcome","NOT_QUEUED_FINAL","notSentClassification"],"sources":["../../../src/utils/classification.ts"],"sourcesContent":["import type { FailureOutcome } from '@gala-chain/gala-core-sdk';\n\n/**\n * The classification values an app shell may need on their own: what an outcome allows. This module\n * is deliberately tiny and imports nothing at run time (the outcome type is erased), so an app\n * shell that builds `classification('not_sent')` or reads `NOT_QUEUED_FINAL` loads a few hundred\n * bytes, not the table of error codes that lives with `classifyFailure`.\n */\n\n/** The result of `classifyFailure`. */\nexport interface FailureClassification {\n readonly outcome: FailureOutcome;\n /**\n * May the app offer the user Retry: repeat the operation as a NEW request (a fresh key for a\n * write) without any risk of applying it twice? Always `false` for `unknown` (confirm the same\n * key instead) and `invalid_input` (the same call fails again). It says a repeat is SAFE, not\n * that it will succeed, and it is the SDK's floor: an app may still withhold Retry (the\n * wallet and frontend_v2 do after a Dark swap: see `isShadowError`).\n */\n readonly retryable: boolean;\n}\n\n/**\n * The SDK's default Retry answer for each outcome. A decision that departs from it (a refusal the\n * gateway will repeat, `SHADOW_NOT_QUEUED`) is written out in the table of `classifyFailure`.\n */\nconst DEFAULT_RETRYABLE = {\n rejected: true,\n unknown: false,\n not_queued: true,\n not_sent: true,\n invalid_input: false,\n transient: true,\n} as const satisfies Record<FailureOutcome, boolean>;\n\n/**\n * Builds the classification the SDK itself gives an outcome, so an app that classifies a step of\n * its OWN (one that is not an SDK call) uses the SDK's type and its values instead of a\n * hard-coded `{ outcome, retryable }` literal. `retryable` is the SDK's default for that outcome\n * (`false` for `unknown` and `invalid_input`, `true` for the rest). Each call returns a new\n * object, safe to mutate or freeze.\n *\n * `options.retryable: false` WITHHOLDS Retry from an outcome that would otherwise allow it: the\n * SDK's own `not_queued` for a refusal the gateway will repeat is\n * `classification('not_queued', { retryable: false })` (also exported, frozen, as\n * {@link NOT_QUEUED_FINAL}). An app may take Retry away from an outcome but never grant it to one\n * that cannot have it, so only `false` is accepted.\n *\n * @example\n * ```ts\n * // The app refreshes a quote before it asks the SDK to sign; the refresh failed.\n * throw new AppSwapError(text, classification('not_sent'));\n * // The app refuses a request for a reason a repeat cannot fix.\n * throw new AppSwapError(text, classification('not_queued', { retryable: false }));\n * ```\n */\nexport function classification(\n outcome: FailureOutcome,\n options: { readonly retryable?: false } = {},\n): FailureClassification {\n return { outcome, retryable: options.retryable === false ? false : DEFAULT_RETRYABLE[outcome] };\n}\n\n/**\n * `classification('not_queued', { retryable: false })`: the gateway refused the write before\n * queueing it AND will refuse a repeat the same way (the version wall, a dark swap that did not\n * queue, an HTTP 4xx). Nothing is pending and Retry is not offered. It is frozen: use it as it is\n * (`classification: NOT_QUEUED_FINAL`) instead of spreading `classification('not_queued')` and\n * overriding `retryable`; `{ ...NOT_QUEUED_FINAL }` is a mutable copy.\n */\nexport const NOT_QUEUED_FINAL: Readonly<FailureClassification> = Object.freeze(\n classification('not_queued', { retryable: false }),\n);\n\n/**\n * `classification('not_sent')`: a step of the app's own failed before anything was signed or\n * sent (a quote refresh, a balance read), so nothing is pending and a repeat is safe.\n */\nexport function notSentClassification(): FailureClassification {\n return classification('not_sent');\n}\n"],"mappings":"AA0BA,MAAMA,EAAoB,CACxB,SAAU,GACV,QAAS,GACT,WAAY,GACZ,SAAU,GACV,cAAe,GACf,UAAW,EACb,EAuBA,SAAgBC,EACd,EACA,EAA0C,CAAC,EACpB,CACvB,MAAO,CAAE,UAAS,UAAWC,EAAQ,YAAc,IAAgBF,EAAkBG,EAAS,CAChG,CASA,MAAaC,EAAoD,OAAO,OACtEH,EAAe,aAAc,CAAE,UAAW,EAAM,CAAC,CACnD,EAMA,SAAgBI,GAA+C,CAC7D,OAAOJ,EAAe,UAAU,CAClC"}
1
+ {"version":3,"file":"classification.js","names":["DEFAULT_RETRYABLE","classification","options","outcome","NOT_QUEUED_FINAL","notSentClassification"],"sources":["../../../src/utils/classification.ts"],"sourcesContent":["import type { FailureOutcome } from '@gala-chain/gala-core-sdk';\n\n/**\n * The classification values an app shell may need on their own: what an outcome allows. This module\n * is deliberately tiny and imports nothing at run time (the outcome type is erased), so an app\n * shell that builds `classification('not_sent')` or reads `NOT_QUEUED_FINAL` loads a few hundred\n * bytes, not the table of error codes that lives with `classifyFailure`.\n */\n\n/** The result of `classifyFailure`. */\nexport interface FailureClassification {\n readonly outcome: FailureOutcome;\n /**\n * May the app offer the user Retry: repeat the operation as a NEW request (a fresh key for a\n * write) without any risk of applying it twice? Always `false` for `unknown` (confirm the same\n * key instead) and `invalid_input` (the same call fails again). It says a repeat is SAFE, not\n * that it will succeed, and it is the SDK's floor: an app may still withhold Retry (the\n * Gala Wallet does after a Dark swap: see `isShadowError`).\n */\n readonly retryable: boolean;\n}\n\n/**\n * The SDK's default Retry answer for each outcome. A decision that departs from it (a refusal the\n * gateway will repeat, `SHADOW_NOT_QUEUED`) is written out in the table of `classifyFailure`.\n */\nconst DEFAULT_RETRYABLE = {\n rejected: true,\n unknown: false,\n not_queued: true,\n not_sent: true,\n invalid_input: false,\n transient: true,\n} as const satisfies Record<FailureOutcome, boolean>;\n\n/**\n * Builds the classification the SDK itself gives an outcome, so an app that classifies a step of\n * its OWN (one that is not an SDK call) uses the SDK's type and its values instead of a\n * hard-coded `{ outcome, retryable }` literal. `retryable` is the SDK's default for that outcome\n * (`false` for `unknown` and `invalid_input`, `true` for the rest). Each call returns a new\n * object, safe to mutate or freeze.\n *\n * `options.retryable: false` WITHHOLDS Retry from an outcome that would otherwise allow it: the\n * SDK's own `not_queued` for a refusal the gateway will repeat is\n * `classification('not_queued', { retryable: false })` (also exported, frozen, as\n * {@link NOT_QUEUED_FINAL}). An app may take Retry away from an outcome but never grant it to one\n * that cannot have it, so only `false` is accepted.\n *\n * @example\n * ```ts\n * // The app refreshes a quote before it asks the SDK to sign; the refresh failed.\n * throw new AppSwapError(text, classification('not_sent'));\n * // The app refuses a request for a reason a repeat cannot fix.\n * throw new AppSwapError(text, classification('not_queued', { retryable: false }));\n * ```\n */\nexport function classification(\n outcome: FailureOutcome,\n options: { readonly retryable?: false } = {},\n): FailureClassification {\n return { outcome, retryable: options.retryable === false ? false : DEFAULT_RETRYABLE[outcome] };\n}\n\n/**\n * `classification('not_queued', { retryable: false })`: the gateway refused the write before\n * queueing it AND will refuse a repeat the same way (the version wall, a dark swap that did not\n * queue, an HTTP 4xx). Nothing is pending and Retry is not offered. It is frozen: use it as it is\n * (`classification: NOT_QUEUED_FINAL`) instead of spreading `classification('not_queued')` and\n * overriding `retryable`; `{ ...NOT_QUEUED_FINAL }` is a mutable copy.\n */\nexport const NOT_QUEUED_FINAL: Readonly<FailureClassification> = Object.freeze(\n classification('not_queued', { retryable: false }),\n);\n\n/**\n * `classification('not_sent')`: a step of the app's own failed before anything was signed or\n * sent (a quote refresh, a balance read), so nothing is pending and a repeat is safe.\n */\nexport function notSentClassification(): FailureClassification {\n return classification('not_sent');\n}\n"],"mappings":"AA0BA,MAAMA,EAAoB,CACxB,SAAU,GACV,QAAS,GACT,WAAY,GACZ,SAAU,GACV,cAAe,GACf,UAAW,EACb,EAuBA,SAAgBC,EACd,EACA,EAA0C,CAAC,EACpB,CACvB,MAAO,CAAE,UAAS,UAAWC,EAAQ,YAAc,IAAgBF,EAAkBG,EAAS,CAChG,CASA,MAAaC,EAAoD,OAAO,OACtEH,EAAe,aAAc,CAAE,UAAW,EAAM,CAAC,CACnD,EAMA,SAAgBI,GAA+C,CAC7D,OAAOJ,EAAe,UAAU,CAClC"}
@@ -1 +1 @@
1
- {"version":3,"file":"dto_builders.js","names":["BigNumber","decimalString","amount","buildRequestTradeDto","validateFee","input","assertCanonicalPair","assertUniqueKey","dto","exactIn","GSwapSDKError","validateNumericAmount","\"amountOutMinimum\"","exactOut","\"amountInMaximum\"","\"priceLimit\"","assertPoolPosition","assertTickRange","buildAddLiquidityDto","side","onlyOne","buildRemoveLiquidityDto","buildCollectPositionFeesDto","buildCreatePoolDto","symbol","isProposableTradingSymbol","key","assertClassKey","price","parsed","part","first","second","allowNeither","firstName","secondName","invalidDto","rebuildV2Dto","text","value","field","method","integer","optionalText","classKey","name","base","uniqueKey","position","qty","f","tokenInIsToken0","shared","priceLimit","token0Side","token1Side","sqrtPrice","privateAccess","\"privateAccess\"","entry","\"isPrivate\"","assertCanonicalV2Dto","rebuilt"],"sources":["../../../src/utils/dto_builders.ts"],"sourcesContent":["import { assertUniqueKey, type GalaChainTokenClassKey } from '@gala-chain/gala-core-sdk';\nimport BigNumber from 'bignumber.js';\nimport { GSwapSDKError } from '../classes/gswap_sdk_error.js';\nimport type { NumericAmount, SwapAmount } from '../types/amounts.js';\nimport type {\n AddLiquidityDTO,\n CollectPositionFeesDTO,\n CreatePoolDTO,\n RemoveLiquidityDTO,\n RequestTradeDTO,\n} from '../types/v2_dtos.js';\nimport { assertTickRange } from './ticks.js';\nimport {\n assertCanonicalPair,\n isProposableTradingSymbol,\n validateFee,\n validateNumericAmount,\n} from './validation.js';\n\n// The pure builders of every v2 write DTO. They validate, canonicalize decimals\n// (`BigNumber#toFixed`) and fix the key order, and nothing else: no network, no signer, no\n// clock, no randomness — the `uniqueKey` is an input. The `Swaps` and `Positions` services call\n// exactly these functions, so a DTO an app builds here (to show in an approval popup, to sign on\n// another device, to submit later) is the DTO the services would have built.\n//\n// Inputs are already CANONICAL: `token0 < token1` by plain string comparison, ticks and prices in\n// that orientation. Orienting a caller's pair (and inverting its ticks and price) is the\n// services' job; a reversed pair is refused (`NON_CANONICAL_PAIR`) rather than silently signed.\n\nconst decimalString = (amount: NumericAmount): string => new BigNumber(amount).toFixed();\n\n/** The pool a RequestTrade trades in, its fee tier, the `uniqueKey` and what is traded. */\nexport type RequestTradeInput = {\n /** Canonical pool symbols: `token0 < token1`. Take them from a quote verbatim. */\n token0: string;\n token1: string;\n fee: number;\n /** Which side of the pool is sold: `true` when the input token is `token0`. */\n tokenInIsToken0: boolean;\n uniqueKey: string;\n} & SwapAmount;\n\n/**\n * Builds the RequestTrade DTO: `exactIn` sells that much of the input token (`sell0Qty` or\n * `sell1Qty`), `exactOut` buys that much of the output token (`buy1Qty` or `buy0Qty`), plus the\n * side's slippage bound and an optional `priceLimit`. Key order is\n * `token0, token1, fee, uniqueKey`, then the one quantity, then the slippage bound, then\n * `priceLimit`.\n *\n * @example\n * ```ts\n * const dto = buildRequestTradeDto({\n * token0: quote.token0Symbol, token1: quote.token1Symbol,\n * tokenInIsToken0: quote.tokenInIsToken0, fee: quote.fee,\n * uniqueKey: createUniqueKey('galaswap-operation-'),\n * exactIn: '100', amountOutMinimum: '14',\n * });\n * ```\n */\nexport function buildRequestTradeDto(input: RequestTradeInput): RequestTradeDTO {\n validateFee(input.fee);\n assertCanonicalPair(input.token0, input.token1);\n assertUniqueKey(input.uniqueKey);\n const dto: Record<string, unknown> = {\n token0: input.token0,\n token1: input.token1,\n fee: input.fee,\n uniqueKey: input.uniqueKey,\n };\n if ('exactIn' in input) {\n const exactIn = input.exactIn;\n if (exactIn === undefined) {\n throw new GSwapSDKError('EXACT_AMOUNT_REQUIRED', { field: 'exactIn' });\n }\n validateNumericAmount(exactIn, 'exactIn');\n dto[input.tokenInIsToken0 ? 'sell0Qty' : 'sell1Qty'] = decimalString(exactIn);\n if (input.amountOutMinimum !== undefined) {\n validateNumericAmount(input.amountOutMinimum, 'amountOutMinimum', true);\n dto['amountOutMinimum'] = decimalString(input.amountOutMinimum);\n }\n } else {\n const exactOut = input.exactOut;\n if (exactOut === undefined) {\n throw new GSwapSDKError('EXACT_AMOUNT_REQUIRED', { field: 'exactOut' });\n }\n validateNumericAmount(exactOut, 'exactOut');\n dto[input.tokenInIsToken0 ? 'buy1Qty' : 'buy0Qty'] = decimalString(exactOut);\n if (input.amountInMaximum !== undefined) {\n validateNumericAmount(input.amountInMaximum, 'amountInMaximum', true);\n dto['amountInMaximum'] = decimalString(input.amountInMaximum);\n }\n }\n if (input.priceLimit !== undefined) {\n validateNumericAmount(input.priceLimit, 'priceLimit');\n dto['priceLimit'] = decimalString(input.priceLimit);\n }\n return dto as unknown as RequestTradeDTO;\n}\n\n/** A canonical pool position: the pair, fee tier, tick range and the attempt's `uniqueKey`. */\nexport interface PoolPositionInput {\n token0: string;\n token1: string;\n fee: number;\n tickLower: number;\n tickUpper: number;\n uniqueKey: string;\n}\n\nfunction assertPoolPosition(input: PoolPositionInput): void {\n validateFee(input.fee);\n assertCanonicalPair(input.token0, input.token1);\n assertTickRange(input.tickLower, input.tickUpper, input.fee);\n assertUniqueKey(input.uniqueKey);\n}\n\n/** Exactly one canonical deposit side. */\nexport type AddLiquidityInput = PoolPositionInput &\n (\n | { depositQuantityToken0: NumericAmount; depositQuantityToken1?: undefined }\n | { depositQuantityToken0?: undefined; depositQuantityToken1: NumericAmount }\n );\n\n/**\n * Builds the AddLiquidity DTO: exactly one of `depositQuantityToken0` / `depositQuantityToken1`\n * (the other side is derived on chain). Key order: `token0, token1, fee, tickLower, tickUpper,\n * uniqueKey, <deposit>`.\n */\nexport function buildAddLiquidityDto(input: AddLiquidityInput): AddLiquidityDTO {\n assertPoolPosition(input);\n const side = onlyOne(\n input.depositQuantityToken0,\n input.depositQuantityToken1,\n 'depositQuantityToken0',\n 'depositQuantityToken1',\n );\n if (side === undefined) {\n throw new GSwapSDKError('EXACTLY_ONE_OF_REQUIRED', {\n first: 'depositQuantityToken0',\n second: 'depositQuantityToken1',\n });\n }\n validateNumericAmount(side.amount, side.name);\n return {\n token0: input.token0,\n token1: input.token1,\n fee: input.fee,\n tickLower: input.tickLower,\n tickUpper: input.tickUpper,\n uniqueKey: input.uniqueKey,\n [side.name]: decimalString(side.amount),\n } as AddLiquidityDTO;\n}\n\n/** At most one canonical withdrawal side; neither closes the position. */\nexport type RemoveLiquidityInput = PoolPositionInput & {\n withdrawalQuantityToken0?: NumericAmount | undefined;\n withdrawalQuantityToken1?: NumericAmount | undefined;\n};\n\n/**\n * Builds the RemoveLiquidity DTO: at most one of `withdrawalQuantityToken0` /\n * `withdrawalQuantityToken1`; omitting both closes the position and sweeps its fees.\n */\nexport function buildRemoveLiquidityDto(input: RemoveLiquidityInput): RemoveLiquidityDTO {\n assertPoolPosition(input);\n const side = onlyOne(\n input.withdrawalQuantityToken0,\n input.withdrawalQuantityToken1,\n 'withdrawalQuantityToken0',\n 'withdrawalQuantityToken1',\n true,\n );\n if (side !== undefined) validateNumericAmount(side.amount, side.name);\n return {\n token0: input.token0,\n token1: input.token1,\n fee: input.fee,\n tickLower: input.tickLower,\n tickUpper: input.tickUpper,\n uniqueKey: input.uniqueKey,\n ...(side === undefined ? {} : { [side.name]: decimalString(side.amount) }),\n };\n}\n\n/** Builds the CollectPositionFees DTO (the contract sweeps all of a position's fees). */\nexport function buildCollectPositionFeesDto(input: PoolPositionInput): CollectPositionFeesDTO {\n assertPoolPosition(input);\n return {\n token0: input.token0,\n token1: input.token1,\n fee: input.fee,\n tickLower: input.tickLower,\n tickUpper: input.tickUpper,\n uniqueKey: input.uniqueKey,\n };\n}\n\n/** A CreatePool in canonical orientation, with exactly one starting price. */\nexport type CreatePoolInput = {\n token0Key: GalaChainTokenClassKey;\n token1Key: GalaChainTokenClassKey;\n /** The symbols to register; `token0Symbol < token1Symbol` and both proposable. */\n token0Symbol: string;\n token1Symbol: string;\n fee: number;\n /** Optional display name; omitted when undefined or empty. */\n name?: string | undefined;\n isPrivate?: boolean | undefined;\n privateAccess?: string[] | undefined;\n uniqueKey: string;\n} & (\n | { startingPrice: NumericAmount; startingSqrtPrice?: undefined }\n | { startingPrice?: undefined; startingSqrtPrice: NumericAmount }\n);\n\n/**\n * Builds the CreatePool DTO. Key order: `token0Key, token1Key, fee, startingPrice |\n * startingSqrtPrice, token0Symbol, token1Symbol, uniqueKey, name, isPrivate, privateAccess`.\n * Prices are those of the canonical pair (token1 per token0); a price quoted the other way round\n * is inverted by the caller before it gets here.\n */\nexport function buildCreatePoolDto(input: CreatePoolInput): CreatePoolDTO {\n validateFee(input.fee);\n assertUniqueKey(input.uniqueKey);\n assertCanonicalPair(input.token0Symbol, input.token1Symbol);\n for (const symbol of [input.token0Symbol, input.token1Symbol]) {\n if (!isProposableTradingSymbol(symbol)) {\n throw new GSwapSDKError('INVALID_TRADING_SYMBOL', { symbol });\n }\n }\n for (const key of [input.token0Key, input.token1Key]) assertClassKey(key);\n const price = onlyOne(\n input.startingPrice,\n input.startingSqrtPrice,\n 'startingPrice',\n 'startingSqrtPrice',\n );\n if (price === undefined) {\n throw new GSwapSDKError('EXACTLY_ONE_OF_REQUIRED', {\n first: 'startingPrice',\n second: 'startingSqrtPrice',\n });\n }\n if (typeof price.amount === 'number') {\n throw new GSwapSDKError('AMOUNT_IS_JS_NUMBER', { field: price.name });\n }\n const parsed = new BigNumber(price.amount);\n if (!parsed.isFinite() || parsed.isLessThanOrEqualTo(0)) {\n throw new GSwapSDKError('INVALID_STARTING_PRICE', { value: String(price.amount) });\n }\n return {\n token0Key: input.token0Key,\n token1Key: input.token1Key,\n fee: input.fee,\n [price.name]: parsed.toFixed(),\n token0Symbol: input.token0Symbol,\n token1Symbol: input.token1Symbol,\n uniqueKey: input.uniqueKey,\n // An empty name is \"no name\", as in frontend_v2's builder (its form yields '').\n ...(input.name === undefined || input.name === '' ? {} : { name: input.name }),\n ...(input.isPrivate === undefined ? {} : { isPrivate: input.isPrivate }),\n ...(input.privateAccess === undefined ? {} : { privateAccess: input.privateAccess }),\n } as CreatePoolDTO;\n}\n\nfunction assertClassKey(key: GalaChainTokenClassKey): void {\n for (const part of ['collection', 'category', 'type', 'additionalKey'] as const) {\n if (typeof key[part] !== 'string' || key[part].length === 0) {\n throw new GSwapSDKError('INCOMPLETE_TOKEN_CLASS_KEY', {\n compositeKey: [key.collection, key.category, key.type, key.additionalKey].join('$'),\n part,\n });\n }\n }\n}\n\n/**\n * Of two mutually exclusive fields, the one that is set (`undefined` when neither is);\n * both set is an error (`AT_MOST_ONE_OF_ALLOWED` or `EXACTLY_ONE_OF_REQUIRED`), and so is\n * neither unless `allowNeither`.\n */\nfunction onlyOne<T>(\n first: T | undefined,\n second: T | undefined,\n firstName: string,\n secondName: string,\n allowNeither = false,\n): { name: string; amount: T } | undefined {\n if (first !== undefined && second !== undefined) {\n throw allowNeither\n ? new GSwapSDKError('AT_MOST_ONE_OF_ALLOWED', { first: firstName, second: secondName })\n : new GSwapSDKError('EXACTLY_ONE_OF_REQUIRED', { first: firstName, second: secondName });\n }\n if (first !== undefined) return { name: firstName, amount: first };\n if (second !== undefined) return { name: secondName, amount: second };\n return undefined;\n}\n\n/** The methods whose DTOs the builders produce. */\nexport type V2WriteMethod =\n 'RequestTrade' | 'AddLiquidity' | 'RemoveLiquidity' | 'CollectPositionFees' | 'CreatePool';\n\ntype DtoExpectation =\n 'string' | 'number' | 'boolean' | 'class_key' | 'string_array' | 'exactly_once';\n\nfunction invalidDto(\n method: string,\n field: string,\n expected: DtoExpectation,\n): GSwapSDKError<'INVALID_DTO'> {\n return new GSwapSDKError('INVALID_DTO', { method, field, expected });\n}\n\n/**\n * Rebuilds a DTO from its own fields with the builder of its method: the canonical form of the\n * DTO, with the same validation. Key order and decimal text are the builder's.\n */\nfunction rebuildV2Dto(\n method: V2WriteMethod,\n dto: Record<string, unknown>,\n): Record<string, unknown> {\n const text = (field: string): string => {\n const value = dto[field];\n if (typeof value !== 'string') throw invalidDto(method, field, 'string');\n return value;\n };\n const integer = (field: string): number => {\n const value = dto[field];\n if (typeof value !== 'number') throw invalidDto(method, field, 'number');\n return value;\n };\n const optionalText = (field: string): string | undefined =>\n dto[field] === undefined ? undefined : text(field);\n const classKey = (field: string): GalaChainTokenClassKey => {\n const value = dto[field];\n if (typeof value !== 'object' || value === null) throw invalidDto(method, field, 'class_key');\n const key = value as Record<string, unknown>;\n const part = (name: string): string => {\n if (typeof key[name] !== 'string') throw invalidDto(method, `${field}.${name}`, 'string');\n return key[name];\n };\n return {\n collection: part('collection'),\n category: part('category'),\n type: part('type'),\n additionalKey: part('additionalKey'),\n };\n };\n // The pool pair is not a field of CreatePool, so these are read per method, not up front.\n const base = () => ({ token0: text('token0'), token1: text('token1'), fee: integer('fee') });\n const uniqueKey = text('uniqueKey');\n const position = () => ({\n ...base(),\n uniqueKey,\n tickLower: integer('tickLower'),\n tickUpper: integer('tickUpper'),\n });\n switch (method) {\n case 'RequestTrade': {\n const qty = ['sell0Qty', 'sell1Qty', 'buy0Qty', 'buy1Qty'].filter(\n (f) => dto[f] !== undefined,\n );\n const [field] = qty;\n if (qty.length !== 1 || field === undefined) {\n throw invalidDto(method, 'sell0Qty|sell1Qty|buy0Qty|buy1Qty', 'exactly_once');\n }\n const tokenInIsToken0 = field === 'sell0Qty' || field === 'buy1Qty';\n const shared = { ...base(), uniqueKey, tokenInIsToken0 };\n const priceLimit = optionalText('priceLimit');\n return field.startsWith('sell')\n ? buildRequestTradeDto({\n ...shared,\n exactIn: text(field),\n amountOutMinimum: optionalText('amountOutMinimum'),\n priceLimit,\n })\n : buildRequestTradeDto({\n ...shared,\n exactOut: text(field),\n amountInMaximum: optionalText('amountInMaximum'),\n priceLimit,\n });\n }\n case 'AddLiquidity': {\n const token0Side = optionalText('depositQuantityToken0');\n const token1Side = optionalText('depositQuantityToken1');\n return buildAddLiquidityDto({\n ...position(),\n ...(token0Side === undefined\n ? { depositQuantityToken1: token1Side as string }\n : { depositQuantityToken0: token0Side }),\n });\n }\n case 'RemoveLiquidity':\n return buildRemoveLiquidityDto({\n ...position(),\n withdrawalQuantityToken0: optionalText('withdrawalQuantityToken0'),\n withdrawalQuantityToken1: optionalText('withdrawalQuantityToken1'),\n });\n case 'CollectPositionFees':\n return buildCollectPositionFeesDto(position());\n case 'CreatePool': {\n const price = optionalText('startingPrice');\n const sqrtPrice = optionalText('startingSqrtPrice');\n const privateAccess = dto['privateAccess'];\n if (\n privateAccess !== undefined &&\n (!Array.isArray(privateAccess) || privateAccess.some((entry) => typeof entry !== 'string'))\n ) {\n throw invalidDto(method, 'privateAccess', 'string_array');\n }\n if (dto['isPrivate'] !== undefined && typeof dto['isPrivate'] !== 'boolean') {\n throw invalidDto(method, 'isPrivate', 'boolean');\n }\n return buildCreatePoolDto({\n token0Key: classKey('token0Key'),\n token1Key: classKey('token1Key'),\n token0Symbol: text('token0Symbol'),\n token1Symbol: text('token1Symbol'),\n fee: integer('fee'),\n uniqueKey,\n name: optionalText('name'),\n isPrivate: dto['isPrivate'],\n privateAccess: privateAccess as string[] | undefined,\n ...(price === undefined\n ? { startingSqrtPrice: sqrtPrice as string }\n : { startingPrice: price }),\n });\n }\n }\n}\n\n/**\n * Asserts that `dto` is EXACTLY what the builder of `method` produces for its own fields — the\n * same fields, canonical decimal text, builder key order, nothing extra (no `signature`). The\n * `submit*` methods use it so that what an app showed a user (a DTO it built) is byte for byte\n * what gets signed; a DTO edited by hand, or built elsewhere, is refused rather than signed.\n *\n * @throws GSwapSDKError `INVALID_DTO` or `DTO_NOT_CANONICAL`.\n */\nexport function assertCanonicalV2Dto(method: V2WriteMethod, dto: Record<string, unknown>): void {\n const rebuilt = rebuildV2Dto(method, dto);\n if (JSON.stringify(rebuilt) !== JSON.stringify(dto)) {\n throw new GSwapSDKError('DTO_NOT_CANONICAL', { method });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"dto_builders.js","names":["BigNumber","decimalString","amount","buildRequestTradeDto","validateFee","input","assertCanonicalPair","assertUniqueKey","dto","exactIn","GSwapSDKError","validateNumericAmount","\"amountOutMinimum\"","exactOut","\"amountInMaximum\"","\"priceLimit\"","assertPoolPosition","assertTickRange","buildAddLiquidityDto","side","onlyOne","buildRemoveLiquidityDto","buildCollectPositionFeesDto","buildCreatePoolDto","symbol","isProposableTradingSymbol","key","assertClassKey","price","parsed","part","first","second","allowNeither","firstName","secondName","invalidDto","rebuildV2Dto","text","value","field","method","integer","optionalText","classKey","name","base","uniqueKey","position","qty","f","tokenInIsToken0","shared","priceLimit","token0Side","token1Side","sqrtPrice","privateAccess","\"privateAccess\"","entry","\"isPrivate\"","assertCanonicalV2Dto","rebuilt"],"sources":["../../../src/utils/dto_builders.ts"],"sourcesContent":["import { assertUniqueKey, type GalaChainTokenClassKey } from '@gala-chain/gala-core-sdk';\nimport BigNumber from 'bignumber.js';\nimport { GSwapSDKError } from '../classes/gswap_sdk_error.js';\nimport type { NumericAmount, SwapAmount } from '../types/amounts.js';\nimport type {\n AddLiquidityDTO,\n CollectPositionFeesDTO,\n CreatePoolDTO,\n RemoveLiquidityDTO,\n RequestTradeDTO,\n} from '../types/v2_dtos.js';\nimport { assertTickRange } from './ticks.js';\nimport {\n assertCanonicalPair,\n isProposableTradingSymbol,\n validateFee,\n validateNumericAmount,\n} from './validation.js';\n\n// The pure builders of every v2 write DTO. They validate, canonicalize decimals\n// (`BigNumber#toFixed`) and fix the key order, and nothing else: no network, no signer, no\n// clock, no randomness — the `uniqueKey` is an input. The `Swaps` and `Positions` services call\n// exactly these functions, so a DTO an app builds here (to show in an approval popup, to sign on\n// another device, to submit later) is the DTO the services would have built.\n//\n// Inputs are already CANONICAL: `token0 < token1` by plain string comparison, ticks and prices in\n// that orientation. Orienting a caller's pair (and inverting its ticks and price) is the\n// services' job; a reversed pair is refused (`NON_CANONICAL_PAIR`) rather than silently signed.\n\nconst decimalString = (amount: NumericAmount): string => new BigNumber(amount).toFixed();\n\n/** The pool a RequestTrade trades in, its fee tier, the `uniqueKey` and what is traded. */\nexport type RequestTradeInput = {\n /** Canonical pool symbols: `token0 < token1`. Take them from a quote verbatim. */\n token0: string;\n token1: string;\n fee: number;\n /** Which side of the pool is sold: `true` when the input token is `token0`. */\n tokenInIsToken0: boolean;\n uniqueKey: string;\n} & SwapAmount;\n\n/**\n * Builds the RequestTrade DTO: `exactIn` sells that much of the input token (`sell0Qty` or\n * `sell1Qty`), `exactOut` buys that much of the output token (`buy1Qty` or `buy0Qty`), plus the\n * side's slippage bound and an optional `priceLimit`. Key order is\n * `token0, token1, fee, uniqueKey`, then the one quantity, then the slippage bound, then\n * `priceLimit`.\n *\n * @example\n * ```ts\n * const dto = buildRequestTradeDto({\n * token0: quote.token0Symbol, token1: quote.token1Symbol,\n * tokenInIsToken0: quote.tokenInIsToken0, fee: quote.fee,\n * uniqueKey: createUniqueKey('galaswap-operation-'),\n * exactIn: '100', amountOutMinimum: '14',\n * });\n * ```\n */\nexport function buildRequestTradeDto(input: RequestTradeInput): RequestTradeDTO {\n validateFee(input.fee);\n assertCanonicalPair(input.token0, input.token1);\n assertUniqueKey(input.uniqueKey);\n const dto: Record<string, unknown> = {\n token0: input.token0,\n token1: input.token1,\n fee: input.fee,\n uniqueKey: input.uniqueKey,\n };\n if ('exactIn' in input) {\n const exactIn = input.exactIn;\n if (exactIn === undefined) {\n throw new GSwapSDKError('EXACT_AMOUNT_REQUIRED', { field: 'exactIn' });\n }\n validateNumericAmount(exactIn, 'exactIn');\n dto[input.tokenInIsToken0 ? 'sell0Qty' : 'sell1Qty'] = decimalString(exactIn);\n if (input.amountOutMinimum !== undefined) {\n validateNumericAmount(input.amountOutMinimum, 'amountOutMinimum', true);\n dto['amountOutMinimum'] = decimalString(input.amountOutMinimum);\n }\n } else {\n const exactOut = input.exactOut;\n if (exactOut === undefined) {\n throw new GSwapSDKError('EXACT_AMOUNT_REQUIRED', { field: 'exactOut' });\n }\n validateNumericAmount(exactOut, 'exactOut');\n dto[input.tokenInIsToken0 ? 'buy1Qty' : 'buy0Qty'] = decimalString(exactOut);\n if (input.amountInMaximum !== undefined) {\n validateNumericAmount(input.amountInMaximum, 'amountInMaximum', true);\n dto['amountInMaximum'] = decimalString(input.amountInMaximum);\n }\n }\n if (input.priceLimit !== undefined) {\n validateNumericAmount(input.priceLimit, 'priceLimit');\n dto['priceLimit'] = decimalString(input.priceLimit);\n }\n return dto as unknown as RequestTradeDTO;\n}\n\n/** A canonical pool position: the pair, fee tier, tick range and the attempt's `uniqueKey`. */\nexport interface PoolPositionInput {\n token0: string;\n token1: string;\n fee: number;\n tickLower: number;\n tickUpper: number;\n uniqueKey: string;\n}\n\nfunction assertPoolPosition(input: PoolPositionInput): void {\n validateFee(input.fee);\n assertCanonicalPair(input.token0, input.token1);\n assertTickRange(input.tickLower, input.tickUpper, input.fee);\n assertUniqueKey(input.uniqueKey);\n}\n\n/** Exactly one canonical deposit side. */\nexport type AddLiquidityInput = PoolPositionInput &\n (\n | { depositQuantityToken0: NumericAmount; depositQuantityToken1?: undefined }\n | { depositQuantityToken0?: undefined; depositQuantityToken1: NumericAmount }\n );\n\n/**\n * Builds the AddLiquidity DTO: exactly one of `depositQuantityToken0` / `depositQuantityToken1`\n * (the other side is derived on chain). Key order: `token0, token1, fee, tickLower, tickUpper,\n * uniqueKey, <deposit>`.\n */\nexport function buildAddLiquidityDto(input: AddLiquidityInput): AddLiquidityDTO {\n assertPoolPosition(input);\n const side = onlyOne(\n input.depositQuantityToken0,\n input.depositQuantityToken1,\n 'depositQuantityToken0',\n 'depositQuantityToken1',\n );\n if (side === undefined) {\n throw new GSwapSDKError('EXACTLY_ONE_OF_REQUIRED', {\n first: 'depositQuantityToken0',\n second: 'depositQuantityToken1',\n });\n }\n validateNumericAmount(side.amount, side.name);\n return {\n token0: input.token0,\n token1: input.token1,\n fee: input.fee,\n tickLower: input.tickLower,\n tickUpper: input.tickUpper,\n uniqueKey: input.uniqueKey,\n [side.name]: decimalString(side.amount),\n } as AddLiquidityDTO;\n}\n\n/** At most one canonical withdrawal side; neither closes the position. */\nexport type RemoveLiquidityInput = PoolPositionInput & {\n withdrawalQuantityToken0?: NumericAmount | undefined;\n withdrawalQuantityToken1?: NumericAmount | undefined;\n};\n\n/**\n * Builds the RemoveLiquidity DTO: at most one of `withdrawalQuantityToken0` /\n * `withdrawalQuantityToken1`; omitting both closes the position and sweeps its fees.\n */\nexport function buildRemoveLiquidityDto(input: RemoveLiquidityInput): RemoveLiquidityDTO {\n assertPoolPosition(input);\n const side = onlyOne(\n input.withdrawalQuantityToken0,\n input.withdrawalQuantityToken1,\n 'withdrawalQuantityToken0',\n 'withdrawalQuantityToken1',\n true,\n );\n if (side !== undefined) validateNumericAmount(side.amount, side.name);\n return {\n token0: input.token0,\n token1: input.token1,\n fee: input.fee,\n tickLower: input.tickLower,\n tickUpper: input.tickUpper,\n uniqueKey: input.uniqueKey,\n ...(side === undefined ? {} : { [side.name]: decimalString(side.amount) }),\n };\n}\n\n/** Builds the CollectPositionFees DTO (the contract sweeps all of a position's fees). */\nexport function buildCollectPositionFeesDto(input: PoolPositionInput): CollectPositionFeesDTO {\n assertPoolPosition(input);\n return {\n token0: input.token0,\n token1: input.token1,\n fee: input.fee,\n tickLower: input.tickLower,\n tickUpper: input.tickUpper,\n uniqueKey: input.uniqueKey,\n };\n}\n\n/** A CreatePool in canonical orientation, with exactly one starting price. */\nexport type CreatePoolInput = {\n token0Key: GalaChainTokenClassKey;\n token1Key: GalaChainTokenClassKey;\n /** The symbols to register; `token0Symbol < token1Symbol` and both proposable. */\n token0Symbol: string;\n token1Symbol: string;\n fee: number;\n /** Optional display name; omitted when undefined or empty. */\n name?: string | undefined;\n isPrivate?: boolean | undefined;\n privateAccess?: string[] | undefined;\n uniqueKey: string;\n} & (\n | { startingPrice: NumericAmount; startingSqrtPrice?: undefined }\n | { startingPrice?: undefined; startingSqrtPrice: NumericAmount }\n);\n\n/**\n * Builds the CreatePool DTO. Key order: `token0Key, token1Key, fee, startingPrice |\n * startingSqrtPrice, token0Symbol, token1Symbol, uniqueKey, name, isPrivate, privateAccess`.\n * Prices are those of the canonical pair (token1 per token0); a price quoted the other way round\n * is inverted by the caller before it gets here.\n */\nexport function buildCreatePoolDto(input: CreatePoolInput): CreatePoolDTO {\n validateFee(input.fee);\n assertUniqueKey(input.uniqueKey);\n assertCanonicalPair(input.token0Symbol, input.token1Symbol);\n for (const symbol of [input.token0Symbol, input.token1Symbol]) {\n if (!isProposableTradingSymbol(symbol)) {\n throw new GSwapSDKError('INVALID_TRADING_SYMBOL', { symbol });\n }\n }\n for (const key of [input.token0Key, input.token1Key]) assertClassKey(key);\n const price = onlyOne(\n input.startingPrice,\n input.startingSqrtPrice,\n 'startingPrice',\n 'startingSqrtPrice',\n );\n if (price === undefined) {\n throw new GSwapSDKError('EXACTLY_ONE_OF_REQUIRED', {\n first: 'startingPrice',\n second: 'startingSqrtPrice',\n });\n }\n if (typeof price.amount === 'number') {\n throw new GSwapSDKError('AMOUNT_IS_JS_NUMBER', { field: price.name });\n }\n const parsed = new BigNumber(price.amount);\n if (!parsed.isFinite() || parsed.isLessThanOrEqualTo(0)) {\n throw new GSwapSDKError('INVALID_STARTING_PRICE', { value: String(price.amount) });\n }\n return {\n token0Key: input.token0Key,\n token1Key: input.token1Key,\n fee: input.fee,\n [price.name]: parsed.toFixed(),\n token0Symbol: input.token0Symbol,\n token1Symbol: input.token1Symbol,\n uniqueKey: input.uniqueKey,\n // An empty name is \"no name\", as the Gala web app's builder treats it (its form yields '').\n ...(input.name === undefined || input.name === '' ? {} : { name: input.name }),\n ...(input.isPrivate === undefined ? {} : { isPrivate: input.isPrivate }),\n ...(input.privateAccess === undefined ? {} : { privateAccess: input.privateAccess }),\n } as CreatePoolDTO;\n}\n\nfunction assertClassKey(key: GalaChainTokenClassKey): void {\n for (const part of ['collection', 'category', 'type', 'additionalKey'] as const) {\n if (typeof key[part] !== 'string' || key[part].length === 0) {\n throw new GSwapSDKError('INCOMPLETE_TOKEN_CLASS_KEY', {\n compositeKey: [key.collection, key.category, key.type, key.additionalKey].join('$'),\n part,\n });\n }\n }\n}\n\n/**\n * Of two mutually exclusive fields, the one that is set (`undefined` when neither is);\n * both set is an error (`AT_MOST_ONE_OF_ALLOWED` or `EXACTLY_ONE_OF_REQUIRED`), and so is\n * neither unless `allowNeither`.\n */\nfunction onlyOne<T>(\n first: T | undefined,\n second: T | undefined,\n firstName: string,\n secondName: string,\n allowNeither = false,\n): { name: string; amount: T } | undefined {\n if (first !== undefined && second !== undefined) {\n throw allowNeither\n ? new GSwapSDKError('AT_MOST_ONE_OF_ALLOWED', { first: firstName, second: secondName })\n : new GSwapSDKError('EXACTLY_ONE_OF_REQUIRED', { first: firstName, second: secondName });\n }\n if (first !== undefined) return { name: firstName, amount: first };\n if (second !== undefined) return { name: secondName, amount: second };\n return undefined;\n}\n\n/** The methods whose DTOs the builders produce. */\nexport type V2WriteMethod =\n 'RequestTrade' | 'AddLiquidity' | 'RemoveLiquidity' | 'CollectPositionFees' | 'CreatePool';\n\ntype DtoExpectation =\n 'string' | 'number' | 'boolean' | 'class_key' | 'string_array' | 'exactly_once';\n\nfunction invalidDto(\n method: string,\n field: string,\n expected: DtoExpectation,\n): GSwapSDKError<'INVALID_DTO'> {\n return new GSwapSDKError('INVALID_DTO', { method, field, expected });\n}\n\n/**\n * Rebuilds a DTO from its own fields with the builder of its method: the canonical form of the\n * DTO, with the same validation. Key order and decimal text are the builder's.\n */\nfunction rebuildV2Dto(\n method: V2WriteMethod,\n dto: Record<string, unknown>,\n): Record<string, unknown> {\n const text = (field: string): string => {\n const value = dto[field];\n if (typeof value !== 'string') throw invalidDto(method, field, 'string');\n return value;\n };\n const integer = (field: string): number => {\n const value = dto[field];\n if (typeof value !== 'number') throw invalidDto(method, field, 'number');\n return value;\n };\n const optionalText = (field: string): string | undefined =>\n dto[field] === undefined ? undefined : text(field);\n const classKey = (field: string): GalaChainTokenClassKey => {\n const value = dto[field];\n if (typeof value !== 'object' || value === null) throw invalidDto(method, field, 'class_key');\n const key = value as Record<string, unknown>;\n const part = (name: string): string => {\n if (typeof key[name] !== 'string') throw invalidDto(method, `${field}.${name}`, 'string');\n return key[name];\n };\n return {\n collection: part('collection'),\n category: part('category'),\n type: part('type'),\n additionalKey: part('additionalKey'),\n };\n };\n // The pool pair is not a field of CreatePool, so these are read per method, not up front.\n const base = () => ({ token0: text('token0'), token1: text('token1'), fee: integer('fee') });\n const uniqueKey = text('uniqueKey');\n const position = () => ({\n ...base(),\n uniqueKey,\n tickLower: integer('tickLower'),\n tickUpper: integer('tickUpper'),\n });\n switch (method) {\n case 'RequestTrade': {\n const qty = ['sell0Qty', 'sell1Qty', 'buy0Qty', 'buy1Qty'].filter(\n (f) => dto[f] !== undefined,\n );\n const [field] = qty;\n if (qty.length !== 1 || field === undefined) {\n throw invalidDto(method, 'sell0Qty|sell1Qty|buy0Qty|buy1Qty', 'exactly_once');\n }\n const tokenInIsToken0 = field === 'sell0Qty' || field === 'buy1Qty';\n const shared = { ...base(), uniqueKey, tokenInIsToken0 };\n const priceLimit = optionalText('priceLimit');\n return field.startsWith('sell')\n ? buildRequestTradeDto({\n ...shared,\n exactIn: text(field),\n amountOutMinimum: optionalText('amountOutMinimum'),\n priceLimit,\n })\n : buildRequestTradeDto({\n ...shared,\n exactOut: text(field),\n amountInMaximum: optionalText('amountInMaximum'),\n priceLimit,\n });\n }\n case 'AddLiquidity': {\n const token0Side = optionalText('depositQuantityToken0');\n const token1Side = optionalText('depositQuantityToken1');\n return buildAddLiquidityDto({\n ...position(),\n ...(token0Side === undefined\n ? { depositQuantityToken1: token1Side as string }\n : { depositQuantityToken0: token0Side }),\n });\n }\n case 'RemoveLiquidity':\n return buildRemoveLiquidityDto({\n ...position(),\n withdrawalQuantityToken0: optionalText('withdrawalQuantityToken0'),\n withdrawalQuantityToken1: optionalText('withdrawalQuantityToken1'),\n });\n case 'CollectPositionFees':\n return buildCollectPositionFeesDto(position());\n case 'CreatePool': {\n const price = optionalText('startingPrice');\n const sqrtPrice = optionalText('startingSqrtPrice');\n const privateAccess = dto['privateAccess'];\n if (\n privateAccess !== undefined &&\n (!Array.isArray(privateAccess) || privateAccess.some((entry) => typeof entry !== 'string'))\n ) {\n throw invalidDto(method, 'privateAccess', 'string_array');\n }\n if (dto['isPrivate'] !== undefined && typeof dto['isPrivate'] !== 'boolean') {\n throw invalidDto(method, 'isPrivate', 'boolean');\n }\n return buildCreatePoolDto({\n token0Key: classKey('token0Key'),\n token1Key: classKey('token1Key'),\n token0Symbol: text('token0Symbol'),\n token1Symbol: text('token1Symbol'),\n fee: integer('fee'),\n uniqueKey,\n name: optionalText('name'),\n isPrivate: dto['isPrivate'],\n privateAccess: privateAccess as string[] | undefined,\n ...(price === undefined\n ? { startingSqrtPrice: sqrtPrice as string }\n : { startingPrice: price }),\n });\n }\n }\n}\n\n/**\n * Asserts that `dto` is EXACTLY what the builder of `method` produces for its own fields — the\n * same fields, canonical decimal text, builder key order, nothing extra (no `signature`). The\n * `submit*` methods use it so that what an app showed a user (a DTO it built) is byte for byte\n * what gets signed; a DTO edited by hand, or built elsewhere, is refused rather than signed.\n *\n * @throws GSwapSDKError `INVALID_DTO` or `DTO_NOT_CANONICAL`.\n */\nexport function assertCanonicalV2Dto(method: V2WriteMethod, dto: Record<string, unknown>): void {\n const rebuilt = rebuildV2Dto(method, dto);\n if (JSON.stringify(rebuilt) !== JSON.stringify(dto)) {\n throw new GSwapSDKError('DTO_NOT_CANONICAL', { method });\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"submit_state.js","names":["modeOf","data","envelope","\"data\"","\"mode\"","recordOf","value","isSubmitRejected","\"error\"","isSubmitExecuted","record","isSubmitQueued","\"uniqueKey\"","classifySubmitEnvelope"],"sources":["../../../src/utils/submit_state.ts"],"sourcesContent":["/**\n * What a write's gateway answer says about the write, as one vocabulary for every app.\n *\n * The gateway answers `{ status, message, error, data }`. `data.mode` is `'sync'` for a liquidity\n * write (its executed chain result is in `data.result`) and `'queued'` for a RequestTrade\n * acknowledgement (accepted, NOT executed: the outcome is read from apply-results with\n * `confirm()`). `error: true` is the gateway refusing the write.\n *\n * A {@link SubmittedTransaction} only exists for an answer the SDK accepted, so its\n * {@link SubmitStatus} is `'executed'` or `'queued'`; a refused answer is a thrown\n * {@link GSwapSDKError}. The predicates below classify a RAW envelope instead, for an app that\n * holds one (`SubmittedTransaction.envelope`, `GSwapSDKError.body`, a stored answer) and for the\n * answers that never became a transaction. They reproduce frontend_v2's `isV2SubmitSuccess` /\n * `isV2SubmitQueued` / `isV2SubmitRejected` exactly, so the app can delete its own copies.\n */\n\n/** The state of an accepted write: executed on chain (`mode: 'sync'`) or queued (`mode: 'queued'`). */\nexport type SubmitStatus = 'executed' | 'queued';\n\n/**\n * How a raw gateway envelope classifies:\n *\n * - `'executed'`: `error === false` and `data.mode === 'sync'` (the executed result came back).\n * - `'queued'`: `error === false`, `data.mode === 'queued'` and `data.uniqueKey` is a string.\n * - `'rejected'`: no envelope at all, or `error === true`: a DEFINITE failure.\n * - `'unconfirmed'`: anything else (`error: false` with another or no `mode`, a queued answer\n * with no `uniqueKey`, an envelope with no boolean `error`): accepted, outcome not known. It is\n * NOT a rejection and must not be shown as one.\n *\n * The four are mutually exclusive and exhaustive.\n */\nexport type SubmitClassification = SubmitStatus | 'rejected' | 'unconfirmed';\n\n/** The `mode` of `envelope.data`, or `undefined` when there is no such field. */\nfunction modeOf(envelope: Record<string, unknown>): unknown {\n const data = envelope['data'];\n return typeof data === 'object' && data !== null\n ? (data as Record<string, unknown>)['mode']\n : undefined;\n}\n\nfunction recordOf(value: unknown): Record<string, unknown> | undefined {\n return typeof value === 'object' && value !== null\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\n/** True when the envelope is a definite failure: absent, or `error === true`. */\nexport function isSubmitRejected(envelope: unknown): boolean {\n if (!envelope) return true;\n return recordOf(envelope)?.['error'] === true;\n}\n\n/**\n * True when the envelope carries an executed result: `error === false` and `data.mode === 'sync'`.\n * A queued RequestTrade acknowledgement is deliberately NOT executed: its outcome is not known\n * until apply-results resolves.\n */\nexport function isSubmitExecuted(envelope: unknown): boolean {\n const record = recordOf(envelope);\n return record?.['error'] === false && modeOf(record) === 'sync';\n}\n\n/** True when the envelope acknowledges a `uniqueKey` as queued (`data.mode === 'queued'`). */\nexport function isSubmitQueued(envelope: unknown): boolean {\n const record = recordOf(envelope);\n if (record?.['error'] !== false || modeOf(record) !== 'queued') return false;\n return typeof recordOf(record['data'])?.['uniqueKey'] === 'string';\n}\n\n/** Classifies a raw gateway envelope; see {@link SubmitClassification}. */\nexport function classifySubmitEnvelope(envelope: unknown): SubmitClassification {\n if (isSubmitRejected(envelope)) return 'rejected';\n if (isSubmitExecuted(envelope)) return 'executed';\n if (isSubmitQueued(envelope)) return 'queued';\n return 'unconfirmed';\n}\n"],"mappings":"AAkCA,SAASA,EAAO,EAA4C,CAC1D,IAAMC,EAAOC,EAASC,KACtB,OAAO,OAAOF,GAAS,UAAYA,EAC9BA,EAAiCG,KAClC,IAAA,EACN,CAEA,SAASC,EAAS,EAAqD,CACrE,OAAO,OAAOC,GAAU,UAAYA,EAC/BA,EACD,IAAA,EACN,CAGA,SAAgBC,EAAiB,EAA4B,CAE3D,MADA,CAAKL,GACEG,EAASH,CAAQ,CAAC,EAAGM,QAAa,EAC3C,CAOA,SAAgBC,EAAiB,EAA4B,CAC3D,IAAMC,EAASL,EAASH,CAAQ,EAChC,OAAOQ,GAASF,QAAa,IAASR,EAAOU,CAAM,IAAM,MAC3D,CAGA,SAAgBC,EAAe,EAA4B,CACzD,IAAMD,EAASL,EAASH,CAAQ,EAEhC,OADIQ,GAASF,QAAa,IAASR,EAAOU,CAAM,IAAM,SAAiB,GAChE,OAAOL,EAASK,EAAOP,IAAO,CAAC,EAAGS,WAAiB,QAC5D,CAGA,SAAgBC,EAAuB,EAAyC,CAI9E,OAHIN,EAAiBL,CAAQ,EAAU,WACnCO,EAAiBP,CAAQ,EAAU,WACnCS,EAAeT,CAAQ,EAAU,SAC9B,aACT"}
1
+ {"version":3,"file":"submit_state.js","names":["modeOf","data","envelope","\"data\"","\"mode\"","recordOf","value","isSubmitRejected","\"error\"","isSubmitExecuted","record","isSubmitQueued","\"uniqueKey\"","classifySubmitEnvelope"],"sources":["../../../src/utils/submit_state.ts"],"sourcesContent":["/**\n * What a write's gateway answer says about the write, as one vocabulary for every app.\n *\n * The gateway answers `{ status, message, error, data }`. `data.mode` is `'sync'` for a liquidity\n * write (its executed chain result is in `data.result`) and `'queued'` for a RequestTrade\n * acknowledgement (accepted, NOT executed: the outcome is read from apply-results with\n * `confirm()`). `error: true` is the gateway refusing the write.\n *\n * A {@link SubmittedTransaction} only exists for an answer the SDK accepted, so its\n * {@link SubmitStatus} is `'executed'` or `'queued'`; a refused answer is a thrown\n * {@link GSwapSDKError}. The predicates below classify a RAW envelope instead, for an app that\n * holds one (`SubmittedTransaction.envelope`, `GSwapSDKError.body`, a stored answer) and for the\n * answers that never became a transaction, so an app does not need its own copies of the\n * success, queued and rejected checks.\n */\n\n/** The state of an accepted write: executed on chain (`mode: 'sync'`) or queued (`mode: 'queued'`). */\nexport type SubmitStatus = 'executed' | 'queued';\n\n/**\n * How a raw gateway envelope classifies:\n *\n * - `'executed'`: `error === false` and `data.mode === 'sync'` (the executed result came back).\n * - `'queued'`: `error === false`, `data.mode === 'queued'` and `data.uniqueKey` is a string.\n * - `'rejected'`: no envelope at all, or `error === true`: a DEFINITE failure.\n * - `'unconfirmed'`: anything else (`error: false` with another or no `mode`, a queued answer\n * with no `uniqueKey`, an envelope with no boolean `error`): accepted, outcome not known. It is\n * NOT a rejection and must not be shown as one.\n *\n * The four are mutually exclusive and exhaustive.\n */\nexport type SubmitClassification = SubmitStatus | 'rejected' | 'unconfirmed';\n\n/** The `mode` of `envelope.data`, or `undefined` when there is no such field. */\nfunction modeOf(envelope: Record<string, unknown>): unknown {\n const data = envelope['data'];\n return typeof data === 'object' && data !== null\n ? (data as Record<string, unknown>)['mode']\n : undefined;\n}\n\nfunction recordOf(value: unknown): Record<string, unknown> | undefined {\n return typeof value === 'object' && value !== null\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\n/** True when the envelope is a definite failure: absent, or `error === true`. */\nexport function isSubmitRejected(envelope: unknown): boolean {\n if (!envelope) return true;\n return recordOf(envelope)?.['error'] === true;\n}\n\n/**\n * True when the envelope carries an executed result: `error === false` and `data.mode === 'sync'`.\n * A queued RequestTrade acknowledgement is deliberately NOT executed: its outcome is not known\n * until apply-results resolves.\n */\nexport function isSubmitExecuted(envelope: unknown): boolean {\n const record = recordOf(envelope);\n return record?.['error'] === false && modeOf(record) === 'sync';\n}\n\n/** True when the envelope acknowledges a `uniqueKey` as queued (`data.mode === 'queued'`). */\nexport function isSubmitQueued(envelope: unknown): boolean {\n const record = recordOf(envelope);\n if (record?.['error'] !== false || modeOf(record) !== 'queued') return false;\n return typeof recordOf(record['data'])?.['uniqueKey'] === 'string';\n}\n\n/** Classifies a raw gateway envelope; see {@link SubmitClassification}. */\nexport function classifySubmitEnvelope(envelope: unknown): SubmitClassification {\n if (isSubmitRejected(envelope)) return 'rejected';\n if (isSubmitExecuted(envelope)) return 'executed';\n if (isSubmitQueued(envelope)) return 'queued';\n return 'unconfirmed';\n}\n"],"mappings":"AAkCA,SAASA,EAAO,EAA4C,CAC1D,IAAMC,EAAOC,EAASC,KACtB,OAAO,OAAOF,GAAS,UAAYA,EAC9BA,EAAiCG,KAClC,IAAA,EACN,CAEA,SAASC,EAAS,EAAqD,CACrE,OAAO,OAAOC,GAAU,UAAYA,EAC/BA,EACD,IAAA,EACN,CAGA,SAAgBC,EAAiB,EAA4B,CAE3D,MADA,CAAKL,GACEG,EAASH,CAAQ,CAAC,EAAGM,QAAa,EAC3C,CAOA,SAAgBC,EAAiB,EAA4B,CAC3D,IAAMC,EAASL,EAASH,CAAQ,EAChC,OAAOQ,GAASF,QAAa,IAASR,EAAOU,CAAM,IAAM,MAC3D,CAGA,SAAgBC,EAAe,EAA4B,CACzD,IAAMD,EAASL,EAASH,CAAQ,EAEhC,OADIQ,GAASF,QAAa,IAASR,EAAOU,CAAM,IAAM,SAAiB,GAChE,OAAOL,EAASK,EAAOP,IAAO,CAAC,EAAGS,WAAiB,QAC5D,CAGA,SAAgBC,EAAuB,EAAyC,CAI9E,OAHIN,EAAiBL,CAAQ,EAAU,WACnCO,EAAiBP,CAAQ,EAAU,WACnCS,EAAeT,CAAQ,EAAU,SAC9B,aACT"}
@@ -1,2 +1,2 @@
1
- import{ALLOWED_FEE_TIERS_TEXT as e}from"../classes/gswap_error_catalog.js";import{GSwapSDKError as t}from"../classes/gswap_sdk_error.js";import{ALL_FEE_TIERS as n}from"../types/fees.js";import{GC_MAX_TICK as r,GC_MIN_TICK as i}from"./tick_math.js";import{galaChainUserRef as a}from"@gala-chain/gala-core-sdk";import o from"bignumber.js";function s(e,n,r=!1){if(typeof e==`number`)throw new t(`AMOUNT_IS_JS_NUMBER`,{field:n});let i;try{i=o(e)}catch{i=o(NaN)}if(!i.isFinite())throw new t(`AMOUNT_NOT_FINITE`,{field:n,value:String(e)});if(!r&&i.isZero())throw new t(`AMOUNT_NOT_POSITIVE`,{field:n,value:String(e)});if(i.isNegative())throw new t(`AMOUNT_NEGATIVE`,{field:n,value:String(e)})}function c(e,n,r){if([e,n,r].some(e=>typeof e==`number`))throw new t(`PRICE_VALUES_IS_JS_NUMBER`,{});let i=e=>{try{return o(e)}catch{return o(NaN)}},a=i(e),s=i(n),c=i(r);if(c=c.isFinite()?c:o(0xde0b6b3a7640000),!a.isFinite()||!s.isFinite()||!c.isFinite()||!a.isPositive()||!s.isPositive()||!c.isPositive())throw new t(`INVALID_PRICE_VALUES`,{spotPrice:String(e),lowerPrice:String(n),upperPrice:String(r)});if(s.isGreaterThan(c))throw new t(`INVALID_PRICE_RANGE`,{lowerPrice:String(n),upperPrice:String(r)})}function l(e,n){if(e<0||!Number.isInteger(e))throw new t(`INVALID_TOKEN_DECIMALS`,{field:n,value:e})}function u(e,n){if(!Number.isInteger(e)||!Number.isInteger(n))throw new t(`TICKS_NOT_INTEGERS`,{tickLower:e,tickUpper:n});if(e>=n)throw new t(`INVALID_TICK_RANGE`,{tickLower:e,tickUpper:n});if(e<-887272||n>887272)throw new t(`TICKS_OUT_OF_BOUNDS`,{tickLower:e,tickUpper:n,minTick:i,maxTick:r})}function d(r){if(!n.some(e=>Number(e)===r))throw new t(`INVALID_FEE`,{field:`fee`,value:r,allowed:e})}function f(e){if(!Number.isInteger(e)||e<=0)throw new t(`INVALID_TICK_SPACING`,{value:e})}function p(e,n){try{return a(e,n)}catch{throw new t(`INVALID_WALLET_ADDRESS`,{field:n,value:String(e)})}}function m(e,n){if(e===void 0)throw new t(`MISSING_WALLET_ADDRESS`,{});return p(e,n)}const h=/* @__PURE__ */ new Set([`Token`,`Unit`,`none`]),g=/^[A-Za-z0-9]+$/u;function _(e){return g.test(e)&&e.length<=32&&!h.has(e)}function v(e,n){if(!(e<n))throw new t(`NON_CANONICAL_PAIR`,{token0:e,token1:n})}export{v as assertCanonicalPair,_ as isProposableTradingSymbol,m as requireUserRef,p as userRef,d as validateFee,s as validateNumericAmount,c as validatePriceValues,u as validateTickRange,f as validateTickSpacing,l as validateTokenDecimals};
1
+ import{ALLOWED_FEE_TIERS_TEXT as e}from"../classes/gswap_error_catalog.js";import{GSwapSDKError as t}from"../classes/gswap_sdk_error.js";import{ALL_FEE_TIERS as n}from"../types/fees.js";import{GC_MAX_TICK as r,GC_MIN_TICK as i}from"./tick_math.js";import{galaChainUserRef as a,isGalaError as o}from"@gala-chain/gala-core-sdk";import s from"bignumber.js";function c(e,n,r=!1){if(typeof e==`number`)throw new t(`AMOUNT_IS_JS_NUMBER`,{field:n});let i;try{i=s(e)}catch{i=s(NaN)}if(!i.isFinite())throw new t(`AMOUNT_NOT_FINITE`,{field:n,value:String(e)});if(!r&&i.isZero())throw new t(`AMOUNT_NOT_POSITIVE`,{field:n,value:String(e)});if(i.isNegative())throw new t(`AMOUNT_NEGATIVE`,{field:n,value:String(e)})}function l(e,n,r){if([e,n,r].some(e=>typeof e==`number`))throw new t(`PRICE_VALUES_IS_JS_NUMBER`,{});let i=e=>{try{return s(e)}catch{return s(NaN)}},a=i(e),o=i(n),c=i(r);if(c=c.isFinite()?c:s(0xde0b6b3a7640000),!a.isFinite()||!o.isFinite()||!c.isFinite()||!a.isPositive()||!o.isPositive()||!c.isPositive())throw new t(`INVALID_PRICE_VALUES`,{spotPrice:String(e),lowerPrice:String(n),upperPrice:String(r)});if(o.isGreaterThan(c))throw new t(`INVALID_PRICE_RANGE`,{lowerPrice:String(n),upperPrice:String(r)})}function u(e,n){if(e<0||!Number.isInteger(e))throw new t(`INVALID_TOKEN_DECIMALS`,{field:n,value:e})}function d(e,n){if(!Number.isInteger(e)||!Number.isInteger(n))throw new t(`TICKS_NOT_INTEGERS`,{tickLower:e,tickUpper:n});if(e>=n)throw new t(`INVALID_TICK_RANGE`,{tickLower:e,tickUpper:n});if(e<-887272||n>887272)throw new t(`TICKS_OUT_OF_BOUNDS`,{tickLower:e,tickUpper:n,minTick:i,maxTick:r})}function f(r){if(!n.some(e=>Number(e)===r))throw new t(`INVALID_FEE`,{field:`fee`,value:r,allowed:e})}function p(e){if(!Number.isInteger(e)||e<=0)throw new t(`INVALID_TICK_SPACING`,{value:e})}function m(e,n){try{return a(e,n)}catch(r){throw o(r)&&r.code===`INVALID_ETH_USER_REF_PREFIX`?r:new t(`INVALID_WALLET_ADDRESS`,{field:n,value:String(e)})}}function h(e,n){if(e===void 0)throw new t(`MISSING_WALLET_ADDRESS`,{});return m(e,n)}const g=/* @__PURE__ */ new Set([`Token`,`Unit`,`none`]),_=/^[A-Za-z0-9]+$/u;function v(e){return _.test(e)&&e.length<=32&&!g.has(e)}function y(e,n){if(!(e<n))throw new t(`NON_CANONICAL_PAIR`,{token0:e,token1:n})}export{y as assertCanonicalPair,v as isProposableTradingSymbol,h as requireUserRef,m as userRef,f as validateFee,c as validateNumericAmount,l as validatePriceValues,d as validateTickRange,p as validateTickSpacing,u as validateTokenDecimals};
2
2
  //# sourceMappingURL=validation.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"validation.js","names":["BigNumber","validateNumericAmount","amount","GSwapSDKError","parameterName","bnAmount","allowZero","validatePriceValues","spotPrice","lowerPrice","upperPrice","value","parse","bnSpotPrice","bnLowerPrice","bnUpperPrice","validateTokenDecimals","decimals","validateTickRange","tickLower","tickUpper","GC_MIN_TICK","GC_MAX_TICK","validateFee","ALL_FEE_TIERS","tier","fee","ALLOWED_FEE_TIERS_TEXT","validateTickSpacing","tickSpacing","userRef","galaChainUserRef","field","requireUserRef","address","RESERVED_TRADING_SYMBOLS","TRADING_SYMBOL_PATTERN","isProposableTradingSymbol","symbol","assertCanonicalPair","token0","token1"],"sources":["../../../src/utils/validation.ts"],"sourcesContent":["import { galaChainUserRef } from '@gala-chain/gala-core-sdk';\nimport BigNumber from 'bignumber.js';\nimport { ALLOWED_FEE_TIERS_TEXT } from '../classes/gswap_error_catalog.js';\nimport { GSwapSDKError } from '../classes/gswap_sdk_error.js';\nimport type { NumericAmount } from '../types/amounts.js';\nimport { ALL_FEE_TIERS } from '../types/fees.js';\nimport type { FEE_TIER } from '../types/fees.js';\nimport { GC_MAX_TICK, GC_MIN_TICK } from './tick_math.js';\n\n/**\n * Validates a decimal amount: a string or `BigNumber`, never a JavaScript number.\n *\n * @param amount - The amount to check.\n * @param parameterName - The field name reported in the error's `params.field`.\n * @param allowZero - Accept zero (default `false`).\n * @throws GSwapSDKError `AMOUNT_IS_JS_NUMBER`, `AMOUNT_NOT_FINITE`, `AMOUNT_NOT_POSITIVE` (zero\n * when `allowZero` is false) or `AMOUNT_NEGATIVE`.\n */\nexport function validateNumericAmount(\n amount: NumericAmount,\n parameterName: string,\n allowZero = false,\n): void {\n if (typeof amount === 'number') {\n throw new GSwapSDKError('AMOUNT_IS_JS_NUMBER', { field: parameterName });\n }\n // bignumber.js 11+ throws on an unparseable value instead of constructing a NaN\n // instance — substitute an explicit NaN so the isFinite() check below still catches\n // it and raises the same typed error, instead of a raw exception escaping.\n let bnAmount: BigNumber;\n try {\n bnAmount = BigNumber(amount);\n } catch {\n bnAmount = BigNumber(NaN);\n }\n\n if (!bnAmount.isFinite()) {\n throw new GSwapSDKError('AMOUNT_NOT_FINITE', {\n field: parameterName,\n value: String(amount),\n });\n }\n\n if (!allowZero && bnAmount.isZero()) {\n throw new GSwapSDKError('AMOUNT_NOT_POSITIVE', {\n field: parameterName,\n value: String(amount),\n });\n }\n\n if (bnAmount.isNegative()) {\n throw new GSwapSDKError('AMOUNT_NEGATIVE', { field: parameterName, value: String(amount) });\n }\n}\n\n/**\n * Validates a spot price and a lower/upper price range: none a JavaScript number, all positive and\n * finite (a non-finite upper bound counts as an unbounded `1e18`), and `lowerPrice <= upperPrice`.\n *\n * @throws GSwapSDKError `PRICE_VALUES_IS_JS_NUMBER`, `INVALID_PRICE_VALUES` or `INVALID_PRICE_RANGE`.\n */\nexport function validatePriceValues(\n spotPrice: NumericAmount,\n lowerPrice: NumericAmount,\n upperPrice: NumericAmount,\n): void {\n if ([spotPrice, lowerPrice, upperPrice].some((value) => typeof value === 'number')) {\n throw new GSwapSDKError('PRICE_VALUES_IS_JS_NUMBER', {});\n }\n // bignumber.js 11+ throws on an unparseable value instead of constructing a NaN\n // instance — substitute an explicit NaN so the isFinite()/isPositive() checks below\n // still catch it and raise the same typed error, instead of a raw exception escaping.\n const parse = (value: NumericAmount): BigNumber => {\n try {\n return BigNumber(value);\n } catch {\n return BigNumber(NaN);\n }\n };\n const bnSpotPrice = parse(spotPrice);\n const bnLowerPrice = parse(lowerPrice);\n let bnUpperPrice = parse(upperPrice);\n\n bnUpperPrice = bnUpperPrice.isFinite() ? bnUpperPrice : BigNumber(1e18);\n\n if (\n !bnSpotPrice.isFinite() ||\n !bnLowerPrice.isFinite() ||\n !bnUpperPrice.isFinite() ||\n !bnSpotPrice.isPositive() ||\n !bnLowerPrice.isPositive() ||\n !bnUpperPrice.isPositive()\n ) {\n throw new GSwapSDKError('INVALID_PRICE_VALUES', {\n spotPrice: String(spotPrice),\n lowerPrice: String(lowerPrice),\n upperPrice: String(upperPrice),\n });\n }\n\n if (bnLowerPrice.isGreaterThan(bnUpperPrice)) {\n throw new GSwapSDKError('INVALID_PRICE_RANGE', {\n lowerPrice: String(lowerPrice),\n upperPrice: String(upperPrice),\n });\n }\n}\n\n/**\n * Validates a token's decimals: a non-negative integer.\n *\n * @throws GSwapSDKError `INVALID_TOKEN_DECIMALS` (`params.field` is `parameterName`).\n */\nexport function validateTokenDecimals(decimals: number, parameterName: string): void {\n if (decimals < 0 || !Number.isInteger(decimals)) {\n throw new GSwapSDKError('INVALID_TOKEN_DECIMALS', { field: parameterName, value: decimals });\n }\n}\n\n/**\n * Validates a position's tick range: two integers with `tickLower < tickUpper`, both within\n * `GC_MIN_TICK`..`GC_MAX_TICK`. It does not check alignment to a fee tier's spacing (see\n * `assertTickRange`).\n *\n * @throws GSwapSDKError `TICKS_NOT_INTEGERS`, `INVALID_TICK_RANGE` or `TICKS_OUT_OF_BOUNDS`.\n */\nexport function validateTickRange(tickLower: number, tickUpper: number): void {\n if (!Number.isInteger(tickLower) || !Number.isInteger(tickUpper)) {\n throw new GSwapSDKError('TICKS_NOT_INTEGERS', { tickLower, tickUpper });\n }\n\n if (tickLower >= tickUpper) {\n throw new GSwapSDKError('INVALID_TICK_RANGE', { tickLower, tickUpper });\n }\n\n if (tickLower < GC_MIN_TICK || tickUpper > GC_MAX_TICK) {\n throw new GSwapSDKError('TICKS_OUT_OF_BOUNDS', {\n tickLower,\n tickUpper,\n minTick: GC_MIN_TICK,\n maxTick: GC_MAX_TICK,\n });\n }\n}\n\n/**\n * Asserts that `fee` is one of the fee tiers in `ALL_FEE_TIERS` (`0`, `500`, `3000`, `10000`).\n *\n * @throws GSwapSDKError `INVALID_FEE` (`params.field` `'fee'`, `params.allowed` the tiers).\n */\nexport function validateFee(fee: number): asserts fee is FEE_TIER {\n if (!ALL_FEE_TIERS.some((tier) => Number(tier) === fee)) {\n throw new GSwapSDKError('INVALID_FEE', {\n field: 'fee',\n value: fee,\n allowed: ALLOWED_FEE_TIERS_TEXT,\n });\n }\n}\n\n/**\n * Validates a tick spacing: a positive integer.\n *\n * @throws GSwapSDKError `INVALID_TICK_SPACING`.\n */\nexport function validateTickSpacing(tickSpacing: number): void {\n if (!Number.isInteger(tickSpacing) || tickSpacing <= 0) {\n throw new GSwapSDKError('INVALID_TICK_SPACING', { value: tickSpacing });\n }\n}\n\n/**\n * The GalaChain user reference an owner, user or wallet address names, in the one spelling the\n * gateway and the swap backend accept: `eth|<EIP-55 checksummed digits>` for an Ethereum account\n * (given as `eth|<40 hex>` in any casing, `eth|0x<40 hex>`, `0x<40 hex>` or bare hex), `client|<id>`\n * as written. This is `galaChainUserRef` of `@gala-chain/gala-core-sdk`, the one implementation;\n * only the error is this package's. Every place this SDK puts a user identity on the wire goes\n * through it, so what is signed, sent and compared on the backend (`owner === position.owner`,\n * strict) is always the chain's own spelling.\n *\n * @param value - The identity the caller gave.\n * @param field - The field name reported in the error's `params.field`.\n * @throws GSwapSDKError `INVALID_WALLET_ADDRESS` for anything else, a mixed-case spelling whose\n * casing is not the EIP-55 checksum included (a typo); nothing is signed or sent.\n */\nexport function userRef(value: unknown, field: string): string {\n try {\n return galaChainUserRef(value, field);\n } catch {\n throw new GSwapSDKError('INVALID_WALLET_ADDRESS', { field, value: String(value) });\n }\n}\n\n/**\n * {@link userRef} of an address that must be there.\n *\n * @throws GSwapSDKError `MISSING_WALLET_ADDRESS` (undefined) or `INVALID_WALLET_ADDRESS`.\n */\nexport function requireUserRef(address: string | undefined, field: string): string {\n if (address === undefined) {\n throw new GSwapSDKError('MISSING_WALLET_ADDRESS', {});\n }\n return userRef(address, field);\n}\n\n/** Words the chain gateway refuses as a trading symbol. */\nconst RESERVED_TRADING_SYMBOLS = new Set(['Token', 'Unit', 'none']);\nconst TRADING_SYMBOL_PATTERN = /^[A-Za-z0-9]+$/u;\nconst MAX_TRADING_SYMBOL_LENGTH = 32;\n\n/**\n * True when `symbol` is acceptable to propose into the trading-symbol registry: ASCII letters and\n * digits only, 1 to 32 characters, and not a reserved word (`Token`, `Unit`, `none`) — the rules\n * the chain gateway enforces on a CreatePool symbol.\n */\nexport function isProposableTradingSymbol(symbol: string): boolean {\n return (\n TRADING_SYMBOL_PATTERN.test(symbol) &&\n symbol.length <= MAX_TRADING_SYMBOL_LENGTH &&\n !RESERVED_TRADING_SYMBOLS.has(symbol)\n );\n}\n\n/**\n * Asserts that a pair is in the contract's canonical order (`token0 < token1` by plain string\n * comparison), which every write DTO requires. A pair a caller took from a stale quote or\n * position can be reversed; signing it would sign something other than what was priced.\n *\n * @throws GSwapSDKError `NON_CANONICAL_PAIR` (`params.token0`, `params.token1`) otherwise.\n */\nexport function assertCanonicalPair(token0: string, token1: string): void {\n if (!(token0 < token1)) {\n throw new GSwapSDKError('NON_CANONICAL_PAIR', { token0, token1 });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"validation.js","names":["BigNumber","validateNumericAmount","amount","GSwapSDKError","parameterName","bnAmount","allowZero","validatePriceValues","spotPrice","lowerPrice","upperPrice","value","parse","bnSpotPrice","bnLowerPrice","bnUpperPrice","validateTokenDecimals","decimals","validateTickRange","tickLower","tickUpper","GC_MIN_TICK","GC_MAX_TICK","validateFee","ALL_FEE_TIERS","tier","fee","ALLOWED_FEE_TIERS_TEXT","validateTickSpacing","tickSpacing","userRef","galaChainUserRef","field","cause","isGalaError","requireUserRef","address","RESERVED_TRADING_SYMBOLS","TRADING_SYMBOL_PATTERN","isProposableTradingSymbol","symbol","assertCanonicalPair","token0","token1"],"sources":["../../../src/utils/validation.ts"],"sourcesContent":["import { galaChainUserRef, isGalaError } from '@gala-chain/gala-core-sdk';\nimport BigNumber from 'bignumber.js';\nimport { ALLOWED_FEE_TIERS_TEXT } from '../classes/gswap_error_catalog.js';\nimport { GSwapSDKError } from '../classes/gswap_sdk_error.js';\nimport type { NumericAmount } from '../types/amounts.js';\nimport { ALL_FEE_TIERS } from '../types/fees.js';\nimport type { FEE_TIER } from '../types/fees.js';\nimport { GC_MAX_TICK, GC_MIN_TICK } from './tick_math.js';\n\n/**\n * Validates a decimal amount: a string or `BigNumber`, never a JavaScript number.\n *\n * @param amount - The amount to check.\n * @param parameterName - The field name reported in the error's `params.field`.\n * @param allowZero - Accept zero (default `false`).\n * @throws GSwapSDKError `AMOUNT_IS_JS_NUMBER`, `AMOUNT_NOT_FINITE`, `AMOUNT_NOT_POSITIVE` (zero\n * when `allowZero` is false) or `AMOUNT_NEGATIVE`.\n */\nexport function validateNumericAmount(\n amount: NumericAmount,\n parameterName: string,\n allowZero = false,\n): void {\n if (typeof amount === 'number') {\n throw new GSwapSDKError('AMOUNT_IS_JS_NUMBER', { field: parameterName });\n }\n // bignumber.js 11+ throws on an unparseable value instead of constructing a NaN\n // instance — substitute an explicit NaN so the isFinite() check below still catches\n // it and raises the same typed error, instead of a raw exception escaping.\n let bnAmount: BigNumber;\n try {\n bnAmount = BigNumber(amount);\n } catch {\n bnAmount = BigNumber(NaN);\n }\n\n if (!bnAmount.isFinite()) {\n throw new GSwapSDKError('AMOUNT_NOT_FINITE', {\n field: parameterName,\n value: String(amount),\n });\n }\n\n if (!allowZero && bnAmount.isZero()) {\n throw new GSwapSDKError('AMOUNT_NOT_POSITIVE', {\n field: parameterName,\n value: String(amount),\n });\n }\n\n if (bnAmount.isNegative()) {\n throw new GSwapSDKError('AMOUNT_NEGATIVE', { field: parameterName, value: String(amount) });\n }\n}\n\n/**\n * Validates a spot price and a lower/upper price range: none a JavaScript number, all positive and\n * finite (a non-finite upper bound counts as an unbounded `1e18`), and `lowerPrice <= upperPrice`.\n *\n * @throws GSwapSDKError `PRICE_VALUES_IS_JS_NUMBER`, `INVALID_PRICE_VALUES` or `INVALID_PRICE_RANGE`.\n */\nexport function validatePriceValues(\n spotPrice: NumericAmount,\n lowerPrice: NumericAmount,\n upperPrice: NumericAmount,\n): void {\n if ([spotPrice, lowerPrice, upperPrice].some((value) => typeof value === 'number')) {\n throw new GSwapSDKError('PRICE_VALUES_IS_JS_NUMBER', {});\n }\n // bignumber.js 11+ throws on an unparseable value instead of constructing a NaN\n // instance — substitute an explicit NaN so the isFinite()/isPositive() checks below\n // still catch it and raise the same typed error, instead of a raw exception escaping.\n const parse = (value: NumericAmount): BigNumber => {\n try {\n return BigNumber(value);\n } catch {\n return BigNumber(NaN);\n }\n };\n const bnSpotPrice = parse(spotPrice);\n const bnLowerPrice = parse(lowerPrice);\n let bnUpperPrice = parse(upperPrice);\n\n bnUpperPrice = bnUpperPrice.isFinite() ? bnUpperPrice : BigNumber(1e18);\n\n if (\n !bnSpotPrice.isFinite() ||\n !bnLowerPrice.isFinite() ||\n !bnUpperPrice.isFinite() ||\n !bnSpotPrice.isPositive() ||\n !bnLowerPrice.isPositive() ||\n !bnUpperPrice.isPositive()\n ) {\n throw new GSwapSDKError('INVALID_PRICE_VALUES', {\n spotPrice: String(spotPrice),\n lowerPrice: String(lowerPrice),\n upperPrice: String(upperPrice),\n });\n }\n\n if (bnLowerPrice.isGreaterThan(bnUpperPrice)) {\n throw new GSwapSDKError('INVALID_PRICE_RANGE', {\n lowerPrice: String(lowerPrice),\n upperPrice: String(upperPrice),\n });\n }\n}\n\n/**\n * Validates a token's decimals: a non-negative integer.\n *\n * @throws GSwapSDKError `INVALID_TOKEN_DECIMALS` (`params.field` is `parameterName`).\n */\nexport function validateTokenDecimals(decimals: number, parameterName: string): void {\n if (decimals < 0 || !Number.isInteger(decimals)) {\n throw new GSwapSDKError('INVALID_TOKEN_DECIMALS', { field: parameterName, value: decimals });\n }\n}\n\n/**\n * Validates a position's tick range: two integers with `tickLower < tickUpper`, both within\n * `GC_MIN_TICK`..`GC_MAX_TICK`. It does not check alignment to a fee tier's spacing (see\n * `assertTickRange`).\n *\n * @throws GSwapSDKError `TICKS_NOT_INTEGERS`, `INVALID_TICK_RANGE` or `TICKS_OUT_OF_BOUNDS`.\n */\nexport function validateTickRange(tickLower: number, tickUpper: number): void {\n if (!Number.isInteger(tickLower) || !Number.isInteger(tickUpper)) {\n throw new GSwapSDKError('TICKS_NOT_INTEGERS', { tickLower, tickUpper });\n }\n\n if (tickLower >= tickUpper) {\n throw new GSwapSDKError('INVALID_TICK_RANGE', { tickLower, tickUpper });\n }\n\n if (tickLower < GC_MIN_TICK || tickUpper > GC_MAX_TICK) {\n throw new GSwapSDKError('TICKS_OUT_OF_BOUNDS', {\n tickLower,\n tickUpper,\n minTick: GC_MIN_TICK,\n maxTick: GC_MAX_TICK,\n });\n }\n}\n\n/**\n * Asserts that `fee` is one of the fee tiers in `ALL_FEE_TIERS` (`0`, `500`, `3000`, `10000`).\n *\n * @throws GSwapSDKError `INVALID_FEE` (`params.field` `'fee'`, `params.allowed` the tiers).\n */\nexport function validateFee(fee: number): asserts fee is FEE_TIER {\n if (!ALL_FEE_TIERS.some((tier) => Number(tier) === fee)) {\n throw new GSwapSDKError('INVALID_FEE', {\n field: 'fee',\n value: fee,\n allowed: ALLOWED_FEE_TIERS_TEXT,\n });\n }\n}\n\n/**\n * Validates a tick spacing: a positive integer.\n *\n * @throws GSwapSDKError `INVALID_TICK_SPACING`.\n */\nexport function validateTickSpacing(tickSpacing: number): void {\n if (!Number.isInteger(tickSpacing) || tickSpacing <= 0) {\n throw new GSwapSDKError('INVALID_TICK_SPACING', { value: tickSpacing });\n }\n}\n\n/**\n * The GalaChain user reference an owner, user or wallet address names, in the one spelling the\n * gateway and the swap backend accept: `eth|<EIP-55 checksummed digits>` for an Ethereum account\n * (given as `eth|<40 hex>` in any casing, `0x<40 hex>` or bare hex), `client|<id>` as written.\n * `eth|0x<hex>` is not an address form. This is `galaChainUserRef` of\n * `@gala-chain/gala-core-sdk`, the one implementation; only the error for a spelling that is no\n * address at all is this package's. Every place this SDK puts a user identity on the wire goes\n * through it, so what is signed, sent and compared on the backend (`owner === position.owner`,\n * strict) is always the chain's own spelling.\n *\n * @param value - The identity the caller gave.\n * @param field - The field name reported in the error's `params.field`.\n * @throws GalaSDKError core's `INVALID_ETH_USER_REF_PREFIX` (passed through as it is: its message\n * says an `eth|` reference takes the 40 hex digits with no `0x` and names the correct spelling,\n * `params.expected`) for `eth|0x...`; GSwapSDKError `INVALID_WALLET_ADDRESS` for anything else, a\n * mixed-case spelling whose casing is not the EIP-55 checksum included (a typo); nothing is\n * signed or sent.\n */\nexport function userRef(value: unknown, field: string): string {\n try {\n return galaChainUserRef(value, field);\n } catch (cause: unknown) {\n if (isGalaError(cause) && cause.code === 'INVALID_ETH_USER_REF_PREFIX') throw cause;\n throw new GSwapSDKError('INVALID_WALLET_ADDRESS', { field, value: String(value) });\n }\n}\n\n/**\n * {@link userRef} of an address that must be there.\n *\n * @throws GSwapSDKError `MISSING_WALLET_ADDRESS` (undefined) or `INVALID_WALLET_ADDRESS`; core's\n * `INVALID_ETH_USER_REF_PREFIX` for `eth|0x...` (see {@link userRef}).\n */\nexport function requireUserRef(address: string | undefined, field: string): string {\n if (address === undefined) {\n throw new GSwapSDKError('MISSING_WALLET_ADDRESS', {});\n }\n return userRef(address, field);\n}\n\n/** Words the chain gateway refuses as a trading symbol. */\nconst RESERVED_TRADING_SYMBOLS = new Set(['Token', 'Unit', 'none']);\nconst TRADING_SYMBOL_PATTERN = /^[A-Za-z0-9]+$/u;\nconst MAX_TRADING_SYMBOL_LENGTH = 32;\n\n/**\n * True when `symbol` is acceptable to propose into the trading-symbol registry: ASCII letters and\n * digits only, 1 to 32 characters, and not a reserved word (`Token`, `Unit`, `none`) — the rules\n * the chain gateway enforces on a CreatePool symbol.\n */\nexport function isProposableTradingSymbol(symbol: string): boolean {\n return (\n TRADING_SYMBOL_PATTERN.test(symbol) &&\n symbol.length <= MAX_TRADING_SYMBOL_LENGTH &&\n !RESERVED_TRADING_SYMBOLS.has(symbol)\n );\n}\n\n/**\n * Asserts that a pair is in the contract's canonical order (`token0 < token1` by plain string\n * comparison), which every write DTO requires. A pair a caller took from a stale quote or\n * position can be reversed; signing it would sign something other than what was priced.\n *\n * @throws GSwapSDKError `NON_CANONICAL_PAIR` (`params.token0`, `params.token1`) otherwise.\n */\nexport function assertCanonicalPair(token0: string, token1: string): void {\n if (!(token0 < token1)) {\n throw new GSwapSDKError('NON_CANONICAL_PAIR', { token0, token1 });\n }\n}\n"],"mappings":"kWAkBA,SAAgBC,EACd,EACA,EACA,EAAY,GACN,CACN,GAAI,OAAOC,GAAW,SACpB,MAAM,IAAIC,EAAc,sBAAuB,CAAE,MAAOC,CAAc,CAAC,EAKzE,IAAIC,EACJ,GAAI,CACF,EAAWL,EAAUE,CAAM,CAC7B,MAAQ,CACN,EAAWF,EAAU,GAAG,CAC1B,CAEA,GAAI,CAACK,EAAS,SAAS,EACrB,MAAM,IAAIF,EAAc,oBAAqB,CAC3C,MAAOC,EACP,MAAO,OAAOF,CAAM,CACtB,CAAC,EAGH,GAAI,CAACI,GAAaD,EAAS,OAAO,EAChC,MAAM,IAAIF,EAAc,sBAAuB,CAC7C,MAAOC,EACP,MAAO,OAAOF,CAAM,CACtB,CAAC,EAGH,GAAIG,EAAS,WAAW,EACtB,MAAM,IAAIF,EAAc,kBAAmB,CAAE,MAAOC,EAAe,MAAO,OAAOF,CAAM,CAAE,CAAC,CAE9F,CAQA,SAAgBK,EACd,EACA,EACA,EACM,CACN,GAAI,CAACC,EAAWC,EAAYC,CAAU,CAAC,CAAC,KAAM,GAAU,OAAOC,GAAU,QAAQ,EAC/E,MAAM,IAAIR,EAAc,4BAA6B,CAAC,CAAC,EAKzD,IAAMS,EAAS,GAAoC,CACjD,GAAI,CACF,OAAOZ,EAAUW,CAAK,CACxB,MAAQ,CACN,OAAOX,EAAU,GAAG,CACtB,CACF,EACMa,EAAcD,EAAMJ,CAAS,EAC7BM,EAAeF,EAAMH,CAAU,EACjCM,EAAeH,EAAMF,CAAU,EAInC,GAFA,EAAeK,EAAa,SAAS,EAAIA,EAAef,EAAU,iBAAI,EAGpE,CAACa,EAAY,SAAS,GACtB,CAACC,EAAa,SAAS,GACvB,CAACC,EAAa,SAAS,GACvB,CAACF,EAAY,WAAW,GACxB,CAACC,EAAa,WAAW,GACzB,CAACC,EAAa,WAAW,EAEzB,MAAM,IAAIZ,EAAc,uBAAwB,CAC9C,UAAW,OAAOK,CAAS,EAC3B,WAAY,OAAOC,CAAU,EAC7B,WAAY,OAAOC,CAAU,CAC/B,CAAC,EAGH,GAAII,EAAa,cAAcC,CAAY,EACzC,MAAM,IAAIZ,EAAc,sBAAuB,CAC7C,WAAY,OAAOM,CAAU,EAC7B,WAAY,OAAOC,CAAU,CAC/B,CAAC,CAEL,CAOA,SAAgBM,EAAsB,EAAkB,EAA6B,CACnF,GAAIC,EAAW,GAAK,CAAC,OAAO,UAAUA,CAAQ,EAC5C,MAAM,IAAId,EAAc,yBAA0B,CAAE,MAAOC,EAAe,MAAOa,CAAS,CAAC,CAE/F,CASA,SAAgBC,EAAkB,EAAmB,EAAyB,CAC5E,GAAI,CAAC,OAAO,UAAUC,CAAS,GAAK,CAAC,OAAO,UAAUC,CAAS,EAC7D,MAAM,IAAIjB,EAAc,qBAAsB,CAAE,YAAW,WAAU,CAAC,EAGxE,GAAIgB,GAAaC,EACf,MAAM,IAAIjB,EAAc,qBAAsB,CAAE,YAAW,WAAU,CAAC,EAGxE,GAAIgB,EAAA,SAA2BC,EAAA,OAC7B,MAAM,IAAIjB,EAAc,sBAAuB,CAC7C,YACA,YACA,QAASkB,EACT,QAASC,CACX,CAAC,CAEL,CAOA,SAAgBC,EAAY,EAAsC,CAChE,GAAI,CAACC,EAAc,KAAM,GAAS,OAAOC,CAAI,IAAMC,CAAG,EACpD,MAAM,IAAIvB,EAAc,cAAe,CACrC,MAAO,MACP,MAAOuB,EACP,QAASC,CACX,CAAC,CAEL,CAOA,SAAgBC,EAAoB,EAA2B,CAC7D,GAAI,CAAC,OAAO,UAAUC,CAAW,GAAKA,GAAe,EACnD,MAAM,IAAI1B,EAAc,uBAAwB,CAAE,MAAO0B,CAAY,CAAC,CAE1E,CAoBA,SAAgBC,EAAQ,EAAgB,EAAuB,CAC7D,GAAI,CACF,OAAOC,EAAiBpB,EAAOqB,CAAK,CACtC,OAASC,EAAgB,CAEvB,MADIC,EAAYD,CAAK,GAAKA,EAAM,OAAS,8BAAqCA,EACxE,IAAI9B,EAAc,yBAA0B,CAAE,QAAO,MAAO,OAAOQ,CAAK,CAAE,CAAC,CACnF,CACF,CAQA,SAAgBwB,EAAe,EAA6B,EAAuB,CACjF,GAAIC,IAAY,IAAA,GACd,MAAM,IAAIjC,EAAc,yBAA0B,CAAC,CAAC,EAEtD,OAAO2B,EAAQM,EAASJ,CAAK,CAC/B,CAGA,MAAMK,kBAA2B,IAAI,IAAI,CAAC,QAAS,OAAQ,MAAM,CAAC,EAC5DC,EAAyB,kBAQ/B,SAAgBC,EAA0B,EAAyB,CACjE,OACED,EAAuB,KAAKE,CAAM,GAClCA,EAAO,QAAU,IACjB,CAACH,EAAyB,IAAIG,CAAM,CAExC,CASA,SAAgBC,EAAoB,EAAgB,EAAsB,CACxE,GAAI,EAAEC,EAASC,GACb,MAAM,IAAIxC,EAAc,qBAAsB,CAAE,SAAQ,QAAO,CAAC,CAEpE"}