@rhea-finance/confidential-swap 0.1.8 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/aggregation.cjs.map +1 -1
- package/dist/aggregation.js.map +1 -1
- package/dist/index.cjs +11 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/aggregation.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/aggregation.ts","../src/errors.ts","../src/routing.ts","../src/utils.ts"],"sourcesContent":["import type { AssetRef, ChainRef, Quote, QuoteRequest, SwapClient } from \"@rhea-finance/cross-chain-aggregation-dex\";\nimport { assert, ConfidentialSwapError, responseAssert, throwIfAborted } from \"./errors\";\nimport { normalizeChain } from \"./routing\";\nimport { clone, raw, text } from \"./utils\";\nimport type { ExecutionSnapshot, FundingSwapAdapter, SwapFundingRequest, Token } from \"./types\";\n\nexport interface FundingReporter {\n report(snapshot: ExecutionSnapshot, signal?: AbortSignal): Promise<string>;\n getStatus(recordId: string, snapshot: ExecutionSnapshot, signal?: AbortSignal): Promise<string>;\n reconcileReport?(snapshot: ExecutionSnapshot, signal?: AbortSignal): Promise<string | undefined>;\n}\nexport interface AggregationAdapterConfig {\n /** Construct this client with reportMode: \"manual\" to avoid duplicate reports. */\n client: Pick<SwapClient, \"quote\" | \"buildSwap\" | \"executeSwap\">;\n reporter: FundingReporter;\n /** Explicit mapping avoids conflating registry asset IDs and aggregation token addresses. */\n mapAsset: (token: Token) => AssetRef;\n reconcileFunding?: FundingSwapAdapter[\"reconcileFunding\"];\n}\nfunction serialize(request: SwapFundingRequest, map: AggregationAdapterConfig[\"mapAsset\"]): QuoteRequest {\n const tokenIn = map(clone(request.sourceToken));\n const tokenOut = map(clone(request.fundingToken));\n assert(tokenIn.chain === request.wallet.chain, \"Aggregation source chain differs from the frozen wallet chain.\");\n text(tokenIn.address, \"Mapped source token address\"); text(tokenOut.address, \"Mapped funding token address\");\n return { fromChain: tokenIn.chain, toChain: tokenOut.chain, tokenIn, tokenOut, amountIn: request.amount, slippageBps: request.slippageBps, sender: request.wallet.accountId, recipient: request.recipient };\n}\nfunction equalAddress(a: string | undefined, b: string, chain: string): boolean {\n return /^\\d+$/.test(chain) ? a?.toLowerCase() === b.toLowerCase() : a === b;\n}\nexport function createAggregationFundingAdapter(config: AggregationAdapterConfig): FundingSwapAdapter {\n return {\n quote: async (request, signal) => {\n const quote = await config.client.quote(serialize(request, config.mapAsset), { signal });\n return { amountOut: quote.estimatedOut, minAmountOut: quote.minAmountOut, expiresAt: quote.expiresAt, data: clone(quote) };\n },\n execute: async (input, request, _id, signal) => {\n throwIfAborted(signal);\n const quote = clone(input.data) as Quote;\n const expected = serialize(request, config.mapAsset);\n responseAssert(quote.amountIn === request.amount && quote.fromChain === expected.fromChain && quote.toChain === expected.toChain\n && equalAddress(quote.buildContext.request.sender, request.wallet.accountId, expected.fromChain)\n && quote.buildContext.request.recipient === request.recipient\n && equalAddress(quote.tokenIn.address, expected.tokenIn.address, expected.fromChain)\n && equalAddress(quote.tokenOut.address, expected.tokenOut.address, expected.toChain)\n && equalAddress(quote.buildContext.request.tokenIn, expected.tokenIn.address, expected.fromChain)\n && equalAddress(quote.buildContext.request.tokenOut, expected.tokenOut.address, expected.toChain), \"Funding quote does not match the frozen execution request.\");\n const build = await config.client.buildSwap({ quote, signal });\n responseAssert(raw(build.minAmountOut) >= raw(input.minAmountOut), \"Built swap minimum output fell below its funding quote.\");\n responseAssert(build.fromChain === expected.fromChain && build.toChain === expected.toChain && build.amountIn === request.amount\n && equalAddress(build.tokenIn.address, expected.tokenIn.address, expected.fromChain)\n && equalAddress(build.tokenOut.address, expected.tokenOut.address, expected.toChain), \"Built swap asset or chain differs from its funding request.\");\n const result = await config.client.executeSwap({ build, signal, waitFor: \"submitted\" });\n responseAssert(result.txHash, \"Funding swap did not return a source transaction hash.\");\n return { txHash: result.txHash, orderId: result.orderId, router: result.router };\n },\n report: config.reporter.report.bind(config.reporter),\n getStatus: config.reporter.getStatus.bind(config.reporter),\n reconcileReport: config.reporter.reconcileReport?.bind(config.reporter),\n reconcileFunding: config.reconcileFunding,\n };\n}\nexport interface HttpFundingReporterConfig {\n baseUrl: string;\n bearerToken?: string;\n fetch?: typeof fetch;\n requestTimeoutMs?: number;\n}\nexport class HttpFundingReporter implements FundingReporter {\n private readonly base: string;\n private readonly fetcher: typeof fetch;\n constructor(private readonly config: HttpFundingReporterConfig) {\n const url = new URL(config.baseUrl);\n assert([\"http:\", \"https:\"].includes(url.protocol) && !url.username && !url.password, \"Invalid report service URL.\");\n this.base = config.baseUrl.replace(/\\/+$/, \"\");\n this.fetcher = config.fetch ?? globalThis.fetch;\n assert(Number.isFinite(config.requestTimeoutMs ?? 30_000) && (config.requestTimeoutMs ?? 30_000) > 0, \"Invalid report timeout.\");\n }\n private async request(path: string, body?: unknown, signal?: AbortSignal): Promise<Record<string, unknown>> {\n throwIfAborted(signal);\n const timeout = AbortSignal.timeout(this.config.requestTimeoutMs ?? 30_000);\n try {\n const response = await this.fetcher(`${this.base}/api/swap/${path}`, {\n method: body ? \"POST\" : \"GET\", signal: signal ? AbortSignal.any([signal, timeout]) : timeout,\n headers: { Accept: \"application/json\", ...(body ? { \"Content-Type\": \"application/json\" } : {}), ...(this.config.bearerToken ? { Authorization: `Bearer ${this.config.bearerToken}` } : {}) },\n ...(body ? { body: JSON.stringify(body) } : {}),\n });\n if (!response.ok) throw new ConfidentialSwapError(\"HTTP_ERROR\", `Funding report request failed (${response.status}).`, response.status);\n let result: unknown;\n try { result = await response.json(); } catch { throw new ConfidentialSwapError(\"INVALID_RESPONSE\", \"Invalid funding report JSON.\"); }\n responseAssert(result && typeof result === \"object\", \"Invalid funding report response.\");\n const envelope = result as Record<string, unknown>;\n responseAssert(envelope.code === 0 && envelope.data && typeof envelope.data === \"object\", \"Invalid funding report envelope.\");\n return envelope.data as Record<string, unknown>;\n } catch (error) {\n throwIfAborted(signal);\n if (timeout.aborted) throw new ConfidentialSwapError(\"TIMEOUT\", \"Funding report request timed out; reconcile before resubmitting.\");\n throw error;\n }\n }\n async report(s: ExecutionSnapshot, signal?: AbortSignal): Promise<string> {\n const plan = s.plan;\n const quoted = plan.swapQuote?.data as Partial<Quote> | undefined;\n assert(s.receipt, \"Cannot report without a funding receipt.\");\n const data = await this.request(\"report\", {\n sender: plan.request.sourceWallet.accountId, recipient: plan.request.recipients[0], from_hash: s.receipt.txHash,\n deposit_address: plan.depositQuote!.quote.depositAddress, from_token: quoted?.tokenIn?.address ?? plan.request.sourceToken.swapAddress ?? plan.request.sourceToken.contractAddress ?? plan.request.sourceToken.assetId,\n to_token: quoted?.tokenOut?.address ?? plan.route.fundingAsset.swapAddress ?? plan.route.fundingAsset.contractAddress ?? plan.route.fundingAsset.assetId,\n from_chain: quoted?.fromChain ?? plan.request.sourceWallet.chain, to_chain: quoted?.toChain ?? plan.swapRequest?.fundingToken.swapChain ?? normalizeChain(plan.route.fundingAsset.blockchain),\n amount_in: plan.request.amount, estimated_out: plan.fundingAmount, router: s.receipt.router ?? \"rhea\", is_cross_chain: plan.route.kind === \"CROSS_CHAIN_SWAP\",\n tx_type: \"confidential-batch-withdraw\", confidentiality: \"advanced\", ...(s.receipt.orderId ? { swapId: s.receipt.orderId } : {}),\n extra: { recipient_count: plan.request.recipients.length, funding_route: plan.route.kind, execution_id: s.id },\n }, signal);\n responseAssert((typeof data.id === \"number\" && Number.isSafeInteger(data.id) && data.id > 0) || (typeof data.id === \"string\" && /^\\d+$/.test(data.id)), \"Report returned no record id.\");\n return String(data.id);\n }\n async getStatus(recordId: string, _s: ExecutionSnapshot, signal?: AbortSignal): Promise<string> {\n const query = new URLSearchParams({ recordId: text(recordId, \"Record id\") });\n const data = await this.request(`order-status?${query}`, undefined, signal);\n return text(data.status ?? data.state, \"Funding status\");\n }\n}\nexport type { AssetRef, ChainRef };\n","export type ErrorCode =\n | \"INVALID_INPUT\" | \"INVALID_RESPONSE\" | \"HTTP_ERROR\" | \"UNAUTHORIZED\"\n | \"WALLET_CHANGED\" | \"MISSING_WALLET\" | \"QUOTE_EXPIRED\" | \"BUSY\"\n | \"CONFIRMATION_REQUIRED\" | \"USER_REJECTED\" | \"TIMEOUT\" | \"ABORTED\" | \"TERMINAL_FAILURE\";\n\nexport class ConfidentialSwapError extends Error {\n readonly name = \"ConfidentialSwapError\";\n constructor(\n readonly code: ErrorCode,\n message: string,\n readonly status?: number,\n readonly executionId?: string,\n ) { super(message); }\n}\n\nexport function assert(condition: unknown, message: string): asserts condition {\n if (!condition) throw new ConfidentialSwapError(\"INVALID_INPUT\", message);\n}\n\nexport function responseAssert(condition: unknown, message: string): asserts condition {\n if (!condition) throw new ConfidentialSwapError(\"INVALID_RESPONSE\", message);\n}\n\nexport function isUserRejectedError(error: unknown): boolean {\n if (error instanceof ConfidentialSwapError) return error.code === \"USER_REJECTED\";\n if (!error || typeof error !== \"object\") return false;\n const value = error as { code?: unknown; message?: unknown; error?: unknown };\n const code = String(value.code ?? \"\").toUpperCase();\n const message = String(value.message ?? value.error ?? \"\");\n return (\n code === \"4001\" ||\n code === \"ACTION_REJECTED\" ||\n /user.*(reject|cancel|den(?:y|ied))|(?:reject|cancel|den(?:y|ied)).*user/i.test(message)\n );\n}\n\nexport function userRejectedError(executionId?: string): ConfidentialSwapError {\n return new ConfidentialSwapError(\n \"USER_REJECTED\",\n \"User rejected transaction.\",\n undefined,\n executionId,\n );\n}\n\nexport function throwIfAborted(signal?: AbortSignal): void {\n if (signal?.aborted)\n throw new ConfidentialSwapError(\"ABORTED\", \"Trade cancelled.\");\n}\n","import { assert } from \"./errors\";\nimport type { FundingRoute, Token } from \"./types\";\n\nconst aliases: Record<string, string> = { ethereum: \"eth\", arbitrum: \"arb\", avalanche: \"avax\", optimism: \"op\", polygon: \"pol\", bitcoin: \"btc\", solana: \"sol\", zcash: \"zec\", bnb: \"bsc\", \"bnb chain\": \"bsc\", hyperevm: \"hypercore\" };\nexport function normalizeChain(value: string): string { const chain = value.trim().toLowerCase(); return aliases[chain] ?? chain; }\nfunction contract(value?: string | null): string { const v = value?.trim() ?? \"\"; return /^0x/i.test(v) ? v.toLowerCase() : v; }\nfunction nativeNear(t: Token): boolean {\n return !t.assetId.startsWith(\"mca:\") && normalizeChain(t.blockchain) === \"near\" &&\n ([\"near\", \"nep141:near\"].includes(t.assetId) || t.contractAddress === \"near\" || Boolean(t.isNative && !t.contractAddress));\n}\n/** Intents omft natives often lack contractAddress; supported_to uses placeholders like `zec` / `btc:mainnet:native`. */\nfunction nativeOmftMatch(source: Token, registered: Token, chain: \"btc\" | \"zec\", intentsAssetId: string, placeholders: readonly string[]): boolean {\n if (normalizeChain(source.blockchain) !== chain || normalizeChain(registered.blockchain) !== chain) return false;\n if (registered.assetId !== intentsAssetId && !placeholders.includes(contract(registered.contractAddress).toLowerCase())) return false;\n const id = source.assetId.trim().toLowerCase();\n const src = contract(source.contractAddress).toLowerCase();\n return (\n id === intentsAssetId.toLowerCase() ||\n placeholders.includes(id) ||\n placeholders.includes(src) ||\n /:(mainnet:)?native$/i.test(source.assetId) ||\n Boolean(source.isNative && source.symbol.toUpperCase() === registered.symbol.toUpperCase())\n );\n}\nfunction matches(source: Token, registered: Token): boolean {\n if (normalizeChain(source.blockchain) !== normalizeChain(registered.blockchain)) return false;\n if (source.assetId && source.assetId === registered.assetId) return true;\n if (nativeNear(source)) return [\"nep141:wrap.near\", \"wrap.near\"].includes(registered.assetId) || registered.contractAddress === \"wrap.near\";\n if (nativeOmftMatch(source, registered, \"zec\", \"nep141:zec.omft.near\", [\"zec\", \"zcash\"])) return true;\n if (nativeOmftMatch(source, registered, \"btc\", \"nep141:btc.omft.near\", [\"btc\", \"bitcoin\"])) return true;\n const src = contract(source.contractAddress);\n const target = contract(registered.contractAddress);\n if (src && target) return src === target;\n return Boolean(source.isNative && !src && !target && source.symbol.toUpperCase() === registered.symbol.toUpperCase());\n}\nexport function resolveRegistryToken(token: Token, registry: readonly Token[]): Token | null {\n return registry.find((t) => matches(token, t)) ?? null;\n}\nfunction dedupeRegistry(registry: readonly Token[]): Token[] {\n return registry.filter((t, i) => t.blockchain && t.assetId && registry.findIndex((other) => normalizeChain(other.blockchain) === normalizeChain(t.blockchain) && (contract(other.contractAddress) || other.assetId) === (contract(t.contractAddress) || t.assetId)) === i);\n}\nexport function resolveFundingRoute(source: Token, registry: readonly Token[]): FundingRoute {\n const tokens = dedupeRegistry(registry);\n const direct = resolveRegistryToken(source, tokens);\n if (direct) return { kind: \"DIRECT\", fundingAsset: direct };\n const rank = (t: Token) => { const i = [\"USDC\", \"USDT\", \"USDT0\"].indexOf(t.symbol.toUpperCase()); return i < 0 ? 3 : i; };\n const prefer = (ts: Token[]) => [...ts].sort((a, b) => rank(a) - rank(b))[0];\n const same = prefer(tokens.filter((t) => normalizeChain(t.blockchain) === normalizeChain(source.blockchain)));\n if (same) return { kind: \"SAME_CHAIN_SWAP\", fundingAsset: same };\n const cross = prefer(tokens.filter((t) => normalizeChain(t.blockchain) === \"near\")) ?? prefer(tokens);\n assert(cross, \"No registry funding asset available.\");\n return { kind: \"CROSS_CHAIN_SWAP\", fundingAsset: cross };\n}\n/**\n * Build funding-route candidates for wallet-funded confidential quotes.\n *\n * DIRECT preferred (source is Intents-supported):\n * 1) DIRECT deposit crediting the destination asset\n * 2) SAME_CHAIN mid tokens (last successful mid first, then USDC/USDT)\n *\n * Non-DIRECT preferred (needs a pre-swap mid token):\n * try mid tokens with last successful mid first, then USDC/USDT / preferred.\n */\nexport function buildFundingRouteCandidates(\n source: Token,\n destination: Token,\n preferred: FundingRoute,\n registry: readonly Token[],\n options?: { preferredMidAssetId?: string },\n): FundingRoute[] {\n const tokens = dedupeRegistry(registry);\n const seen = new Set<string>();\n const routes: FundingRoute[] = [];\n const add = (kind: FundingRoute[\"kind\"], asset: Token | null | undefined) => {\n if (!asset?.assetId || seen.has(asset.assetId)) return;\n seen.add(asset.assetId);\n routes.push({ kind, fundingAsset: asset });\n };\n const sourceChain = normalizeChain(source.blockchain);\n const routeKindFor = (asset: Token): FundingRoute[\"kind\"] =>\n normalizeChain(asset.blockchain) === sourceChain ? \"SAME_CHAIN_SWAP\" : \"CROSS_CHAIN_SWAP\";\n const midCandidates = (): Token[] => {\n const ordered: Token[] = [];\n const push = (asset: Token | null | undefined) => {\n if (!asset?.assetId || ordered.some((t) => t.assetId === asset.assetId)) return;\n ordered.push(asset);\n };\n if (options?.preferredMidAssetId) {\n push(tokens.find((t) => t.assetId === options.preferredMidAssetId));\n }\n for (const symbol of [\"USDC\", \"USDT\"] as const) {\n push(tokens.find((t) => normalizeChain(t.blockchain) === sourceChain && t.symbol.toUpperCase() === symbol));\n }\n if (preferred.kind !== \"DIRECT\") push(preferred.fundingAsset);\n return ordered;\n };\n\n if (preferred.kind === \"DIRECT\") {\n add(\"DIRECT\", destination);\n for (const mid of midCandidates()) add(routeKindFor(mid), mid);\n return routes;\n }\n\n for (const mid of midCandidates()) add(routeKindFor(mid), mid);\n if (routes.length === 0) return [preferred];\n return routes;\n}\n","import { sha256 } from \"@noble/hashes/sha256\";\nimport { bytesToHex } from \"@noble/hashes/utils\";\nimport { assert, throwIfAborted } from \"./errors\";\nimport type { Balance, QuotePlan } from \"./types\";\n\nexport function raw(value: unknown, field = \"Amount\", allowZero = false): bigint {\n assert(typeof value === \"string\" && /^(0|[1-9]\\d*)$/.test(value), `${field} must be a canonical integer string.`);\n const result = BigInt(value);\n assert(allowZero || result > 0n, `${field} must be positive.`);\n return result;\n}\n/** Rescale a raw integer between token decimals (floor when shrinking). */\nexport function rescaleRawAmount(amount: string, fromDecimals: number, toDecimals: number): string {\n assert(Number.isInteger(fromDecimals) && fromDecimals >= 0 && fromDecimals <= 255, \"Invalid source decimals.\");\n assert(Number.isInteger(toDecimals) && toDecimals >= 0 && toDecimals <= 255, \"Invalid target decimals.\");\n const value = raw(amount);\n if (fromDecimals === toDecimals) return value.toString();\n if (toDecimals > fromDecimals) {\n return (value * 10n ** BigInt(toDecimals - fromDecimals)).toString();\n }\n const scaled = value / 10n ** BigInt(fromDecimals - toDecimals);\n assert(scaled > 0n, \"Amount too small after decimal conversion.\");\n return scaled.toString();\n}\nexport function text(value: unknown, field: string): string {\n assert(typeof value === \"string\" && value.trim().length > 0, `${field} is required.`);\n return value.trim();\n}\nexport function clone<T>(value: T): T { return structuredClone(value); }\nfunction canonical(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(canonical).join(\",\")}]`;\n if (value && typeof value === \"object\") {\n return `{${Object.entries(value).filter(([, v]) => v !== undefined).sort(([a], [b]) => a.localeCompare(b))\n .map(([k, v]) => `${JSON.stringify(k)}:${canonical(v)}`).join(\",\")}}`;\n }\n assert(value === null || [\"string\", \"boolean\", \"number\"].includes(typeof value), \"Plan data must be JSON-serializable.\");\n if (typeof value === \"number\") assert(Number.isFinite(value), \"Plan numbers must be finite.\");\n return JSON.stringify(value);\n}\nexport function planDigest(plan: Omit<QuotePlan, \"digest\"> | QuotePlan): string {\n const { digest: _digest, ...body } = plan as QuotePlan;\n return bytesToHex(sha256(new TextEncoder().encode(canonical(body))));\n}\nexport function randomFraction(): number {\n return crypto.getRandomValues(new Uint32Array(1))[0]! / 0xffffffff;\n}\nexport function splitConfidentialRawAmount(totalRaw: string, count: number, values?: readonly number[]): string[] {\n assert(Number.isInteger(count) && count >= 1 && count <= 10, \"A batch requires 1 to 10 recipients.\");\n const total = raw(totalRaw);\n assert(total >= BigInt(count), \"Amount too small for every recipient.\");\n const minimum = total * 80n / (100n * BigInt(count));\n const lowBound = minimum > 0n ? minimum : 1n;\n const denominator = 100n * BigInt(count);\n const maximum = (total * 120n + denominator - 1n) / denominator;\n const highBound = maximum > lowBound ? maximum : lowBound;\n const shares: bigint[] = [];\n let remaining = total;\n for (let i = 0; i < count - 1; i++) {\n const left = BigInt(count - i - 1);\n const low = remaining - highBound * left > lowBound ? remaining - highBound * left : lowBound;\n const high = remaining - lowBound * left < highBound ? remaining - lowBound * left : highBound;\n assert(low <= high, \"Amount cannot be split safely.\");\n const fraction = values?.[i] ?? randomFraction();\n assert(Number.isFinite(fraction) && fraction >= 0 && fraction <= 1, \"Random fractions must be within [0, 1].\");\n const share = low + (high - low) * BigInt(Math.floor(fraction * 1_000_000)) / 1_000_000n;\n shares.push(share);\n remaining -= share;\n }\n assert(remaining >= lowBound && remaining <= highBound, \"Final share outside safe bounds.\");\n return [...shares, remaining].map(String);\n}\nexport function availableForAsset(balances: readonly Balance[], assetId: string): string {\n const exact = balances.find((b) => b.tokenId === assetId);\n const entry = exact ?? balances.find((b) => b.tokenId.startsWith(\"imt:\") && b.tokenId.endsWith(`:${assetId}`));\n return raw(entry?.available ?? \"0\", \"Available balance\", true).toString();\n}\nexport async function delay(ms: number, signal?: AbortSignal): Promise<void> {\n throwIfAborted(signal);\n await new Promise<void>((resolve, reject) => {\n const finish = () => { signal?.removeEventListener(\"abort\", abort); resolve(); };\n const timer = setTimeout(finish, ms);\n const abort = () => { clearTimeout(timer); signal?.removeEventListener(\"abort\", abort); try { throwIfAborted(signal); } catch (e) { reject(e); } };\n signal?.addEventListener(\"abort\", abort, { once: true });\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACKO,IAAM,wBAAN,cAAoC,MAAM;AAAA,EAE/C,YACW,MACT,SACS,QACA,aACT;AAAE,UAAM,OAAO;AAJN;AAEA;AACA;AAAA,EACS;AAAA,EAJT;AAAA,EAEA;AAAA,EACA;AAAA,EALF,OAAO;AAOlB;AAEO,SAAS,OAAO,WAAoB,SAAoC;AAC7E,MAAI,CAAC,UAAW,OAAM,IAAI,sBAAsB,iBAAiB,OAAO;AAC1E;AAEO,SAAS,eAAe,WAAoB,SAAoC;AACrF,MAAI,CAAC,UAAW,OAAM,IAAI,sBAAsB,oBAAoB,OAAO;AAC7E;AAwBO,SAAS,eAAe,QAA4B;AACzD,MAAI,QAAQ;AACV,UAAM,IAAI,sBAAsB,WAAW,kBAAkB;AACjE;;;AC7CA,IAAM,UAAkC,EAAE,UAAU,OAAO,UAAU,OAAO,WAAW,QAAQ,UAAU,MAAM,SAAS,OAAO,SAAS,OAAO,QAAQ,OAAO,OAAO,OAAO,KAAK,OAAO,aAAa,OAAO,UAAU,YAAY;AAC3N,SAAS,eAAe,OAAuB;AAAE,QAAM,QAAQ,MAAM,KAAK,EAAE,YAAY;AAAG,SAAO,QAAQ,KAAK,KAAK;AAAO;;;ACJlI,oBAAuB;AACvB,mBAA2B;AAIpB,SAAS,IAAI,OAAgB,QAAQ,UAAU,YAAY,OAAe;AAC/E,SAAO,OAAO,UAAU,YAAY,iBAAiB,KAAK,KAAK,GAAG,GAAG,KAAK,sCAAsC;AAChH,QAAM,SAAS,OAAO,KAAK;AAC3B,SAAO,aAAa,SAAS,IAAI,GAAG,KAAK,oBAAoB;AAC7D,SAAO;AACT;AAcO,SAAS,KAAK,OAAgB,OAAuB;AAC1D,SAAO,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,SAAS,GAAG,GAAG,KAAK,eAAe;AACpF,SAAO,MAAM,KAAK;AACpB;AACO,SAAS,MAAS,OAAa;AAAE,SAAO,gBAAgB,KAAK;AAAG;;;AHTvE,SAAS,UAAU,SAA6B,KAAyD;AACvG,QAAM,UAAU,IAAI,MAAM,QAAQ,WAAW,CAAC;AAC9C,QAAM,WAAW,IAAI,MAAM,QAAQ,YAAY,CAAC;AAChD,SAAO,QAAQ,UAAU,QAAQ,OAAO,OAAO,gEAAgE;AAC/G,OAAK,QAAQ,SAAS,6BAA6B;AAAG,OAAK,SAAS,SAAS,8BAA8B;AAC3G,SAAO,EAAE,WAAW,QAAQ,OAAO,SAAS,SAAS,OAAO,SAAS,UAAU,UAAU,QAAQ,QAAQ,aAAa,QAAQ,aAAa,QAAQ,QAAQ,OAAO,WAAW,WAAW,QAAQ,UAAU;AAC5M;AACA,SAAS,aAAa,GAAuB,GAAW,OAAwB;AAC9E,SAAO,QAAQ,KAAK,KAAK,IAAI,GAAG,YAAY,MAAM,EAAE,YAAY,IAAI,MAAM;AAC5E;AACO,SAAS,gCAAgC,QAAsD;AACpG,SAAO;AAAA,IACL,OAAO,OAAO,SAAS,WAAW;AAChC,YAAM,QAAQ,MAAM,OAAO,OAAO,MAAM,UAAU,SAAS,OAAO,QAAQ,GAAG,EAAE,OAAO,CAAC;AACvF,aAAO,EAAE,WAAW,MAAM,cAAc,cAAc,MAAM,cAAc,WAAW,MAAM,WAAW,MAAM,MAAM,KAAK,EAAE;AAAA,IAC3H;AAAA,IACA,SAAS,OAAO,OAAO,SAAS,KAAK,WAAW;AAC9C,qBAAe,MAAM;AACrB,YAAM,QAAQ,MAAM,MAAM,IAAI;AAC9B,YAAM,WAAW,UAAU,SAAS,OAAO,QAAQ;AACnD,qBAAe,MAAM,aAAa,QAAQ,UAAU,MAAM,cAAc,SAAS,aAAa,MAAM,YAAY,SAAS,WACpH,aAAa,MAAM,aAAa,QAAQ,QAAQ,QAAQ,OAAO,WAAW,SAAS,SAAS,KAC5F,MAAM,aAAa,QAAQ,cAAc,QAAQ,aACjD,aAAa,MAAM,QAAQ,SAAS,SAAS,QAAQ,SAAS,SAAS,SAAS,KAChF,aAAa,MAAM,SAAS,SAAS,SAAS,SAAS,SAAS,SAAS,OAAO,KAChF,aAAa,MAAM,aAAa,QAAQ,SAAS,SAAS,QAAQ,SAAS,SAAS,SAAS,KAC7F,aAAa,MAAM,aAAa,QAAQ,UAAU,SAAS,SAAS,SAAS,SAAS,OAAO,GAAG,4DAA4D;AACjK,YAAM,QAAQ,MAAM,OAAO,OAAO,UAAU,EAAE,OAAO,OAAO,CAAC;AAC7D,qBAAe,IAAI,MAAM,YAAY,KAAK,IAAI,MAAM,YAAY,GAAG,yDAAyD;AAC5H,qBAAe,MAAM,cAAc,SAAS,aAAa,MAAM,YAAY,SAAS,WAAW,MAAM,aAAa,QAAQ,UACrH,aAAa,MAAM,QAAQ,SAAS,SAAS,QAAQ,SAAS,SAAS,SAAS,KAChF,aAAa,MAAM,SAAS,SAAS,SAAS,SAAS,SAAS,SAAS,OAAO,GAAG,6DAA6D;AACrJ,YAAM,SAAS,MAAM,OAAO,OAAO,YAAY,EAAE,OAAO,QAAQ,SAAS,YAAY,CAAC;AACtF,qBAAe,OAAO,QAAQ,wDAAwD;AACtF,aAAO,EAAE,QAAQ,OAAO,QAAQ,SAAS,OAAO,SAAS,QAAQ,OAAO,OAAO;AAAA,IACjF;AAAA,IACA,QAAQ,OAAO,SAAS,OAAO,KAAK,OAAO,QAAQ;AAAA,IACnD,WAAW,OAAO,SAAS,UAAU,KAAK,OAAO,QAAQ;AAAA,IACzD,iBAAiB,OAAO,SAAS,iBAAiB,KAAK,OAAO,QAAQ;AAAA,IACtE,kBAAkB,OAAO;AAAA,EAC3B;AACF;AAOO,IAAM,sBAAN,MAAqD;AAAA,EAG1D,YAA6B,QAAmC;AAAnC;AAC3B,UAAM,MAAM,IAAI,IAAI,OAAO,OAAO;AAClC,WAAO,CAAC,SAAS,QAAQ,EAAE,SAAS,IAAI,QAAQ,KAAK,CAAC,IAAI,YAAY,CAAC,IAAI,UAAU,6BAA6B;AAClH,SAAK,OAAO,OAAO,QAAQ,QAAQ,QAAQ,EAAE;AAC7C,SAAK,UAAU,OAAO,SAAS,WAAW;AAC1C,WAAO,OAAO,SAAS,OAAO,oBAAoB,GAAM,MAAM,OAAO,oBAAoB,OAAU,GAAG,yBAAyB;AAAA,EACjI;AAAA,EAN6B;AAAA,EAFZ;AAAA,EACA;AAAA,EAQjB,MAAc,QAAQ,MAAc,MAAgB,QAAwD;AAC1G,mBAAe,MAAM;AACrB,UAAM,UAAU,YAAY,QAAQ,KAAK,OAAO,oBAAoB,GAAM;AAC1E,QAAI;AACF,YAAM,WAAW,MAAM,KAAK,QAAQ,GAAG,KAAK,IAAI,aAAa,IAAI,IAAI;AAAA,QACnE,QAAQ,OAAO,SAAS;AAAA,QAAO,QAAQ,SAAS,YAAY,IAAI,CAAC,QAAQ,OAAO,CAAC,IAAI;AAAA,QACrF,SAAS,EAAE,QAAQ,oBAAoB,GAAI,OAAO,EAAE,gBAAgB,mBAAmB,IAAI,CAAC,GAAI,GAAI,KAAK,OAAO,cAAc,EAAE,eAAe,UAAU,KAAK,OAAO,WAAW,GAAG,IAAI,CAAC,EAAG;AAAA,QAC3L,GAAI,OAAO,EAAE,MAAM,KAAK,UAAU,IAAI,EAAE,IAAI,CAAC;AAAA,MAC/C,CAAC;AACD,UAAI,CAAC,SAAS,GAAI,OAAM,IAAI,sBAAsB,cAAc,kCAAkC,SAAS,MAAM,MAAM,SAAS,MAAM;AACtI,UAAI;AACJ,UAAI;AAAE,iBAAS,MAAM,SAAS,KAAK;AAAA,MAAG,QAAQ;AAAE,cAAM,IAAI,sBAAsB,oBAAoB,8BAA8B;AAAA,MAAG;AACrI,qBAAe,UAAU,OAAO,WAAW,UAAU,kCAAkC;AACvF,YAAM,WAAW;AACjB,qBAAe,SAAS,SAAS,KAAK,SAAS,QAAQ,OAAO,SAAS,SAAS,UAAU,kCAAkC;AAC5H,aAAO,SAAS;AAAA,IAClB,SAAS,OAAO;AACd,qBAAe,MAAM;AACrB,UAAI,QAAQ,QAAS,OAAM,IAAI,sBAAsB,WAAW,kEAAkE;AAClI,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EACA,MAAM,OAAO,GAAsB,QAAuC;AACxE,UAAM,OAAO,EAAE;AACf,UAAM,SAAS,KAAK,WAAW;AAC/B,WAAO,EAAE,SAAS,0CAA0C;AAC5D,UAAM,OAAO,MAAM,KAAK,QAAQ,UAAU;AAAA,MACxC,QAAQ,KAAK,QAAQ,aAAa;AAAA,MAAW,WAAW,KAAK,QAAQ,WAAW,CAAC;AAAA,MAAG,WAAW,EAAE,QAAQ;AAAA,MACzG,iBAAiB,KAAK,aAAc,MAAM;AAAA,MAAgB,YAAY,QAAQ,SAAS,WAAW,KAAK,QAAQ,YAAY,eAAe,KAAK,QAAQ,YAAY,mBAAmB,KAAK,QAAQ,YAAY;AAAA,MAC/M,UAAU,QAAQ,UAAU,WAAW,KAAK,MAAM,aAAa,eAAe,KAAK,MAAM,aAAa,mBAAmB,KAAK,MAAM,aAAa;AAAA,MACjJ,YAAY,QAAQ,aAAa,KAAK,QAAQ,aAAa;AAAA,MAAO,UAAU,QAAQ,WAAW,KAAK,aAAa,aAAa,aAAa,eAAe,KAAK,MAAM,aAAa,UAAU;AAAA,MAC5L,WAAW,KAAK,QAAQ;AAAA,MAAQ,eAAe,KAAK;AAAA,MAAe,QAAQ,EAAE,QAAQ,UAAU;AAAA,MAAQ,gBAAgB,KAAK,MAAM,SAAS;AAAA,MAC3I,SAAS;AAAA,MAA+B,iBAAiB;AAAA,MAAY,GAAI,EAAE,QAAQ,UAAU,EAAE,QAAQ,EAAE,QAAQ,QAAQ,IAAI,CAAC;AAAA,MAC9H,OAAO,EAAE,iBAAiB,KAAK,QAAQ,WAAW,QAAQ,eAAe,KAAK,MAAM,MAAM,cAAc,EAAE,GAAG;AAAA,IAC/G,GAAG,MAAM;AACT,mBAAgB,OAAO,KAAK,OAAO,YAAY,OAAO,cAAc,KAAK,EAAE,KAAK,KAAK,KAAK,KAAO,OAAO,KAAK,OAAO,YAAY,QAAQ,KAAK,KAAK,EAAE,GAAI,+BAA+B;AACvL,WAAO,OAAO,KAAK,EAAE;AAAA,EACvB;AAAA,EACA,MAAM,UAAU,UAAkB,IAAuB,QAAuC;AAC9F,UAAM,QAAQ,IAAI,gBAAgB,EAAE,UAAU,KAAK,UAAU,WAAW,EAAE,CAAC;AAC3E,UAAM,OAAO,MAAM,KAAK,QAAQ,gBAAgB,KAAK,IAAI,QAAW,MAAM;AAC1E,WAAO,KAAK,KAAK,UAAU,KAAK,OAAO,gBAAgB;AAAA,EACzD;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/aggregation.ts","../src/errors.ts","../src/routing.ts","../src/utils.ts"],"sourcesContent":["import type { AssetRef, ChainRef, Quote, QuoteRequest, SwapClient } from \"@rhea-finance/cross-chain-aggregation-dex\";\nimport { assert, ConfidentialSwapError, responseAssert, throwIfAborted } from \"./errors\";\nimport { normalizeChain } from \"./routing\";\nimport { clone, raw, text } from \"./utils\";\nimport type { ExecutionSnapshot, FundingSwapAdapter, SwapFundingRequest, Token } from \"./types\";\n\nexport interface FundingReporter {\n report(snapshot: ExecutionSnapshot, signal?: AbortSignal): Promise<string>;\n getStatus(recordId: string, snapshot: ExecutionSnapshot, signal?: AbortSignal): Promise<string>;\n reconcileReport?(snapshot: ExecutionSnapshot, signal?: AbortSignal): Promise<string | undefined>;\n}\nexport interface AggregationAdapterConfig {\n /** Construct this client with reportMode: \"manual\" to avoid duplicate reports. */\n client: Pick<SwapClient, \"quote\" | \"buildSwap\" | \"executeSwap\">;\n reporter: FundingReporter;\n /** Explicit mapping avoids conflating registry asset IDs and aggregation token addresses. */\n mapAsset: (token: Token) => AssetRef;\n reconcileFunding?: FundingSwapAdapter[\"reconcileFunding\"];\n}\nfunction serialize(request: SwapFundingRequest, map: AggregationAdapterConfig[\"mapAsset\"]): QuoteRequest {\n const tokenIn = map(clone(request.sourceToken));\n const tokenOut = map(clone(request.fundingToken));\n assert(tokenIn.chain === request.wallet.chain, \"Aggregation source chain differs from the frozen wallet chain.\");\n text(tokenIn.address, \"Mapped source token address\"); text(tokenOut.address, \"Mapped funding token address\");\n return { fromChain: tokenIn.chain, toChain: tokenOut.chain, tokenIn, tokenOut, amountIn: request.amount, slippageBps: request.slippageBps, sender: request.wallet.accountId, recipient: request.recipient };\n}\nfunction equalAddress(a: string | undefined, b: string, chain: string): boolean {\n return /^\\d+$/.test(chain) ? a?.toLowerCase() === b.toLowerCase() : a === b;\n}\nexport function createAggregationFundingAdapter(config: AggregationAdapterConfig): FundingSwapAdapter {\n return {\n quote: async (request, signal) => {\n const quote = await config.client.quote(serialize(request, config.mapAsset), { signal });\n return { amountOut: quote.estimatedOut, minAmountOut: quote.minAmountOut, expiresAt: quote.expiresAt, data: clone(quote) };\n },\n execute: async (input, request, _id, signal) => {\n throwIfAborted(signal);\n const quote = clone(input.data) as Quote;\n const expected = serialize(request, config.mapAsset);\n responseAssert(quote.amountIn === request.amount && quote.fromChain === expected.fromChain && quote.toChain === expected.toChain\n && equalAddress(quote.buildContext.request.sender, request.wallet.accountId, expected.fromChain)\n && quote.buildContext.request.recipient === request.recipient\n && equalAddress(quote.tokenIn.address, expected.tokenIn.address, expected.fromChain)\n && equalAddress(quote.tokenOut.address, expected.tokenOut.address, expected.toChain)\n && equalAddress(quote.buildContext.request.tokenIn, expected.tokenIn.address, expected.fromChain)\n && equalAddress(quote.buildContext.request.tokenOut, expected.tokenOut.address, expected.toChain), \"Funding quote does not match the frozen execution request.\");\n const build = await config.client.buildSwap({ quote, signal });\n responseAssert(raw(build.minAmountOut) >= raw(input.minAmountOut), \"Built swap minimum output fell below its funding quote.\");\n responseAssert(build.fromChain === expected.fromChain && build.toChain === expected.toChain && build.amountIn === request.amount\n && equalAddress(build.tokenIn.address, expected.tokenIn.address, expected.fromChain)\n && equalAddress(build.tokenOut.address, expected.tokenOut.address, expected.toChain), \"Built swap asset or chain differs from its funding request.\");\n const result = await config.client.executeSwap({ build, signal, waitFor: \"submitted\" });\n responseAssert(result.txHash, \"Funding swap did not return a source transaction hash.\");\n return { txHash: result.txHash, orderId: result.orderId, router: result.router };\n },\n report: config.reporter.report.bind(config.reporter),\n getStatus: config.reporter.getStatus.bind(config.reporter),\n reconcileReport: config.reporter.reconcileReport?.bind(config.reporter),\n reconcileFunding: config.reconcileFunding,\n };\n}\nexport interface HttpFundingReporterConfig {\n baseUrl: string;\n bearerToken?: string;\n fetch?: typeof fetch;\n requestTimeoutMs?: number;\n}\nexport class HttpFundingReporter implements FundingReporter {\n private readonly base: string;\n private readonly fetcher: typeof fetch;\n constructor(private readonly config: HttpFundingReporterConfig) {\n const url = new URL(config.baseUrl);\n assert([\"http:\", \"https:\"].includes(url.protocol) && !url.username && !url.password, \"Invalid report service URL.\");\n this.base = config.baseUrl.replace(/\\/+$/, \"\");\n this.fetcher = config.fetch ?? globalThis.fetch;\n assert(Number.isFinite(config.requestTimeoutMs ?? 30_000) && (config.requestTimeoutMs ?? 30_000) > 0, \"Invalid report timeout.\");\n }\n private async request(path: string, body?: unknown, signal?: AbortSignal): Promise<Record<string, unknown>> {\n throwIfAborted(signal);\n const timeout = AbortSignal.timeout(this.config.requestTimeoutMs ?? 30_000);\n try {\n const response = await this.fetcher(`${this.base}/api/swap/${path}`, {\n method: body ? \"POST\" : \"GET\", signal: signal ? AbortSignal.any([signal, timeout]) : timeout,\n headers: { Accept: \"application/json\", ...(body ? { \"Content-Type\": \"application/json\" } : {}), ...(this.config.bearerToken ? { Authorization: `Bearer ${this.config.bearerToken}` } : {}) },\n ...(body ? { body: JSON.stringify(body) } : {}),\n });\n if (!response.ok) throw new ConfidentialSwapError(\"HTTP_ERROR\", `Funding report request failed (${response.status}).`, response.status);\n let result: unknown;\n try { result = await response.json(); } catch { throw new ConfidentialSwapError(\"INVALID_RESPONSE\", \"Invalid funding report JSON.\"); }\n responseAssert(result && typeof result === \"object\", \"Invalid funding report response.\");\n const envelope = result as Record<string, unknown>;\n responseAssert(envelope.code === 0 && envelope.data && typeof envelope.data === \"object\", \"Invalid funding report envelope.\");\n return envelope.data as Record<string, unknown>;\n } catch (error) {\n throwIfAborted(signal);\n if (timeout.aborted) throw new ConfidentialSwapError(\"TIMEOUT\", \"Funding report request timed out; reconcile before resubmitting.\");\n throw error;\n }\n }\n async report(s: ExecutionSnapshot, signal?: AbortSignal): Promise<string> {\n const plan = s.plan;\n const quoted = plan.swapQuote?.data as Partial<Quote> | undefined;\n assert(s.receipt, \"Cannot report without a funding receipt.\");\n const data = await this.request(\"report\", {\n sender: plan.request.sourceWallet.accountId, recipient: plan.request.recipients[0], from_hash: s.receipt.txHash,\n deposit_address: plan.depositQuote!.quote.depositAddress, from_token: quoted?.tokenIn?.address ?? plan.request.sourceToken.swapAddress ?? plan.request.sourceToken.contractAddress ?? plan.request.sourceToken.assetId,\n to_token: quoted?.tokenOut?.address ?? plan.route.fundingAsset.swapAddress ?? plan.route.fundingAsset.contractAddress ?? plan.route.fundingAsset.assetId,\n from_chain: quoted?.fromChain ?? plan.request.sourceWallet.chain, to_chain: quoted?.toChain ?? plan.swapRequest?.fundingToken.swapChain ?? normalizeChain(plan.route.fundingAsset.blockchain),\n amount_in: plan.request.amount, estimated_out: plan.fundingAmount, router: s.receipt.router ?? \"rhea\", is_cross_chain: plan.route.kind === \"CROSS_CHAIN_SWAP\",\n tx_type: \"confidential-batch-withdraw\", confidentiality: \"advanced\", ...(s.receipt.orderId ? { swapId: s.receipt.orderId } : {}),\n extra: { recipient_count: plan.request.recipients.length, funding_route: plan.route.kind, execution_id: s.id },\n }, signal);\n responseAssert((typeof data.id === \"number\" && Number.isSafeInteger(data.id) && data.id > 0) || (typeof data.id === \"string\" && /^\\d+$/.test(data.id)), \"Report returned no record id.\");\n return String(data.id);\n }\n async getStatus(recordId: string, _s: ExecutionSnapshot, signal?: AbortSignal): Promise<string> {\n const query = new URLSearchParams({ recordId: text(recordId, \"Record id\") });\n const data = await this.request(`order-status?${query}`, undefined, signal);\n return text(data.status ?? data.state, \"Funding status\");\n }\n}\nexport type { AssetRef, ChainRef };\n","export type ErrorCode =\n | \"INVALID_INPUT\" | \"INVALID_RESPONSE\" | \"HTTP_ERROR\" | \"UNAUTHORIZED\"\n | \"WALLET_CHANGED\" | \"MISSING_WALLET\" | \"QUOTE_EXPIRED\" | \"BUSY\"\n | \"CONFIRMATION_REQUIRED\" | \"USER_REJECTED\" | \"TIMEOUT\" | \"ABORTED\" | \"TERMINAL_FAILURE\";\n\nexport class ConfidentialSwapError extends Error {\n readonly name = \"ConfidentialSwapError\";\n constructor(\n readonly code: ErrorCode,\n message: string,\n readonly status?: number,\n readonly executionId?: string,\n ) { super(message); }\n}\n\nexport function assert(condition: unknown, message: string): asserts condition {\n if (!condition) throw new ConfidentialSwapError(\"INVALID_INPUT\", message);\n}\n\nexport function responseAssert(condition: unknown, message: string): asserts condition {\n if (!condition) throw new ConfidentialSwapError(\"INVALID_RESPONSE\", message);\n}\n\nexport function isUserRejectedError(error: unknown): boolean {\n if (error instanceof ConfidentialSwapError) return error.code === \"USER_REJECTED\";\n if (!error || typeof error !== \"object\") return false;\n const value = error as { code?: unknown; message?: unknown; error?: unknown };\n const code = String(value.code ?? \"\").toUpperCase();\n const message = String(value.message ?? value.error ?? \"\");\n return (\n code === \"4001\" ||\n code === \"ACTION_REJECTED\" ||\n /user.*(reject|cancel|den(?:y|ied))|(?:reject|cancel|den(?:y|ied)).*user/i.test(message)\n );\n}\n\nexport function userRejectedError(executionId?: string): ConfidentialSwapError {\n return new ConfidentialSwapError(\n \"USER_REJECTED\",\n \"User rejected transaction.\",\n undefined,\n executionId,\n );\n}\n\nexport function throwIfAborted(signal?: AbortSignal): void {\n if (signal?.aborted)\n throw new ConfidentialSwapError(\"ABORTED\", \"Trade cancelled.\");\n}\n","import { assert } from \"./errors\";\nimport type { FundingRoute, Token } from \"./types\";\n\nconst aliases: Record<string, string> = { ethereum: \"eth\", arbitrum: \"arb\", avalanche: \"avax\", optimism: \"op\", polygon: \"pol\", bitcoin: \"btc\", solana: \"sol\", zcash: \"zec\", bnb: \"bsc\", \"bnb chain\": \"bsc\", hyperevm: \"hypercore\" };\nexport function normalizeChain(value: string): string { const chain = value.trim().toLowerCase(); return aliases[chain] ?? chain; }\nfunction contract(value?: string | null): string { const v = value?.trim() ?? \"\"; return /^0x/i.test(v) ? v.toLowerCase() : v; }\nfunction nativeNear(t: Token): boolean {\n return !t.assetId.startsWith(\"mca:\") && normalizeChain(t.blockchain) === \"near\" &&\n ([\"near\", \"nep141:near\"].includes(t.assetId) || t.contractAddress === \"near\" || Boolean(t.isNative && !t.contractAddress));\n}\n/** Intents omft natives often lack contractAddress; supported_to uses placeholders like `zec` / `btc:mainnet:native`. */\nfunction nativeOmftMatch(source: Token, registered: Token, chain: \"btc\" | \"zec\", intentsAssetId: string, placeholders: readonly string[]): boolean {\n if (normalizeChain(source.blockchain) !== chain || normalizeChain(registered.blockchain) !== chain) return false;\n if (registered.assetId !== intentsAssetId && !placeholders.includes(contract(registered.contractAddress).toLowerCase())) return false;\n const id = source.assetId.trim().toLowerCase();\n const src = contract(source.contractAddress).toLowerCase();\n return (\n id === intentsAssetId.toLowerCase() ||\n placeholders.includes(id) ||\n placeholders.includes(src) ||\n /:(mainnet:)?native$/i.test(source.assetId) ||\n Boolean(source.isNative && source.symbol.toUpperCase() === registered.symbol.toUpperCase())\n );\n}\nfunction matches(source: Token, registered: Token): boolean {\n if (normalizeChain(source.blockchain) !== normalizeChain(registered.blockchain)) return false;\n if (source.assetId && source.assetId === registered.assetId) return true;\n if (nativeNear(source)) return [\"nep141:wrap.near\", \"wrap.near\"].includes(registered.assetId) || registered.contractAddress === \"wrap.near\";\n if (nativeOmftMatch(source, registered, \"zec\", \"nep141:zec.omft.near\", [\"zec\", \"zcash\"])) return true;\n if (nativeOmftMatch(source, registered, \"btc\", \"nep141:btc.omft.near\", [\"btc\", \"bitcoin\"])) return true;\n const src = contract(source.contractAddress);\n const target = contract(registered.contractAddress);\n if (src && target) return src === target;\n return Boolean(source.isNative && !src && !target && source.symbol.toUpperCase() === registered.symbol.toUpperCase());\n}\nexport function resolveRegistryToken(token: Token, registry: readonly Token[]): Token | null {\n return registry.find((t) => matches(token, t)) ?? null;\n}\nfunction dedupeRegistry(registry: readonly Token[]): Token[] {\n return registry.filter((t, i) => t.blockchain && t.assetId && registry.findIndex((other) => normalizeChain(other.blockchain) === normalizeChain(t.blockchain) && (contract(other.contractAddress) || other.assetId) === (contract(t.contractAddress) || t.assetId)) === i);\n}\nexport function resolveFundingRoute(source: Token, registry: readonly Token[]): FundingRoute {\n const tokens = dedupeRegistry(registry);\n const direct = resolveRegistryToken(source, tokens);\n if (direct) return { kind: \"DIRECT\", fundingAsset: direct };\n const rank = (t: Token) => { const i = [\"USDC\", \"USDT\", \"USDT0\"].indexOf(t.symbol.toUpperCase()); return i < 0 ? 3 : i; };\n const prefer = (ts: Token[]) => [...ts].sort((a, b) => rank(a) - rank(b))[0];\n const same = prefer(tokens.filter((t) => normalizeChain(t.blockchain) === normalizeChain(source.blockchain)));\n if (same) return { kind: \"SAME_CHAIN_SWAP\", fundingAsset: same };\n const cross = prefer(tokens.filter((t) => normalizeChain(t.blockchain) === \"near\")) ?? prefer(tokens);\n assert(cross, \"No registry funding asset available.\");\n return { kind: \"CROSS_CHAIN_SWAP\", fundingAsset: cross };\n}\n/**\n * Build funding-route candidates for wallet-funded confidential quotes.\n *\n * DIRECT preferred (source is Intents-supported):\n * 1) DIRECT deposit crediting the destination asset\n * 2) SAME_CHAIN mid tokens (last successful mid first, then USDC/USDT)\n *\n * Non-DIRECT preferred (needs a pre-swap mid token):\n * try mid tokens with last successful mid first, then USDC/USDT / preferred.\n */\nexport function buildFundingRouteCandidates(\n source: Token,\n destination: Token,\n preferred: FundingRoute,\n registry: readonly Token[],\n options?: { preferredMidAssetId?: string },\n): FundingRoute[] {\n const tokens = dedupeRegistry(registry);\n const seen = new Set<string>();\n const routes: FundingRoute[] = [];\n const add = (kind: FundingRoute[\"kind\"], asset: Token | null | undefined) => {\n if (!asset?.assetId || seen.has(asset.assetId)) return;\n seen.add(asset.assetId);\n routes.push({ kind, fundingAsset: asset });\n };\n const sourceChain = normalizeChain(source.blockchain);\n const routeKindFor = (asset: Token): FundingRoute[\"kind\"] =>\n normalizeChain(asset.blockchain) === sourceChain ? \"SAME_CHAIN_SWAP\" : \"CROSS_CHAIN_SWAP\";\n const midCandidates = (): Token[] => {\n const ordered: Token[] = [];\n const push = (asset: Token | null | undefined) => {\n if (!asset?.assetId || ordered.some((t) => t.assetId === asset.assetId)) return;\n ordered.push(asset);\n };\n if (options?.preferredMidAssetId) {\n push(tokens.find((t) => t.assetId === options.preferredMidAssetId));\n }\n for (const symbol of [\"USDC\", \"USDT\"] as const) {\n push(tokens.find((t) => normalizeChain(t.blockchain) === sourceChain && t.symbol.toUpperCase() === symbol));\n }\n if (preferred.kind !== \"DIRECT\") push(preferred.fundingAsset);\n return ordered;\n };\n\n if (preferred.kind === \"DIRECT\") {\n add(\"DIRECT\", destination);\n if (sourceChain === \"btc\" || sourceChain === \"zec\") {\n for (const symbol of [\"USDC\", \"USDT\"] as const) {\n add(\n \"CROSS_CHAIN_SWAP\",\n tokens.find(\n (token) =>\n normalizeChain(token.blockchain) === \"near\" &&\n token.symbol.toUpperCase() === symbol,\n ),\n );\n }\n }\n for (const mid of midCandidates()) add(routeKindFor(mid), mid);\n return routes;\n }\n\n for (const mid of midCandidates()) add(routeKindFor(mid), mid);\n if (routes.length === 0) return [preferred];\n return routes;\n}\n","import { sha256 } from \"@noble/hashes/sha256\";\nimport { bytesToHex } from \"@noble/hashes/utils\";\nimport { assert, throwIfAborted } from \"./errors\";\nimport type { Balance, QuotePlan } from \"./types\";\n\nexport function raw(value: unknown, field = \"Amount\", allowZero = false): bigint {\n assert(typeof value === \"string\" && /^(0|[1-9]\\d*)$/.test(value), `${field} must be a canonical integer string.`);\n const result = BigInt(value);\n assert(allowZero || result > 0n, `${field} must be positive.`);\n return result;\n}\n/** Rescale a raw integer between token decimals (floor when shrinking). */\nexport function rescaleRawAmount(amount: string, fromDecimals: number, toDecimals: number): string {\n assert(Number.isInteger(fromDecimals) && fromDecimals >= 0 && fromDecimals <= 255, \"Invalid source decimals.\");\n assert(Number.isInteger(toDecimals) && toDecimals >= 0 && toDecimals <= 255, \"Invalid target decimals.\");\n const value = raw(amount);\n if (fromDecimals === toDecimals) return value.toString();\n if (toDecimals > fromDecimals) {\n return (value * 10n ** BigInt(toDecimals - fromDecimals)).toString();\n }\n const scaled = value / 10n ** BigInt(fromDecimals - toDecimals);\n assert(scaled > 0n, \"Amount too small after decimal conversion.\");\n return scaled.toString();\n}\nexport function text(value: unknown, field: string): string {\n assert(typeof value === \"string\" && value.trim().length > 0, `${field} is required.`);\n return value.trim();\n}\nexport function clone<T>(value: T): T { return structuredClone(value); }\nfunction canonical(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(canonical).join(\",\")}]`;\n if (value && typeof value === \"object\") {\n return `{${Object.entries(value).filter(([, v]) => v !== undefined).sort(([a], [b]) => a.localeCompare(b))\n .map(([k, v]) => `${JSON.stringify(k)}:${canonical(v)}`).join(\",\")}}`;\n }\n assert(value === null || [\"string\", \"boolean\", \"number\"].includes(typeof value), \"Plan data must be JSON-serializable.\");\n if (typeof value === \"number\") assert(Number.isFinite(value), \"Plan numbers must be finite.\");\n return JSON.stringify(value);\n}\nexport function planDigest(plan: Omit<QuotePlan, \"digest\"> | QuotePlan): string {\n const { digest: _digest, ...body } = plan as QuotePlan;\n return bytesToHex(sha256(new TextEncoder().encode(canonical(body))));\n}\nexport function randomFraction(): number {\n return crypto.getRandomValues(new Uint32Array(1))[0]! / 0xffffffff;\n}\nexport function splitConfidentialRawAmount(totalRaw: string, count: number, values?: readonly number[]): string[] {\n assert(Number.isInteger(count) && count >= 1 && count <= 10, \"A batch requires 1 to 10 recipients.\");\n const total = raw(totalRaw);\n assert(total >= BigInt(count), \"Amount too small for every recipient.\");\n const minimum = total * 80n / (100n * BigInt(count));\n const lowBound = minimum > 0n ? minimum : 1n;\n const denominator = 100n * BigInt(count);\n const maximum = (total * 120n + denominator - 1n) / denominator;\n const highBound = maximum > lowBound ? maximum : lowBound;\n const shares: bigint[] = [];\n let remaining = total;\n for (let i = 0; i < count - 1; i++) {\n const left = BigInt(count - i - 1);\n const low = remaining - highBound * left > lowBound ? remaining - highBound * left : lowBound;\n const high = remaining - lowBound * left < highBound ? remaining - lowBound * left : highBound;\n assert(low <= high, \"Amount cannot be split safely.\");\n const fraction = values?.[i] ?? randomFraction();\n assert(Number.isFinite(fraction) && fraction >= 0 && fraction <= 1, \"Random fractions must be within [0, 1].\");\n const share = low + (high - low) * BigInt(Math.floor(fraction * 1_000_000)) / 1_000_000n;\n shares.push(share);\n remaining -= share;\n }\n assert(remaining >= lowBound && remaining <= highBound, \"Final share outside safe bounds.\");\n return [...shares, remaining].map(String);\n}\nexport function availableForAsset(balances: readonly Balance[], assetId: string): string {\n const exact = balances.find((b) => b.tokenId === assetId);\n const entry = exact ?? balances.find((b) => b.tokenId.startsWith(\"imt:\") && b.tokenId.endsWith(`:${assetId}`));\n return raw(entry?.available ?? \"0\", \"Available balance\", true).toString();\n}\nexport async function delay(ms: number, signal?: AbortSignal): Promise<void> {\n throwIfAborted(signal);\n await new Promise<void>((resolve, reject) => {\n const finish = () => { signal?.removeEventListener(\"abort\", abort); resolve(); };\n const timer = setTimeout(finish, ms);\n const abort = () => { clearTimeout(timer); signal?.removeEventListener(\"abort\", abort); try { throwIfAborted(signal); } catch (e) { reject(e); } };\n signal?.addEventListener(\"abort\", abort, { once: true });\n });\n}\n"],"mappings":"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","names":[]}
|
package/dist/aggregation.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/routing.ts","../src/utils.ts","../src/aggregation.ts"],"sourcesContent":["export type ErrorCode =\n | \"INVALID_INPUT\" | \"INVALID_RESPONSE\" | \"HTTP_ERROR\" | \"UNAUTHORIZED\"\n | \"WALLET_CHANGED\" | \"MISSING_WALLET\" | \"QUOTE_EXPIRED\" | \"BUSY\"\n | \"CONFIRMATION_REQUIRED\" | \"USER_REJECTED\" | \"TIMEOUT\" | \"ABORTED\" | \"TERMINAL_FAILURE\";\n\nexport class ConfidentialSwapError extends Error {\n readonly name = \"ConfidentialSwapError\";\n constructor(\n readonly code: ErrorCode,\n message: string,\n readonly status?: number,\n readonly executionId?: string,\n ) { super(message); }\n}\n\nexport function assert(condition: unknown, message: string): asserts condition {\n if (!condition) throw new ConfidentialSwapError(\"INVALID_INPUT\", message);\n}\n\nexport function responseAssert(condition: unknown, message: string): asserts condition {\n if (!condition) throw new ConfidentialSwapError(\"INVALID_RESPONSE\", message);\n}\n\nexport function isUserRejectedError(error: unknown): boolean {\n if (error instanceof ConfidentialSwapError) return error.code === \"USER_REJECTED\";\n if (!error || typeof error !== \"object\") return false;\n const value = error as { code?: unknown; message?: unknown; error?: unknown };\n const code = String(value.code ?? \"\").toUpperCase();\n const message = String(value.message ?? value.error ?? \"\");\n return (\n code === \"4001\" ||\n code === \"ACTION_REJECTED\" ||\n /user.*(reject|cancel|den(?:y|ied))|(?:reject|cancel|den(?:y|ied)).*user/i.test(message)\n );\n}\n\nexport function userRejectedError(executionId?: string): ConfidentialSwapError {\n return new ConfidentialSwapError(\n \"USER_REJECTED\",\n \"User rejected transaction.\",\n undefined,\n executionId,\n );\n}\n\nexport function throwIfAborted(signal?: AbortSignal): void {\n if (signal?.aborted)\n throw new ConfidentialSwapError(\"ABORTED\", \"Trade cancelled.\");\n}\n","import { assert } from \"./errors\";\nimport type { FundingRoute, Token } from \"./types\";\n\nconst aliases: Record<string, string> = { ethereum: \"eth\", arbitrum: \"arb\", avalanche: \"avax\", optimism: \"op\", polygon: \"pol\", bitcoin: \"btc\", solana: \"sol\", zcash: \"zec\", bnb: \"bsc\", \"bnb chain\": \"bsc\", hyperevm: \"hypercore\" };\nexport function normalizeChain(value: string): string { const chain = value.trim().toLowerCase(); return aliases[chain] ?? chain; }\nfunction contract(value?: string | null): string { const v = value?.trim() ?? \"\"; return /^0x/i.test(v) ? v.toLowerCase() : v; }\nfunction nativeNear(t: Token): boolean {\n return !t.assetId.startsWith(\"mca:\") && normalizeChain(t.blockchain) === \"near\" &&\n ([\"near\", \"nep141:near\"].includes(t.assetId) || t.contractAddress === \"near\" || Boolean(t.isNative && !t.contractAddress));\n}\n/** Intents omft natives often lack contractAddress; supported_to uses placeholders like `zec` / `btc:mainnet:native`. */\nfunction nativeOmftMatch(source: Token, registered: Token, chain: \"btc\" | \"zec\", intentsAssetId: string, placeholders: readonly string[]): boolean {\n if (normalizeChain(source.blockchain) !== chain || normalizeChain(registered.blockchain) !== chain) return false;\n if (registered.assetId !== intentsAssetId && !placeholders.includes(contract(registered.contractAddress).toLowerCase())) return false;\n const id = source.assetId.trim().toLowerCase();\n const src = contract(source.contractAddress).toLowerCase();\n return (\n id === intentsAssetId.toLowerCase() ||\n placeholders.includes(id) ||\n placeholders.includes(src) ||\n /:(mainnet:)?native$/i.test(source.assetId) ||\n Boolean(source.isNative && source.symbol.toUpperCase() === registered.symbol.toUpperCase())\n );\n}\nfunction matches(source: Token, registered: Token): boolean {\n if (normalizeChain(source.blockchain) !== normalizeChain(registered.blockchain)) return false;\n if (source.assetId && source.assetId === registered.assetId) return true;\n if (nativeNear(source)) return [\"nep141:wrap.near\", \"wrap.near\"].includes(registered.assetId) || registered.contractAddress === \"wrap.near\";\n if (nativeOmftMatch(source, registered, \"zec\", \"nep141:zec.omft.near\", [\"zec\", \"zcash\"])) return true;\n if (nativeOmftMatch(source, registered, \"btc\", \"nep141:btc.omft.near\", [\"btc\", \"bitcoin\"])) return true;\n const src = contract(source.contractAddress);\n const target = contract(registered.contractAddress);\n if (src && target) return src === target;\n return Boolean(source.isNative && !src && !target && source.symbol.toUpperCase() === registered.symbol.toUpperCase());\n}\nexport function resolveRegistryToken(token: Token, registry: readonly Token[]): Token | null {\n return registry.find((t) => matches(token, t)) ?? null;\n}\nfunction dedupeRegistry(registry: readonly Token[]): Token[] {\n return registry.filter((t, i) => t.blockchain && t.assetId && registry.findIndex((other) => normalizeChain(other.blockchain) === normalizeChain(t.blockchain) && (contract(other.contractAddress) || other.assetId) === (contract(t.contractAddress) || t.assetId)) === i);\n}\nexport function resolveFundingRoute(source: Token, registry: readonly Token[]): FundingRoute {\n const tokens = dedupeRegistry(registry);\n const direct = resolveRegistryToken(source, tokens);\n if (direct) return { kind: \"DIRECT\", fundingAsset: direct };\n const rank = (t: Token) => { const i = [\"USDC\", \"USDT\", \"USDT0\"].indexOf(t.symbol.toUpperCase()); return i < 0 ? 3 : i; };\n const prefer = (ts: Token[]) => [...ts].sort((a, b) => rank(a) - rank(b))[0];\n const same = prefer(tokens.filter((t) => normalizeChain(t.blockchain) === normalizeChain(source.blockchain)));\n if (same) return { kind: \"SAME_CHAIN_SWAP\", fundingAsset: same };\n const cross = prefer(tokens.filter((t) => normalizeChain(t.blockchain) === \"near\")) ?? prefer(tokens);\n assert(cross, \"No registry funding asset available.\");\n return { kind: \"CROSS_CHAIN_SWAP\", fundingAsset: cross };\n}\n/**\n * Build funding-route candidates for wallet-funded confidential quotes.\n *\n * DIRECT preferred (source is Intents-supported):\n * 1) DIRECT deposit crediting the destination asset\n * 2) SAME_CHAIN mid tokens (last successful mid first, then USDC/USDT)\n *\n * Non-DIRECT preferred (needs a pre-swap mid token):\n * try mid tokens with last successful mid first, then USDC/USDT / preferred.\n */\nexport function buildFundingRouteCandidates(\n source: Token,\n destination: Token,\n preferred: FundingRoute,\n registry: readonly Token[],\n options?: { preferredMidAssetId?: string },\n): FundingRoute[] {\n const tokens = dedupeRegistry(registry);\n const seen = new Set<string>();\n const routes: FundingRoute[] = [];\n const add = (kind: FundingRoute[\"kind\"], asset: Token | null | undefined) => {\n if (!asset?.assetId || seen.has(asset.assetId)) return;\n seen.add(asset.assetId);\n routes.push({ kind, fundingAsset: asset });\n };\n const sourceChain = normalizeChain(source.blockchain);\n const routeKindFor = (asset: Token): FundingRoute[\"kind\"] =>\n normalizeChain(asset.blockchain) === sourceChain ? \"SAME_CHAIN_SWAP\" : \"CROSS_CHAIN_SWAP\";\n const midCandidates = (): Token[] => {\n const ordered: Token[] = [];\n const push = (asset: Token | null | undefined) => {\n if (!asset?.assetId || ordered.some((t) => t.assetId === asset.assetId)) return;\n ordered.push(asset);\n };\n if (options?.preferredMidAssetId) {\n push(tokens.find((t) => t.assetId === options.preferredMidAssetId));\n }\n for (const symbol of [\"USDC\", \"USDT\"] as const) {\n push(tokens.find((t) => normalizeChain(t.blockchain) === sourceChain && t.symbol.toUpperCase() === symbol));\n }\n if (preferred.kind !== \"DIRECT\") push(preferred.fundingAsset);\n return ordered;\n };\n\n if (preferred.kind === \"DIRECT\") {\n add(\"DIRECT\", destination);\n for (const mid of midCandidates()) add(routeKindFor(mid), mid);\n return routes;\n }\n\n for (const mid of midCandidates()) add(routeKindFor(mid), mid);\n if (routes.length === 0) return [preferred];\n return routes;\n}\n","import { sha256 } from \"@noble/hashes/sha256\";\nimport { bytesToHex } from \"@noble/hashes/utils\";\nimport { assert, throwIfAborted } from \"./errors\";\nimport type { Balance, QuotePlan } from \"./types\";\n\nexport function raw(value: unknown, field = \"Amount\", allowZero = false): bigint {\n assert(typeof value === \"string\" && /^(0|[1-9]\\d*)$/.test(value), `${field} must be a canonical integer string.`);\n const result = BigInt(value);\n assert(allowZero || result > 0n, `${field} must be positive.`);\n return result;\n}\n/** Rescale a raw integer between token decimals (floor when shrinking). */\nexport function rescaleRawAmount(amount: string, fromDecimals: number, toDecimals: number): string {\n assert(Number.isInteger(fromDecimals) && fromDecimals >= 0 && fromDecimals <= 255, \"Invalid source decimals.\");\n assert(Number.isInteger(toDecimals) && toDecimals >= 0 && toDecimals <= 255, \"Invalid target decimals.\");\n const value = raw(amount);\n if (fromDecimals === toDecimals) return value.toString();\n if (toDecimals > fromDecimals) {\n return (value * 10n ** BigInt(toDecimals - fromDecimals)).toString();\n }\n const scaled = value / 10n ** BigInt(fromDecimals - toDecimals);\n assert(scaled > 0n, \"Amount too small after decimal conversion.\");\n return scaled.toString();\n}\nexport function text(value: unknown, field: string): string {\n assert(typeof value === \"string\" && value.trim().length > 0, `${field} is required.`);\n return value.trim();\n}\nexport function clone<T>(value: T): T { return structuredClone(value); }\nfunction canonical(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(canonical).join(\",\")}]`;\n if (value && typeof value === \"object\") {\n return `{${Object.entries(value).filter(([, v]) => v !== undefined).sort(([a], [b]) => a.localeCompare(b))\n .map(([k, v]) => `${JSON.stringify(k)}:${canonical(v)}`).join(\",\")}}`;\n }\n assert(value === null || [\"string\", \"boolean\", \"number\"].includes(typeof value), \"Plan data must be JSON-serializable.\");\n if (typeof value === \"number\") assert(Number.isFinite(value), \"Plan numbers must be finite.\");\n return JSON.stringify(value);\n}\nexport function planDigest(plan: Omit<QuotePlan, \"digest\"> | QuotePlan): string {\n const { digest: _digest, ...body } = plan as QuotePlan;\n return bytesToHex(sha256(new TextEncoder().encode(canonical(body))));\n}\nexport function randomFraction(): number {\n return crypto.getRandomValues(new Uint32Array(1))[0]! / 0xffffffff;\n}\nexport function splitConfidentialRawAmount(totalRaw: string, count: number, values?: readonly number[]): string[] {\n assert(Number.isInteger(count) && count >= 1 && count <= 10, \"A batch requires 1 to 10 recipients.\");\n const total = raw(totalRaw);\n assert(total >= BigInt(count), \"Amount too small for every recipient.\");\n const minimum = total * 80n / (100n * BigInt(count));\n const lowBound = minimum > 0n ? minimum : 1n;\n const denominator = 100n * BigInt(count);\n const maximum = (total * 120n + denominator - 1n) / denominator;\n const highBound = maximum > lowBound ? maximum : lowBound;\n const shares: bigint[] = [];\n let remaining = total;\n for (let i = 0; i < count - 1; i++) {\n const left = BigInt(count - i - 1);\n const low = remaining - highBound * left > lowBound ? remaining - highBound * left : lowBound;\n const high = remaining - lowBound * left < highBound ? remaining - lowBound * left : highBound;\n assert(low <= high, \"Amount cannot be split safely.\");\n const fraction = values?.[i] ?? randomFraction();\n assert(Number.isFinite(fraction) && fraction >= 0 && fraction <= 1, \"Random fractions must be within [0, 1].\");\n const share = low + (high - low) * BigInt(Math.floor(fraction * 1_000_000)) / 1_000_000n;\n shares.push(share);\n remaining -= share;\n }\n assert(remaining >= lowBound && remaining <= highBound, \"Final share outside safe bounds.\");\n return [...shares, remaining].map(String);\n}\nexport function availableForAsset(balances: readonly Balance[], assetId: string): string {\n const exact = balances.find((b) => b.tokenId === assetId);\n const entry = exact ?? balances.find((b) => b.tokenId.startsWith(\"imt:\") && b.tokenId.endsWith(`:${assetId}`));\n return raw(entry?.available ?? \"0\", \"Available balance\", true).toString();\n}\nexport async function delay(ms: number, signal?: AbortSignal): Promise<void> {\n throwIfAborted(signal);\n await new Promise<void>((resolve, reject) => {\n const finish = () => { signal?.removeEventListener(\"abort\", abort); resolve(); };\n const timer = setTimeout(finish, ms);\n const abort = () => { clearTimeout(timer); signal?.removeEventListener(\"abort\", abort); try { throwIfAborted(signal); } catch (e) { reject(e); } };\n signal?.addEventListener(\"abort\", abort, { once: true });\n });\n}\n","import type { AssetRef, ChainRef, Quote, QuoteRequest, SwapClient } from \"@rhea-finance/cross-chain-aggregation-dex\";\nimport { assert, ConfidentialSwapError, responseAssert, throwIfAborted } from \"./errors\";\nimport { normalizeChain } from \"./routing\";\nimport { clone, raw, text } from \"./utils\";\nimport type { ExecutionSnapshot, FundingSwapAdapter, SwapFundingRequest, Token } from \"./types\";\n\nexport interface FundingReporter {\n report(snapshot: ExecutionSnapshot, signal?: AbortSignal): Promise<string>;\n getStatus(recordId: string, snapshot: ExecutionSnapshot, signal?: AbortSignal): Promise<string>;\n reconcileReport?(snapshot: ExecutionSnapshot, signal?: AbortSignal): Promise<string | undefined>;\n}\nexport interface AggregationAdapterConfig {\n /** Construct this client with reportMode: \"manual\" to avoid duplicate reports. */\n client: Pick<SwapClient, \"quote\" | \"buildSwap\" | \"executeSwap\">;\n reporter: FundingReporter;\n /** Explicit mapping avoids conflating registry asset IDs and aggregation token addresses. */\n mapAsset: (token: Token) => AssetRef;\n reconcileFunding?: FundingSwapAdapter[\"reconcileFunding\"];\n}\nfunction serialize(request: SwapFundingRequest, map: AggregationAdapterConfig[\"mapAsset\"]): QuoteRequest {\n const tokenIn = map(clone(request.sourceToken));\n const tokenOut = map(clone(request.fundingToken));\n assert(tokenIn.chain === request.wallet.chain, \"Aggregation source chain differs from the frozen wallet chain.\");\n text(tokenIn.address, \"Mapped source token address\"); text(tokenOut.address, \"Mapped funding token address\");\n return { fromChain: tokenIn.chain, toChain: tokenOut.chain, tokenIn, tokenOut, amountIn: request.amount, slippageBps: request.slippageBps, sender: request.wallet.accountId, recipient: request.recipient };\n}\nfunction equalAddress(a: string | undefined, b: string, chain: string): boolean {\n return /^\\d+$/.test(chain) ? a?.toLowerCase() === b.toLowerCase() : a === b;\n}\nexport function createAggregationFundingAdapter(config: AggregationAdapterConfig): FundingSwapAdapter {\n return {\n quote: async (request, signal) => {\n const quote = await config.client.quote(serialize(request, config.mapAsset), { signal });\n return { amountOut: quote.estimatedOut, minAmountOut: quote.minAmountOut, expiresAt: quote.expiresAt, data: clone(quote) };\n },\n execute: async (input, request, _id, signal) => {\n throwIfAborted(signal);\n const quote = clone(input.data) as Quote;\n const expected = serialize(request, config.mapAsset);\n responseAssert(quote.amountIn === request.amount && quote.fromChain === expected.fromChain && quote.toChain === expected.toChain\n && equalAddress(quote.buildContext.request.sender, request.wallet.accountId, expected.fromChain)\n && quote.buildContext.request.recipient === request.recipient\n && equalAddress(quote.tokenIn.address, expected.tokenIn.address, expected.fromChain)\n && equalAddress(quote.tokenOut.address, expected.tokenOut.address, expected.toChain)\n && equalAddress(quote.buildContext.request.tokenIn, expected.tokenIn.address, expected.fromChain)\n && equalAddress(quote.buildContext.request.tokenOut, expected.tokenOut.address, expected.toChain), \"Funding quote does not match the frozen execution request.\");\n const build = await config.client.buildSwap({ quote, signal });\n responseAssert(raw(build.minAmountOut) >= raw(input.minAmountOut), \"Built swap minimum output fell below its funding quote.\");\n responseAssert(build.fromChain === expected.fromChain && build.toChain === expected.toChain && build.amountIn === request.amount\n && equalAddress(build.tokenIn.address, expected.tokenIn.address, expected.fromChain)\n && equalAddress(build.tokenOut.address, expected.tokenOut.address, expected.toChain), \"Built swap asset or chain differs from its funding request.\");\n const result = await config.client.executeSwap({ build, signal, waitFor: \"submitted\" });\n responseAssert(result.txHash, \"Funding swap did not return a source transaction hash.\");\n return { txHash: result.txHash, orderId: result.orderId, router: result.router };\n },\n report: config.reporter.report.bind(config.reporter),\n getStatus: config.reporter.getStatus.bind(config.reporter),\n reconcileReport: config.reporter.reconcileReport?.bind(config.reporter),\n reconcileFunding: config.reconcileFunding,\n };\n}\nexport interface HttpFundingReporterConfig {\n baseUrl: string;\n bearerToken?: string;\n fetch?: typeof fetch;\n requestTimeoutMs?: number;\n}\nexport class HttpFundingReporter implements FundingReporter {\n private readonly base: string;\n private readonly fetcher: typeof fetch;\n constructor(private readonly config: HttpFundingReporterConfig) {\n const url = new URL(config.baseUrl);\n assert([\"http:\", \"https:\"].includes(url.protocol) && !url.username && !url.password, \"Invalid report service URL.\");\n this.base = config.baseUrl.replace(/\\/+$/, \"\");\n this.fetcher = config.fetch ?? globalThis.fetch;\n assert(Number.isFinite(config.requestTimeoutMs ?? 30_000) && (config.requestTimeoutMs ?? 30_000) > 0, \"Invalid report timeout.\");\n }\n private async request(path: string, body?: unknown, signal?: AbortSignal): Promise<Record<string, unknown>> {\n throwIfAborted(signal);\n const timeout = AbortSignal.timeout(this.config.requestTimeoutMs ?? 30_000);\n try {\n const response = await this.fetcher(`${this.base}/api/swap/${path}`, {\n method: body ? \"POST\" : \"GET\", signal: signal ? AbortSignal.any([signal, timeout]) : timeout,\n headers: { Accept: \"application/json\", ...(body ? { \"Content-Type\": \"application/json\" } : {}), ...(this.config.bearerToken ? { Authorization: `Bearer ${this.config.bearerToken}` } : {}) },\n ...(body ? { body: JSON.stringify(body) } : {}),\n });\n if (!response.ok) throw new ConfidentialSwapError(\"HTTP_ERROR\", `Funding report request failed (${response.status}).`, response.status);\n let result: unknown;\n try { result = await response.json(); } catch { throw new ConfidentialSwapError(\"INVALID_RESPONSE\", \"Invalid funding report JSON.\"); }\n responseAssert(result && typeof result === \"object\", \"Invalid funding report response.\");\n const envelope = result as Record<string, unknown>;\n responseAssert(envelope.code === 0 && envelope.data && typeof envelope.data === \"object\", \"Invalid funding report envelope.\");\n return envelope.data as Record<string, unknown>;\n } catch (error) {\n throwIfAborted(signal);\n if (timeout.aborted) throw new ConfidentialSwapError(\"TIMEOUT\", \"Funding report request timed out; reconcile before resubmitting.\");\n throw error;\n }\n }\n async report(s: ExecutionSnapshot, signal?: AbortSignal): Promise<string> {\n const plan = s.plan;\n const quoted = plan.swapQuote?.data as Partial<Quote> | undefined;\n assert(s.receipt, \"Cannot report without a funding receipt.\");\n const data = await this.request(\"report\", {\n sender: plan.request.sourceWallet.accountId, recipient: plan.request.recipients[0], from_hash: s.receipt.txHash,\n deposit_address: plan.depositQuote!.quote.depositAddress, from_token: quoted?.tokenIn?.address ?? plan.request.sourceToken.swapAddress ?? plan.request.sourceToken.contractAddress ?? plan.request.sourceToken.assetId,\n to_token: quoted?.tokenOut?.address ?? plan.route.fundingAsset.swapAddress ?? plan.route.fundingAsset.contractAddress ?? plan.route.fundingAsset.assetId,\n from_chain: quoted?.fromChain ?? plan.request.sourceWallet.chain, to_chain: quoted?.toChain ?? plan.swapRequest?.fundingToken.swapChain ?? normalizeChain(plan.route.fundingAsset.blockchain),\n amount_in: plan.request.amount, estimated_out: plan.fundingAmount, router: s.receipt.router ?? \"rhea\", is_cross_chain: plan.route.kind === \"CROSS_CHAIN_SWAP\",\n tx_type: \"confidential-batch-withdraw\", confidentiality: \"advanced\", ...(s.receipt.orderId ? { swapId: s.receipt.orderId } : {}),\n extra: { recipient_count: plan.request.recipients.length, funding_route: plan.route.kind, execution_id: s.id },\n }, signal);\n responseAssert((typeof data.id === \"number\" && Number.isSafeInteger(data.id) && data.id > 0) || (typeof data.id === \"string\" && /^\\d+$/.test(data.id)), \"Report returned no record id.\");\n return String(data.id);\n }\n async getStatus(recordId: string, _s: ExecutionSnapshot, signal?: AbortSignal): Promise<string> {\n const query = new URLSearchParams({ recordId: text(recordId, \"Record id\") });\n const data = await this.request(`order-status?${query}`, undefined, signal);\n return text(data.status ?? data.state, \"Funding status\");\n }\n}\nexport type { AssetRef, ChainRef };\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/routing.ts","../src/utils.ts","../src/aggregation.ts"],"sourcesContent":["export type ErrorCode =\n | \"INVALID_INPUT\" | \"INVALID_RESPONSE\" | \"HTTP_ERROR\" | \"UNAUTHORIZED\"\n | \"WALLET_CHANGED\" | \"MISSING_WALLET\" | \"QUOTE_EXPIRED\" | \"BUSY\"\n | \"CONFIRMATION_REQUIRED\" | \"USER_REJECTED\" | \"TIMEOUT\" | \"ABORTED\" | \"TERMINAL_FAILURE\";\n\nexport class ConfidentialSwapError extends Error {\n readonly name = \"ConfidentialSwapError\";\n constructor(\n readonly code: ErrorCode,\n message: string,\n readonly status?: number,\n readonly executionId?: string,\n ) { super(message); }\n}\n\nexport function assert(condition: unknown, message: string): asserts condition {\n if (!condition) throw new ConfidentialSwapError(\"INVALID_INPUT\", message);\n}\n\nexport function responseAssert(condition: unknown, message: string): asserts condition {\n if (!condition) throw new ConfidentialSwapError(\"INVALID_RESPONSE\", message);\n}\n\nexport function isUserRejectedError(error: unknown): boolean {\n if (error instanceof ConfidentialSwapError) return error.code === \"USER_REJECTED\";\n if (!error || typeof error !== \"object\") return false;\n const value = error as { code?: unknown; message?: unknown; error?: unknown };\n const code = String(value.code ?? \"\").toUpperCase();\n const message = String(value.message ?? value.error ?? \"\");\n return (\n code === \"4001\" ||\n code === \"ACTION_REJECTED\" ||\n /user.*(reject|cancel|den(?:y|ied))|(?:reject|cancel|den(?:y|ied)).*user/i.test(message)\n );\n}\n\nexport function userRejectedError(executionId?: string): ConfidentialSwapError {\n return new ConfidentialSwapError(\n \"USER_REJECTED\",\n \"User rejected transaction.\",\n undefined,\n executionId,\n );\n}\n\nexport function throwIfAborted(signal?: AbortSignal): void {\n if (signal?.aborted)\n throw new ConfidentialSwapError(\"ABORTED\", \"Trade cancelled.\");\n}\n","import { assert } from \"./errors\";\nimport type { FundingRoute, Token } from \"./types\";\n\nconst aliases: Record<string, string> = { ethereum: \"eth\", arbitrum: \"arb\", avalanche: \"avax\", optimism: \"op\", polygon: \"pol\", bitcoin: \"btc\", solana: \"sol\", zcash: \"zec\", bnb: \"bsc\", \"bnb chain\": \"bsc\", hyperevm: \"hypercore\" };\nexport function normalizeChain(value: string): string { const chain = value.trim().toLowerCase(); return aliases[chain] ?? chain; }\nfunction contract(value?: string | null): string { const v = value?.trim() ?? \"\"; return /^0x/i.test(v) ? v.toLowerCase() : v; }\nfunction nativeNear(t: Token): boolean {\n return !t.assetId.startsWith(\"mca:\") && normalizeChain(t.blockchain) === \"near\" &&\n ([\"near\", \"nep141:near\"].includes(t.assetId) || t.contractAddress === \"near\" || Boolean(t.isNative && !t.contractAddress));\n}\n/** Intents omft natives often lack contractAddress; supported_to uses placeholders like `zec` / `btc:mainnet:native`. */\nfunction nativeOmftMatch(source: Token, registered: Token, chain: \"btc\" | \"zec\", intentsAssetId: string, placeholders: readonly string[]): boolean {\n if (normalizeChain(source.blockchain) !== chain || normalizeChain(registered.blockchain) !== chain) return false;\n if (registered.assetId !== intentsAssetId && !placeholders.includes(contract(registered.contractAddress).toLowerCase())) return false;\n const id = source.assetId.trim().toLowerCase();\n const src = contract(source.contractAddress).toLowerCase();\n return (\n id === intentsAssetId.toLowerCase() ||\n placeholders.includes(id) ||\n placeholders.includes(src) ||\n /:(mainnet:)?native$/i.test(source.assetId) ||\n Boolean(source.isNative && source.symbol.toUpperCase() === registered.symbol.toUpperCase())\n );\n}\nfunction matches(source: Token, registered: Token): boolean {\n if (normalizeChain(source.blockchain) !== normalizeChain(registered.blockchain)) return false;\n if (source.assetId && source.assetId === registered.assetId) return true;\n if (nativeNear(source)) return [\"nep141:wrap.near\", \"wrap.near\"].includes(registered.assetId) || registered.contractAddress === \"wrap.near\";\n if (nativeOmftMatch(source, registered, \"zec\", \"nep141:zec.omft.near\", [\"zec\", \"zcash\"])) return true;\n if (nativeOmftMatch(source, registered, \"btc\", \"nep141:btc.omft.near\", [\"btc\", \"bitcoin\"])) return true;\n const src = contract(source.contractAddress);\n const target = contract(registered.contractAddress);\n if (src && target) return src === target;\n return Boolean(source.isNative && !src && !target && source.symbol.toUpperCase() === registered.symbol.toUpperCase());\n}\nexport function resolveRegistryToken(token: Token, registry: readonly Token[]): Token | null {\n return registry.find((t) => matches(token, t)) ?? null;\n}\nfunction dedupeRegistry(registry: readonly Token[]): Token[] {\n return registry.filter((t, i) => t.blockchain && t.assetId && registry.findIndex((other) => normalizeChain(other.blockchain) === normalizeChain(t.blockchain) && (contract(other.contractAddress) || other.assetId) === (contract(t.contractAddress) || t.assetId)) === i);\n}\nexport function resolveFundingRoute(source: Token, registry: readonly Token[]): FundingRoute {\n const tokens = dedupeRegistry(registry);\n const direct = resolveRegistryToken(source, tokens);\n if (direct) return { kind: \"DIRECT\", fundingAsset: direct };\n const rank = (t: Token) => { const i = [\"USDC\", \"USDT\", \"USDT0\"].indexOf(t.symbol.toUpperCase()); return i < 0 ? 3 : i; };\n const prefer = (ts: Token[]) => [...ts].sort((a, b) => rank(a) - rank(b))[0];\n const same = prefer(tokens.filter((t) => normalizeChain(t.blockchain) === normalizeChain(source.blockchain)));\n if (same) return { kind: \"SAME_CHAIN_SWAP\", fundingAsset: same };\n const cross = prefer(tokens.filter((t) => normalizeChain(t.blockchain) === \"near\")) ?? prefer(tokens);\n assert(cross, \"No registry funding asset available.\");\n return { kind: \"CROSS_CHAIN_SWAP\", fundingAsset: cross };\n}\n/**\n * Build funding-route candidates for wallet-funded confidential quotes.\n *\n * DIRECT preferred (source is Intents-supported):\n * 1) DIRECT deposit crediting the destination asset\n * 2) SAME_CHAIN mid tokens (last successful mid first, then USDC/USDT)\n *\n * Non-DIRECT preferred (needs a pre-swap mid token):\n * try mid tokens with last successful mid first, then USDC/USDT / preferred.\n */\nexport function buildFundingRouteCandidates(\n source: Token,\n destination: Token,\n preferred: FundingRoute,\n registry: readonly Token[],\n options?: { preferredMidAssetId?: string },\n): FundingRoute[] {\n const tokens = dedupeRegistry(registry);\n const seen = new Set<string>();\n const routes: FundingRoute[] = [];\n const add = (kind: FundingRoute[\"kind\"], asset: Token | null | undefined) => {\n if (!asset?.assetId || seen.has(asset.assetId)) return;\n seen.add(asset.assetId);\n routes.push({ kind, fundingAsset: asset });\n };\n const sourceChain = normalizeChain(source.blockchain);\n const routeKindFor = (asset: Token): FundingRoute[\"kind\"] =>\n normalizeChain(asset.blockchain) === sourceChain ? \"SAME_CHAIN_SWAP\" : \"CROSS_CHAIN_SWAP\";\n const midCandidates = (): Token[] => {\n const ordered: Token[] = [];\n const push = (asset: Token | null | undefined) => {\n if (!asset?.assetId || ordered.some((t) => t.assetId === asset.assetId)) return;\n ordered.push(asset);\n };\n if (options?.preferredMidAssetId) {\n push(tokens.find((t) => t.assetId === options.preferredMidAssetId));\n }\n for (const symbol of [\"USDC\", \"USDT\"] as const) {\n push(tokens.find((t) => normalizeChain(t.blockchain) === sourceChain && t.symbol.toUpperCase() === symbol));\n }\n if (preferred.kind !== \"DIRECT\") push(preferred.fundingAsset);\n return ordered;\n };\n\n if (preferred.kind === \"DIRECT\") {\n add(\"DIRECT\", destination);\n if (sourceChain === \"btc\" || sourceChain === \"zec\") {\n for (const symbol of [\"USDC\", \"USDT\"] as const) {\n add(\n \"CROSS_CHAIN_SWAP\",\n tokens.find(\n (token) =>\n normalizeChain(token.blockchain) === \"near\" &&\n token.symbol.toUpperCase() === symbol,\n ),\n );\n }\n }\n for (const mid of midCandidates()) add(routeKindFor(mid), mid);\n return routes;\n }\n\n for (const mid of midCandidates()) add(routeKindFor(mid), mid);\n if (routes.length === 0) return [preferred];\n return routes;\n}\n","import { sha256 } from \"@noble/hashes/sha256\";\nimport { bytesToHex } from \"@noble/hashes/utils\";\nimport { assert, throwIfAborted } from \"./errors\";\nimport type { Balance, QuotePlan } from \"./types\";\n\nexport function raw(value: unknown, field = \"Amount\", allowZero = false): bigint {\n assert(typeof value === \"string\" && /^(0|[1-9]\\d*)$/.test(value), `${field} must be a canonical integer string.`);\n const result = BigInt(value);\n assert(allowZero || result > 0n, `${field} must be positive.`);\n return result;\n}\n/** Rescale a raw integer between token decimals (floor when shrinking). */\nexport function rescaleRawAmount(amount: string, fromDecimals: number, toDecimals: number): string {\n assert(Number.isInteger(fromDecimals) && fromDecimals >= 0 && fromDecimals <= 255, \"Invalid source decimals.\");\n assert(Number.isInteger(toDecimals) && toDecimals >= 0 && toDecimals <= 255, \"Invalid target decimals.\");\n const value = raw(amount);\n if (fromDecimals === toDecimals) return value.toString();\n if (toDecimals > fromDecimals) {\n return (value * 10n ** BigInt(toDecimals - fromDecimals)).toString();\n }\n const scaled = value / 10n ** BigInt(fromDecimals - toDecimals);\n assert(scaled > 0n, \"Amount too small after decimal conversion.\");\n return scaled.toString();\n}\nexport function text(value: unknown, field: string): string {\n assert(typeof value === \"string\" && value.trim().length > 0, `${field} is required.`);\n return value.trim();\n}\nexport function clone<T>(value: T): T { return structuredClone(value); }\nfunction canonical(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(canonical).join(\",\")}]`;\n if (value && typeof value === \"object\") {\n return `{${Object.entries(value).filter(([, v]) => v !== undefined).sort(([a], [b]) => a.localeCompare(b))\n .map(([k, v]) => `${JSON.stringify(k)}:${canonical(v)}`).join(\",\")}}`;\n }\n assert(value === null || [\"string\", \"boolean\", \"number\"].includes(typeof value), \"Plan data must be JSON-serializable.\");\n if (typeof value === \"number\") assert(Number.isFinite(value), \"Plan numbers must be finite.\");\n return JSON.stringify(value);\n}\nexport function planDigest(plan: Omit<QuotePlan, \"digest\"> | QuotePlan): string {\n const { digest: _digest, ...body } = plan as QuotePlan;\n return bytesToHex(sha256(new TextEncoder().encode(canonical(body))));\n}\nexport function randomFraction(): number {\n return crypto.getRandomValues(new Uint32Array(1))[0]! / 0xffffffff;\n}\nexport function splitConfidentialRawAmount(totalRaw: string, count: number, values?: readonly number[]): string[] {\n assert(Number.isInteger(count) && count >= 1 && count <= 10, \"A batch requires 1 to 10 recipients.\");\n const total = raw(totalRaw);\n assert(total >= BigInt(count), \"Amount too small for every recipient.\");\n const minimum = total * 80n / (100n * BigInt(count));\n const lowBound = minimum > 0n ? minimum : 1n;\n const denominator = 100n * BigInt(count);\n const maximum = (total * 120n + denominator - 1n) / denominator;\n const highBound = maximum > lowBound ? maximum : lowBound;\n const shares: bigint[] = [];\n let remaining = total;\n for (let i = 0; i < count - 1; i++) {\n const left = BigInt(count - i - 1);\n const low = remaining - highBound * left > lowBound ? remaining - highBound * left : lowBound;\n const high = remaining - lowBound * left < highBound ? remaining - lowBound * left : highBound;\n assert(low <= high, \"Amount cannot be split safely.\");\n const fraction = values?.[i] ?? randomFraction();\n assert(Number.isFinite(fraction) && fraction >= 0 && fraction <= 1, \"Random fractions must be within [0, 1].\");\n const share = low + (high - low) * BigInt(Math.floor(fraction * 1_000_000)) / 1_000_000n;\n shares.push(share);\n remaining -= share;\n }\n assert(remaining >= lowBound && remaining <= highBound, \"Final share outside safe bounds.\");\n return [...shares, remaining].map(String);\n}\nexport function availableForAsset(balances: readonly Balance[], assetId: string): string {\n const exact = balances.find((b) => b.tokenId === assetId);\n const entry = exact ?? balances.find((b) => b.tokenId.startsWith(\"imt:\") && b.tokenId.endsWith(`:${assetId}`));\n return raw(entry?.available ?? \"0\", \"Available balance\", true).toString();\n}\nexport async function delay(ms: number, signal?: AbortSignal): Promise<void> {\n throwIfAborted(signal);\n await new Promise<void>((resolve, reject) => {\n const finish = () => { signal?.removeEventListener(\"abort\", abort); resolve(); };\n const timer = setTimeout(finish, ms);\n const abort = () => { clearTimeout(timer); signal?.removeEventListener(\"abort\", abort); try { throwIfAborted(signal); } catch (e) { reject(e); } };\n signal?.addEventListener(\"abort\", abort, { once: true });\n });\n}\n","import type { AssetRef, ChainRef, Quote, QuoteRequest, SwapClient } from \"@rhea-finance/cross-chain-aggregation-dex\";\nimport { assert, ConfidentialSwapError, responseAssert, throwIfAborted } from \"./errors\";\nimport { normalizeChain } from \"./routing\";\nimport { clone, raw, text } from \"./utils\";\nimport type { ExecutionSnapshot, FundingSwapAdapter, SwapFundingRequest, Token } from \"./types\";\n\nexport interface FundingReporter {\n report(snapshot: ExecutionSnapshot, signal?: AbortSignal): Promise<string>;\n getStatus(recordId: string, snapshot: ExecutionSnapshot, signal?: AbortSignal): Promise<string>;\n reconcileReport?(snapshot: ExecutionSnapshot, signal?: AbortSignal): Promise<string | undefined>;\n}\nexport interface AggregationAdapterConfig {\n /** Construct this client with reportMode: \"manual\" to avoid duplicate reports. */\n client: Pick<SwapClient, \"quote\" | \"buildSwap\" | \"executeSwap\">;\n reporter: FundingReporter;\n /** Explicit mapping avoids conflating registry asset IDs and aggregation token addresses. */\n mapAsset: (token: Token) => AssetRef;\n reconcileFunding?: FundingSwapAdapter[\"reconcileFunding\"];\n}\nfunction serialize(request: SwapFundingRequest, map: AggregationAdapterConfig[\"mapAsset\"]): QuoteRequest {\n const tokenIn = map(clone(request.sourceToken));\n const tokenOut = map(clone(request.fundingToken));\n assert(tokenIn.chain === request.wallet.chain, \"Aggregation source chain differs from the frozen wallet chain.\");\n text(tokenIn.address, \"Mapped source token address\"); text(tokenOut.address, \"Mapped funding token address\");\n return { fromChain: tokenIn.chain, toChain: tokenOut.chain, tokenIn, tokenOut, amountIn: request.amount, slippageBps: request.slippageBps, sender: request.wallet.accountId, recipient: request.recipient };\n}\nfunction equalAddress(a: string | undefined, b: string, chain: string): boolean {\n return /^\\d+$/.test(chain) ? a?.toLowerCase() === b.toLowerCase() : a === b;\n}\nexport function createAggregationFundingAdapter(config: AggregationAdapterConfig): FundingSwapAdapter {\n return {\n quote: async (request, signal) => {\n const quote = await config.client.quote(serialize(request, config.mapAsset), { signal });\n return { amountOut: quote.estimatedOut, minAmountOut: quote.minAmountOut, expiresAt: quote.expiresAt, data: clone(quote) };\n },\n execute: async (input, request, _id, signal) => {\n throwIfAborted(signal);\n const quote = clone(input.data) as Quote;\n const expected = serialize(request, config.mapAsset);\n responseAssert(quote.amountIn === request.amount && quote.fromChain === expected.fromChain && quote.toChain === expected.toChain\n && equalAddress(quote.buildContext.request.sender, request.wallet.accountId, expected.fromChain)\n && quote.buildContext.request.recipient === request.recipient\n && equalAddress(quote.tokenIn.address, expected.tokenIn.address, expected.fromChain)\n && equalAddress(quote.tokenOut.address, expected.tokenOut.address, expected.toChain)\n && equalAddress(quote.buildContext.request.tokenIn, expected.tokenIn.address, expected.fromChain)\n && equalAddress(quote.buildContext.request.tokenOut, expected.tokenOut.address, expected.toChain), \"Funding quote does not match the frozen execution request.\");\n const build = await config.client.buildSwap({ quote, signal });\n responseAssert(raw(build.minAmountOut) >= raw(input.minAmountOut), \"Built swap minimum output fell below its funding quote.\");\n responseAssert(build.fromChain === expected.fromChain && build.toChain === expected.toChain && build.amountIn === request.amount\n && equalAddress(build.tokenIn.address, expected.tokenIn.address, expected.fromChain)\n && equalAddress(build.tokenOut.address, expected.tokenOut.address, expected.toChain), \"Built swap asset or chain differs from its funding request.\");\n const result = await config.client.executeSwap({ build, signal, waitFor: \"submitted\" });\n responseAssert(result.txHash, \"Funding swap did not return a source transaction hash.\");\n return { txHash: result.txHash, orderId: result.orderId, router: result.router };\n },\n report: config.reporter.report.bind(config.reporter),\n getStatus: config.reporter.getStatus.bind(config.reporter),\n reconcileReport: config.reporter.reconcileReport?.bind(config.reporter),\n reconcileFunding: config.reconcileFunding,\n };\n}\nexport interface HttpFundingReporterConfig {\n baseUrl: string;\n bearerToken?: string;\n fetch?: typeof fetch;\n requestTimeoutMs?: number;\n}\nexport class HttpFundingReporter implements FundingReporter {\n private readonly base: string;\n private readonly fetcher: typeof fetch;\n constructor(private readonly config: HttpFundingReporterConfig) {\n const url = new URL(config.baseUrl);\n assert([\"http:\", \"https:\"].includes(url.protocol) && !url.username && !url.password, \"Invalid report service URL.\");\n this.base = config.baseUrl.replace(/\\/+$/, \"\");\n this.fetcher = config.fetch ?? globalThis.fetch;\n assert(Number.isFinite(config.requestTimeoutMs ?? 30_000) && (config.requestTimeoutMs ?? 30_000) > 0, \"Invalid report timeout.\");\n }\n private async request(path: string, body?: unknown, signal?: AbortSignal): Promise<Record<string, unknown>> {\n throwIfAborted(signal);\n const timeout = AbortSignal.timeout(this.config.requestTimeoutMs ?? 30_000);\n try {\n const response = await this.fetcher(`${this.base}/api/swap/${path}`, {\n method: body ? \"POST\" : \"GET\", signal: signal ? AbortSignal.any([signal, timeout]) : timeout,\n headers: { Accept: \"application/json\", ...(body ? { \"Content-Type\": \"application/json\" } : {}), ...(this.config.bearerToken ? { Authorization: `Bearer ${this.config.bearerToken}` } : {}) },\n ...(body ? { body: JSON.stringify(body) } : {}),\n });\n if (!response.ok) throw new ConfidentialSwapError(\"HTTP_ERROR\", `Funding report request failed (${response.status}).`, response.status);\n let result: unknown;\n try { result = await response.json(); } catch { throw new ConfidentialSwapError(\"INVALID_RESPONSE\", \"Invalid funding report JSON.\"); }\n responseAssert(result && typeof result === \"object\", \"Invalid funding report response.\");\n const envelope = result as Record<string, unknown>;\n responseAssert(envelope.code === 0 && envelope.data && typeof envelope.data === \"object\", \"Invalid funding report envelope.\");\n return envelope.data as Record<string, unknown>;\n } catch (error) {\n throwIfAborted(signal);\n if (timeout.aborted) throw new ConfidentialSwapError(\"TIMEOUT\", \"Funding report request timed out; reconcile before resubmitting.\");\n throw error;\n }\n }\n async report(s: ExecutionSnapshot, signal?: AbortSignal): Promise<string> {\n const plan = s.plan;\n const quoted = plan.swapQuote?.data as Partial<Quote> | undefined;\n assert(s.receipt, \"Cannot report without a funding receipt.\");\n const data = await this.request(\"report\", {\n sender: plan.request.sourceWallet.accountId, recipient: plan.request.recipients[0], from_hash: s.receipt.txHash,\n deposit_address: plan.depositQuote!.quote.depositAddress, from_token: quoted?.tokenIn?.address ?? plan.request.sourceToken.swapAddress ?? plan.request.sourceToken.contractAddress ?? plan.request.sourceToken.assetId,\n to_token: quoted?.tokenOut?.address ?? plan.route.fundingAsset.swapAddress ?? plan.route.fundingAsset.contractAddress ?? plan.route.fundingAsset.assetId,\n from_chain: quoted?.fromChain ?? plan.request.sourceWallet.chain, to_chain: quoted?.toChain ?? plan.swapRequest?.fundingToken.swapChain ?? normalizeChain(plan.route.fundingAsset.blockchain),\n amount_in: plan.request.amount, estimated_out: plan.fundingAmount, router: s.receipt.router ?? \"rhea\", is_cross_chain: plan.route.kind === \"CROSS_CHAIN_SWAP\",\n tx_type: \"confidential-batch-withdraw\", confidentiality: \"advanced\", ...(s.receipt.orderId ? { swapId: s.receipt.orderId } : {}),\n extra: { recipient_count: plan.request.recipients.length, funding_route: plan.route.kind, execution_id: s.id },\n }, signal);\n responseAssert((typeof data.id === \"number\" && Number.isSafeInteger(data.id) && data.id > 0) || (typeof data.id === \"string\" && /^\\d+$/.test(data.id)), \"Report returned no record id.\");\n return String(data.id);\n }\n async getStatus(recordId: string, _s: ExecutionSnapshot, signal?: AbortSignal): Promise<string> {\n const query = new URLSearchParams({ recordId: text(recordId, \"Record id\") });\n const data = await this.request(`order-status?${query}`, undefined, signal);\n return text(data.status ?? data.state, \"Funding status\");\n }\n}\nexport type { AssetRef, ChainRef };\n"],"mappings":";AAKO,IAAM,wBAAN,cAAoC,MAAM;AAAA,EAE/C,YACW,MACT,SACS,QACA,aACT;AAAE,UAAM,OAAO;AAJN;AAEA;AACA;AAAA,EACS;AAAA,EAJT;AAAA,EAEA;AAAA,EACA;AAAA,EALF,OAAO;AAOlB;AAEO,SAAS,OAAO,WAAoB,SAAoC;AAC7E,MAAI,CAAC,UAAW,OAAM,IAAI,sBAAsB,iBAAiB,OAAO;AAC1E;AAEO,SAAS,eAAe,WAAoB,SAAoC;AACrF,MAAI,CAAC,UAAW,OAAM,IAAI,sBAAsB,oBAAoB,OAAO;AAC7E;AAwBO,SAAS,eAAe,QAA4B;AACzD,MAAI,QAAQ;AACV,UAAM,IAAI,sBAAsB,WAAW,kBAAkB;AACjE;;;AC7CA,IAAM,UAAkC,EAAE,UAAU,OAAO,UAAU,OAAO,WAAW,QAAQ,UAAU,MAAM,SAAS,OAAO,SAAS,OAAO,QAAQ,OAAO,OAAO,OAAO,KAAK,OAAO,aAAa,OAAO,UAAU,YAAY;AAC3N,SAAS,eAAe,OAAuB;AAAE,QAAM,QAAQ,MAAM,KAAK,EAAE,YAAY;AAAG,SAAO,QAAQ,KAAK,KAAK;AAAO;;;ACJlI,SAAS,cAAc;AACvB,SAAS,kBAAkB;AAIpB,SAAS,IAAI,OAAgB,QAAQ,UAAU,YAAY,OAAe;AAC/E,SAAO,OAAO,UAAU,YAAY,iBAAiB,KAAK,KAAK,GAAG,GAAG,KAAK,sCAAsC;AAChH,QAAM,SAAS,OAAO,KAAK;AAC3B,SAAO,aAAa,SAAS,IAAI,GAAG,KAAK,oBAAoB;AAC7D,SAAO;AACT;AAcO,SAAS,KAAK,OAAgB,OAAuB;AAC1D,SAAO,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,SAAS,GAAG,GAAG,KAAK,eAAe;AACpF,SAAO,MAAM,KAAK;AACpB;AACO,SAAS,MAAS,OAAa;AAAE,SAAO,gBAAgB,KAAK;AAAG;;;ACTvE,SAAS,UAAU,SAA6B,KAAyD;AACvG,QAAM,UAAU,IAAI,MAAM,QAAQ,WAAW,CAAC;AAC9C,QAAM,WAAW,IAAI,MAAM,QAAQ,YAAY,CAAC;AAChD,SAAO,QAAQ,UAAU,QAAQ,OAAO,OAAO,gEAAgE;AAC/G,OAAK,QAAQ,SAAS,6BAA6B;AAAG,OAAK,SAAS,SAAS,8BAA8B;AAC3G,SAAO,EAAE,WAAW,QAAQ,OAAO,SAAS,SAAS,OAAO,SAAS,UAAU,UAAU,QAAQ,QAAQ,aAAa,QAAQ,aAAa,QAAQ,QAAQ,OAAO,WAAW,WAAW,QAAQ,UAAU;AAC5M;AACA,SAAS,aAAa,GAAuB,GAAW,OAAwB;AAC9E,SAAO,QAAQ,KAAK,KAAK,IAAI,GAAG,YAAY,MAAM,EAAE,YAAY,IAAI,MAAM;AAC5E;AACO,SAAS,gCAAgC,QAAsD;AACpG,SAAO;AAAA,IACL,OAAO,OAAO,SAAS,WAAW;AAChC,YAAM,QAAQ,MAAM,OAAO,OAAO,MAAM,UAAU,SAAS,OAAO,QAAQ,GAAG,EAAE,OAAO,CAAC;AACvF,aAAO,EAAE,WAAW,MAAM,cAAc,cAAc,MAAM,cAAc,WAAW,MAAM,WAAW,MAAM,MAAM,KAAK,EAAE;AAAA,IAC3H;AAAA,IACA,SAAS,OAAO,OAAO,SAAS,KAAK,WAAW;AAC9C,qBAAe,MAAM;AACrB,YAAM,QAAQ,MAAM,MAAM,IAAI;AAC9B,YAAM,WAAW,UAAU,SAAS,OAAO,QAAQ;AACnD,qBAAe,MAAM,aAAa,QAAQ,UAAU,MAAM,cAAc,SAAS,aAAa,MAAM,YAAY,SAAS,WACpH,aAAa,MAAM,aAAa,QAAQ,QAAQ,QAAQ,OAAO,WAAW,SAAS,SAAS,KAC5F,MAAM,aAAa,QAAQ,cAAc,QAAQ,aACjD,aAAa,MAAM,QAAQ,SAAS,SAAS,QAAQ,SAAS,SAAS,SAAS,KAChF,aAAa,MAAM,SAAS,SAAS,SAAS,SAAS,SAAS,SAAS,OAAO,KAChF,aAAa,MAAM,aAAa,QAAQ,SAAS,SAAS,QAAQ,SAAS,SAAS,SAAS,KAC7F,aAAa,MAAM,aAAa,QAAQ,UAAU,SAAS,SAAS,SAAS,SAAS,OAAO,GAAG,4DAA4D;AACjK,YAAM,QAAQ,MAAM,OAAO,OAAO,UAAU,EAAE,OAAO,OAAO,CAAC;AAC7D,qBAAe,IAAI,MAAM,YAAY,KAAK,IAAI,MAAM,YAAY,GAAG,yDAAyD;AAC5H,qBAAe,MAAM,cAAc,SAAS,aAAa,MAAM,YAAY,SAAS,WAAW,MAAM,aAAa,QAAQ,UACrH,aAAa,MAAM,QAAQ,SAAS,SAAS,QAAQ,SAAS,SAAS,SAAS,KAChF,aAAa,MAAM,SAAS,SAAS,SAAS,SAAS,SAAS,SAAS,OAAO,GAAG,6DAA6D;AACrJ,YAAM,SAAS,MAAM,OAAO,OAAO,YAAY,EAAE,OAAO,QAAQ,SAAS,YAAY,CAAC;AACtF,qBAAe,OAAO,QAAQ,wDAAwD;AACtF,aAAO,EAAE,QAAQ,OAAO,QAAQ,SAAS,OAAO,SAAS,QAAQ,OAAO,OAAO;AAAA,IACjF;AAAA,IACA,QAAQ,OAAO,SAAS,OAAO,KAAK,OAAO,QAAQ;AAAA,IACnD,WAAW,OAAO,SAAS,UAAU,KAAK,OAAO,QAAQ;AAAA,IACzD,iBAAiB,OAAO,SAAS,iBAAiB,KAAK,OAAO,QAAQ;AAAA,IACtE,kBAAkB,OAAO;AAAA,EAC3B;AACF;AAOO,IAAM,sBAAN,MAAqD;AAAA,EAG1D,YAA6B,QAAmC;AAAnC;AAC3B,UAAM,MAAM,IAAI,IAAI,OAAO,OAAO;AAClC,WAAO,CAAC,SAAS,QAAQ,EAAE,SAAS,IAAI,QAAQ,KAAK,CAAC,IAAI,YAAY,CAAC,IAAI,UAAU,6BAA6B;AAClH,SAAK,OAAO,OAAO,QAAQ,QAAQ,QAAQ,EAAE;AAC7C,SAAK,UAAU,OAAO,SAAS,WAAW;AAC1C,WAAO,OAAO,SAAS,OAAO,oBAAoB,GAAM,MAAM,OAAO,oBAAoB,OAAU,GAAG,yBAAyB;AAAA,EACjI;AAAA,EAN6B;AAAA,EAFZ;AAAA,EACA;AAAA,EAQjB,MAAc,QAAQ,MAAc,MAAgB,QAAwD;AAC1G,mBAAe,MAAM;AACrB,UAAM,UAAU,YAAY,QAAQ,KAAK,OAAO,oBAAoB,GAAM;AAC1E,QAAI;AACF,YAAM,WAAW,MAAM,KAAK,QAAQ,GAAG,KAAK,IAAI,aAAa,IAAI,IAAI;AAAA,QACnE,QAAQ,OAAO,SAAS;AAAA,QAAO,QAAQ,SAAS,YAAY,IAAI,CAAC,QAAQ,OAAO,CAAC,IAAI;AAAA,QACrF,SAAS,EAAE,QAAQ,oBAAoB,GAAI,OAAO,EAAE,gBAAgB,mBAAmB,IAAI,CAAC,GAAI,GAAI,KAAK,OAAO,cAAc,EAAE,eAAe,UAAU,KAAK,OAAO,WAAW,GAAG,IAAI,CAAC,EAAG;AAAA,QAC3L,GAAI,OAAO,EAAE,MAAM,KAAK,UAAU,IAAI,EAAE,IAAI,CAAC;AAAA,MAC/C,CAAC;AACD,UAAI,CAAC,SAAS,GAAI,OAAM,IAAI,sBAAsB,cAAc,kCAAkC,SAAS,MAAM,MAAM,SAAS,MAAM;AACtI,UAAI;AACJ,UAAI;AAAE,iBAAS,MAAM,SAAS,KAAK;AAAA,MAAG,QAAQ;AAAE,cAAM,IAAI,sBAAsB,oBAAoB,8BAA8B;AAAA,MAAG;AACrI,qBAAe,UAAU,OAAO,WAAW,UAAU,kCAAkC;AACvF,YAAM,WAAW;AACjB,qBAAe,SAAS,SAAS,KAAK,SAAS,QAAQ,OAAO,SAAS,SAAS,UAAU,kCAAkC;AAC5H,aAAO,SAAS;AAAA,IAClB,SAAS,OAAO;AACd,qBAAe,MAAM;AACrB,UAAI,QAAQ,QAAS,OAAM,IAAI,sBAAsB,WAAW,kEAAkE;AAClI,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EACA,MAAM,OAAO,GAAsB,QAAuC;AACxE,UAAM,OAAO,EAAE;AACf,UAAM,SAAS,KAAK,WAAW;AAC/B,WAAO,EAAE,SAAS,0CAA0C;AAC5D,UAAM,OAAO,MAAM,KAAK,QAAQ,UAAU;AAAA,MACxC,QAAQ,KAAK,QAAQ,aAAa;AAAA,MAAW,WAAW,KAAK,QAAQ,WAAW,CAAC;AAAA,MAAG,WAAW,EAAE,QAAQ;AAAA,MACzG,iBAAiB,KAAK,aAAc,MAAM;AAAA,MAAgB,YAAY,QAAQ,SAAS,WAAW,KAAK,QAAQ,YAAY,eAAe,KAAK,QAAQ,YAAY,mBAAmB,KAAK,QAAQ,YAAY;AAAA,MAC/M,UAAU,QAAQ,UAAU,WAAW,KAAK,MAAM,aAAa,eAAe,KAAK,MAAM,aAAa,mBAAmB,KAAK,MAAM,aAAa;AAAA,MACjJ,YAAY,QAAQ,aAAa,KAAK,QAAQ,aAAa;AAAA,MAAO,UAAU,QAAQ,WAAW,KAAK,aAAa,aAAa,aAAa,eAAe,KAAK,MAAM,aAAa,UAAU;AAAA,MAC5L,WAAW,KAAK,QAAQ;AAAA,MAAQ,eAAe,KAAK;AAAA,MAAe,QAAQ,EAAE,QAAQ,UAAU;AAAA,MAAQ,gBAAgB,KAAK,MAAM,SAAS;AAAA,MAC3I,SAAS;AAAA,MAA+B,iBAAiB;AAAA,MAAY,GAAI,EAAE,QAAQ,UAAU,EAAE,QAAQ,EAAE,QAAQ,QAAQ,IAAI,CAAC;AAAA,MAC9H,OAAO,EAAE,iBAAiB,KAAK,QAAQ,WAAW,QAAQ,eAAe,KAAK,MAAM,MAAM,cAAc,EAAE,GAAG;AAAA,IAC/G,GAAG,MAAM;AACT,mBAAgB,OAAO,KAAK,OAAO,YAAY,OAAO,cAAc,KAAK,EAAE,KAAK,KAAK,KAAK,KAAO,OAAO,KAAK,OAAO,YAAY,QAAQ,KAAK,KAAK,EAAE,GAAI,+BAA+B;AACvL,WAAO,OAAO,KAAK,EAAE;AAAA,EACvB;AAAA,EACA,MAAM,UAAU,UAAkB,IAAuB,QAAuC;AAC9F,UAAM,QAAQ,IAAI,gBAAgB,EAAE,UAAU,KAAK,UAAU,WAAW,EAAE,CAAC;AAC3E,UAAM,OAAO,MAAM,KAAK,QAAQ,gBAAgB,KAAK,IAAI,QAAW,MAAM;AAC1E,WAAO,KAAK,KAAK,UAAU,KAAK,OAAO,gBAAgB;AAAA,EACzD;AACF;","names":[]}
|
package/dist/index.cjs
CHANGED
|
@@ -297,6 +297,16 @@ function buildFundingRouteCandidates(source, destination, preferred, registry, o
|
|
|
297
297
|
};
|
|
298
298
|
if (preferred.kind === "DIRECT") {
|
|
299
299
|
add("DIRECT", destination);
|
|
300
|
+
if (sourceChain === "btc" || sourceChain === "zec") {
|
|
301
|
+
for (const symbol of ["USDC", "USDT"]) {
|
|
302
|
+
add(
|
|
303
|
+
"CROSS_CHAIN_SWAP",
|
|
304
|
+
tokens.find(
|
|
305
|
+
(token) => normalizeChain(token.blockchain) === "near" && token.symbol.toUpperCase() === symbol
|
|
306
|
+
)
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
300
310
|
for (const mid of midCandidates()) add(routeKindFor(mid), mid);
|
|
301
311
|
return routes;
|
|
302
312
|
}
|
|
@@ -1320,8 +1330,8 @@ var ConfidentialSwapClient = class {
|
|
|
1320
1330
|
* re-quote funding while keeping the same plan id and recipient splits.
|
|
1321
1331
|
*/
|
|
1322
1332
|
async ensureFreshFundingQuotes(plan, signal) {
|
|
1323
|
-
if (this.now() < plan.expiresAt) return plan;
|
|
1324
1333
|
if (plan.request.fundingSource === "balance") {
|
|
1334
|
+
if (this.now() < plan.expiresAt) return plan;
|
|
1325
1335
|
throw new ConfidentialSwapError("QUOTE_EXPIRED", "Obtain a fresh quote before funding.");
|
|
1326
1336
|
}
|
|
1327
1337
|
const refreshed = await refreshWalletFundingQuotes(plan, this.quoteDeps(), signal);
|