@pafi-dev/trading 0.3.1 → 0.3.2
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/index.cjs +17 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -2
- package/dist/index.d.ts +7 -2
- package/dist/index.js +17 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -444,8 +444,18 @@ var TradingHandlers = class {
|
|
|
444
444
|
* holds exactly `amountIn` of input; receives `outputGross - fee`
|
|
445
445
|
* net. Quote response surfaces both `estimatedOutputAmount` (gross)
|
|
446
446
|
* and `outputNet` so the FE can display reality.
|
|
447
|
+
*
|
|
448
|
+
* v0.3.1 — `authenticatedAddress` first param. Caller (issuer
|
|
449
|
+
* controller / FE proxy) MUST pass the address extracted from the
|
|
450
|
+
* verified session/JWT. Handler asserts it equals
|
|
451
|
+
* `request.userAddress`. See SDK_CORE_TRADING_AUDIT.md C6.
|
|
447
452
|
*/
|
|
448
|
-
async handleSwap(request) {
|
|
453
|
+
async handleSwap(authenticatedAddress, request) {
|
|
454
|
+
if ((0, import_viem4.getAddress)(authenticatedAddress) !== (0, import_viem4.getAddress)(request.userAddress)) {
|
|
455
|
+
throw new Error(
|
|
456
|
+
`handleSwap: authenticatedAddress (${authenticatedAddress}) does not match request.userAddress (${request.userAddress})`
|
|
457
|
+
);
|
|
458
|
+
}
|
|
449
459
|
if (request.chainId !== this.chainId) {
|
|
450
460
|
throw new Error(`handleSwap: unsupported chainId ${request.chainId}`);
|
|
451
461
|
}
|
|
@@ -549,7 +559,12 @@ var TradingHandlers = class {
|
|
|
549
559
|
* `getContractAddresses(chainId).orderlyRelay` is the placeholder
|
|
550
560
|
* sentinel (Relay not deployed for that chain).
|
|
551
561
|
*/
|
|
552
|
-
async handlePerpDeposit(request) {
|
|
562
|
+
async handlePerpDeposit(authenticatedAddress, request) {
|
|
563
|
+
if ((0, import_viem4.getAddress)(authenticatedAddress) !== (0, import_viem4.getAddress)(request.userAddress)) {
|
|
564
|
+
throw new Error(
|
|
565
|
+
`handlePerpDeposit: authenticatedAddress (${authenticatedAddress}) does not match request.userAddress (${request.userAddress})`
|
|
566
|
+
);
|
|
567
|
+
}
|
|
553
568
|
if (request.chainId !== this.chainId) {
|
|
554
569
|
throw new Error(`handlePerpDeposit: unsupported chainId ${request.chainId}`);
|
|
555
570
|
}
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/api/handlers.ts","../src/quoting/routes.ts","../src/quoting/quote.ts","../src/swap/approval.ts","../src/swap/universalRouter.ts","../src/swap/simulate.ts","../src/swap/buildSwap.ts","../src/pools.ts"],"sourcesContent":["export { TradingHandlers } from \"./api/handlers\";\nexport type { TradingHandlersConfig } from \"./api/handlers\";\n\nexport type {\n ApiQuoteRequest,\n ApiQuoteResponse,\n ApiQuoteError,\n ApiSwapRequest,\n ApiSwapResponse,\n ApiPerpDepositRequest,\n ApiPerpDepositResponse,\n} from \"./api/types\";\n\nexport { fetchPafiPools, PAFI_SUBGRAPH_URL } from \"./pools\";\n\n// v1.6 — Generalized swap UserOp builder + V4 quoting (moved from\n// @pafi-dev/core 2026-04-27). Exposed as building blocks for callers\n// that compose swap flows beyond what `TradingHandlers` provides.\nexport {\n buildSwapUserOp,\n type BuildSwapUserOpParams,\n buildUniversalRouterExecuteArgs,\n buildV4SwapInput,\n buildSwapFromQuote,\n buildPermit2ApprovalCalldata,\n buildErc20ApprovalCalldata,\n checkAllowance,\n simulateSwap,\n type SwapSimulationResult,\n} from \"./swap\";\n\nexport {\n findBestQuote,\n quoteBestRoute,\n quoteExactInput,\n quoteExactInputSingle,\n buildAllPaths,\n combineRoutes,\n} from \"./quoting\";\n","import { getAddress } from \"viem\";\nimport type { Address, PublicClient } from \"viem\";\nimport {\n buildPerpDepositWithGasDeduction,\n buildPerpDepositViaRelay,\n ORDERLY_RELAY_ABI,\n getContractAddresses,\n UNIVERSAL_ROUTER_ADDRESSES,\n ORDERLY_VAULT_ABI,\n ORDERLY_VAULT_ADDRESSES,\n BROKER_HASHES,\n TOKEN_HASHES,\n computeAccountId,\n quoteOperatorFeePt,\n quoteOperatorFeeUsdt,\n} from \"@pafi-dev/core\";\nimport { findBestQuote } from \"../quoting\";\nimport { buildSwapUserOp } from \"../swap\";\nimport type {\n ApiQuoteRequest,\n ApiQuoteResponse,\n ApiSwapRequest,\n ApiSwapResponse,\n ApiPerpDepositRequest,\n ApiPerpDepositResponse,\n} from \"./types\";\n\nexport interface TradingHandlersConfig {\n provider: PublicClient;\n chainId: number;\n}\n\n/**\n * Framework-agnostic handlers for on-chain trading actions.\n *\n * All handlers are stateless — they need only a PublicClient for RPC\n * calls. No ledger, no signer, no DB. Issuers wrap these in their own\n * HTTP controllers (Express / NestJS / Hono / etc.) the same way they\n * wrap `IssuerApiHandlers` from `@pafi-dev/issuer`.\n *\n * Example (NestJS):\n *\n * const trading = new TradingHandlers({ provider, chainId });\n *\n * // GET /quote\n * const quote = await trading.handleQuote({ chainId, pointTokenAddress, amount, pools });\n *\n * // POST /swap\n * const swap = await trading.handleSwap({ chainId, userAddress, pointTokenAddress, amount, aaNonce });\n *\n * // POST /perp-deposit\n * const deposit = await trading.handlePerpDeposit({ chainId, userAddress, amount, aaNonce, brokerId });\n */\nexport class TradingHandlers {\n private readonly provider: PublicClient;\n private readonly chainId: number;\n\n constructor(config: TradingHandlersConfig) {\n this.provider = config.provider;\n this.chainId = config.chainId;\n }\n\n // =========================================================================\n // GET /quote\n // =========================================================================\n\n /**\n * Quote exact-input PT → USDT via Uniswap V4 on-chain Quoter.\n *\n * Uses multicall to batch all candidate routes into a single RPC call.\n * Returns `quoteError: \"QUOTE_UNAVAILABLE\"` when no pool/path exists\n * rather than throwing, so callers can show a soft \"unavailable\" UI\n * state without 500-ing.\n */\n async handleQuote(request: ApiQuoteRequest): Promise<ApiQuoteResponse> {\n if (request.chainId !== this.chainId) {\n throw new Error(`handleQuote: unsupported chainId ${request.chainId}`);\n }\n if (request.amount === 0n) {\n return {\n inputAmount: 0n,\n estimatedOutputAmount: 0n,\n outputNet: 0n,\n feeAmountOutput: 0n,\n gasEstimate: 0n,\n };\n }\n\n const inputTokenAddress = getAddress(request.inputTokenAddress);\n const outputTokenAddress = getAddress(request.outputTokenAddress);\n const pools = request.pools ?? [];\n\n try {\n const best = await findBestQuote(\n this.provider,\n request.chainId,\n inputTokenAddress,\n outputTokenAddress,\n request.amount,\n pools,\n );\n const feeAmountOutput = await quoteOperatorFeeOutput(\n this.provider,\n request.chainId,\n outputTokenAddress,\n ).catch(() => 0n);\n const outputNet =\n best.bestRoute.amountOut > feeAmountOutput\n ? best.bestRoute.amountOut - feeAmountOutput\n : 0n;\n return {\n inputAmount: request.amount,\n estimatedOutputAmount: best.bestRoute.amountOut,\n outputNet,\n feeAmountOutput,\n gasEstimate: best.bestRoute.gasEstimate,\n };\n } catch {\n return {\n inputAmount: request.amount,\n estimatedOutputAmount: 0n,\n outputNet: 0n,\n feeAmountOutput: 0n,\n gasEstimate: 0n,\n quoteError: \"QUOTE_UNAVAILABLE\",\n };\n }\n }\n\n // =========================================================================\n // POST /swap\n // =========================================================================\n\n /**\n * Build a swap UserOp (direction-agnostic).\n *\n * Quotes the best route, applies slippage, then encodes a 4-step\n * batch: input.approve → Permit2.approve → UniversalRouter.execute →\n * output.transfer (fee in OUTPUT token, omitted when fee = 0).\n *\n * v0.3 — Fee model is OUTPUT-side (token-availability rule). User\n * holds exactly `amountIn` of input; receives `outputGross - fee`\n * net. Quote response surfaces both `estimatedOutputAmount` (gross)\n * and `outputNet` so the FE can display reality.\n */\n async handleSwap(request: ApiSwapRequest): Promise<ApiSwapResponse> {\n if (request.chainId !== this.chainId) {\n throw new Error(`handleSwap: unsupported chainId ${request.chainId}`);\n }\n if (request.amount <= 0n) {\n throw new Error(\"handleSwap: amount must be positive\");\n }\n\n const { pafiFeeRecipient } = getContractAddresses(request.chainId);\n const universalRouter = UNIVERSAL_ROUTER_ADDRESSES[request.chainId];\n if (!universalRouter) {\n throw new Error(`handleSwap: no UniversalRouter for chainId ${request.chainId}`);\n }\n\n const inputTokenAddress = getAddress(request.inputTokenAddress);\n const outputTokenAddress = getAddress(request.outputTokenAddress);\n const userAddress = getAddress(request.userAddress);\n const pools = request.pools ?? [];\n\n let quoteResult: Awaited<ReturnType<typeof findBestQuote>>;\n try {\n quoteResult = await findBestQuote(\n this.provider,\n request.chainId,\n inputTokenAddress,\n outputTokenAddress,\n request.amount,\n pools,\n );\n } catch {\n throw new Error(\n `handleSwap: no swap path found from ${inputTokenAddress} to ${outputTokenAddress}`,\n );\n }\n\n // Resolve the operator fee in OUTPUT token units. Auto-quote uses\n // the right underlying quoter based on output token (USDT vs PT).\n const gasFeeAmountOutput =\n request.gasFeeAmountOutput !== undefined\n ? request.gasFeeAmountOutput\n : await quoteOperatorFeeOutput(\n this.provider,\n request.chainId,\n outputTokenAddress,\n ).catch(() => 0n);\n\n const hops = quoteResult.bestRoute.path.length;\n // Multi-hop routes compound slippage across legs — bias higher when\n // caller didn't override. 50 bps single-hop, 100 bps multi-hop.\n const slippageBps =\n request.slippageBps ?? (hops > 1 ? 100 : 50);\n\n const estimatedOutputAmount = quoteResult.bestRoute.amountOut;\n const minAmountOut = (estimatedOutputAmount * BigInt(10000 - slippageBps)) / 10000n;\n\n if (gasFeeAmountOutput > 0n && minAmountOut < gasFeeAmountOutput) {\n throw new Error(\n `handleSwap: minAmountOut (${minAmountOut}) below operator fee (${gasFeeAmountOutput}) — increase swap amount or accept tighter slippage`,\n );\n }\n\n const deadline = BigInt(Math.floor(Date.now() / 1000) + 5 * 60);\n\n const userOp = buildSwapUserOp({\n userAddress,\n aaNonce: request.aaNonce,\n inputTokenAddress,\n outputTokenAddress,\n universalRouterAddress: universalRouter,\n amountIn: request.amount,\n minAmountOut,\n swapPath: quoteResult.bestRoute.path,\n deadline,\n gasFeeAmountOutput,\n feeRecipient: pafiFeeRecipient,\n });\n\n // Fee-stripped fallback for paymaster-refused path.\n const userOpFallback =\n gasFeeAmountOutput > 0n\n ? buildSwapUserOp({\n userAddress,\n aaNonce: request.aaNonce,\n inputTokenAddress,\n outputTokenAddress,\n universalRouterAddress: universalRouter,\n amountIn: request.amount,\n minAmountOut,\n swapPath: quoteResult.bestRoute.path,\n deadline,\n gasFeeAmountOutput: 0n,\n feeRecipient: pafiFeeRecipient,\n })\n : undefined;\n\n return {\n userOp,\n userOpFallback,\n estimatedOutputAmount,\n minAmountOut,\n hops,\n deadline,\n feeAmountUsed: gasFeeAmountOutput,\n feeRecipient: pafiFeeRecipient,\n };\n }\n\n // =========================================================================\n // POST /perp-deposit\n // =========================================================================\n\n /**\n * Build an Orderly perp deposit UserOp.\n *\n * Default path is the **PAFI Orderly Relay** (`viaRelay: true`):\n * USDC.approve(relay) + relay.deposit(req). The Relay holds an ETH\n * reserve and pays Orderly's LayerZero `msg.value` out of it; the\n * user pays a USDC fee (quoted via `Relay.quoteTokenFee`) instead.\n * No native ETH on the user wallet is required, so paymaster\n * sponsorship of the ERC-4337 gas is sufficient end-to-end.\n *\n * Fallback path (`viaRelay: false`): direct `Vault.deposit{value}`.\n * Reserved for chains where no Relay is deployed — the user wallet\n * **must** hold `layerZeroFee` as native ETH.\n *\n * The Relay path automatically falls back to Vault when\n * `getContractAddresses(chainId).orderlyRelay` is the placeholder\n * sentinel (Relay not deployed for that chain).\n */\n async handlePerpDeposit(request: ApiPerpDepositRequest): Promise<ApiPerpDepositResponse> {\n if (request.chainId !== this.chainId) {\n throw new Error(`handlePerpDeposit: unsupported chainId ${request.chainId}`);\n }\n if (request.amount <= 0n) {\n throw new Error(\"handlePerpDeposit: amount must be positive\");\n }\n\n const vault = ORDERLY_VAULT_ADDRESSES[request.chainId];\n if (!vault) {\n throw new Error(`handlePerpDeposit: no Orderly Vault for chainId ${request.chainId}`);\n }\n\n const brokerHash = BROKER_HASHES[request.brokerId as keyof typeof BROKER_HASHES];\n if (!brokerHash) {\n throw new Error(`handlePerpDeposit: unknown brokerId \"${request.brokerId}\"`);\n }\n const tokenHash = TOKEN_HASHES.USDC;\n const userAddress = getAddress(request.userAddress);\n\n const [usdcAddress, brokerAllowed] = await Promise.all([\n this.provider.readContract({\n address: vault,\n abi: ORDERLY_VAULT_ABI,\n functionName: \"getAllowedToken\",\n args: [tokenHash],\n }) as Promise<Address>,\n this.provider.readContract({\n address: vault,\n abi: ORDERLY_VAULT_ABI,\n functionName: \"getAllowedBroker\",\n args: [brokerHash],\n }) as Promise<boolean>,\n ]);\n\n if (!brokerAllowed) {\n throw new Error(\n `handlePerpDeposit: broker \"${request.brokerId}\" is not whitelisted on Orderly Vault`,\n );\n }\n\n const accountId = computeAccountId(userAddress, brokerHash);\n const depositData = {\n accountId,\n brokerHash,\n tokenHash,\n tokenAmount: request.amount,\n };\n\n // Always read layerZeroFee for response — even on the Relay path\n // it's useful informational output (lets the FE show \"Relay\n // covers ~X ETH for you\").\n const layerZeroFee = (await this.provider.readContract({\n address: vault,\n abi: ORDERLY_VAULT_ABI,\n functionName: \"getDepositFee\",\n args: [userAddress, depositData],\n })) as bigint;\n\n const useRelay = request.viaRelay !== false;\n const { orderlyRelay: relayAddress, pafiFeeRecipient } =\n getContractAddresses(request.chainId);\n const relayDeployed = !isPlaceholderAddress(relayAddress);\n\n // Resolve operator fee in USDC (input-token). Only applied to the\n // Relay path; the legacy direct-Vault path skips operator fee\n // entirely (it's already gas-heavy due to the user-paid\n // LayerZero msg.value, and PAFI doesn't sponsor that path on Base\n // anyway).\n const gasFeeUsdc =\n request.gasFeeUsdc !== undefined\n ? request.gasFeeUsdc\n : useRelay && relayDeployed\n ? await quoteOperatorFeeUsdt({\n provider: this.provider,\n chainId: request.chainId,\n }).catch(() => 0n)\n : 0n;\n\n if (useRelay && relayDeployed) {\n // Cap = max(amount * 5%, 2 USDC). The Relay fee is a flat USDC\n // amount derived from the LayerZero ETH cost at oracle price —\n // it does NOT scale with deposit size. A pure percentage cap\n // breaks for small deposits (e.g. 0.01 USDC test deposit, 5%\n // cap = 500 wei < 14k wei real fee). The 2 USDC floor covers\n // normal LayerZero pricing on Base; the 5% slope still guards\n // against oracle spikes on large deposits.\n const RELAY_FEE_FLOOR_USDC = 2_000_000n; // 2 USDC (6 decimals)\n const percentCap = (request.amount * 500n) / 10_000n;\n const maxRelayFee =\n request.maxRelayFee ??\n (percentCap > RELAY_FEE_FLOOR_USDC ? percentCap : RELAY_FEE_FLOOR_USDC);\n\n const relayRequest = {\n token: usdcAddress,\n receiver: userAddress,\n brokerHash,\n totalAmount: request.amount,\n maxFee: maxRelayFee,\n };\n\n const relayTokenFee = (await this.provider.readContract({\n address: relayAddress,\n abi: ORDERLY_RELAY_ABI,\n functionName: \"quoteTokenFee\",\n args: [relayRequest],\n })) as bigint;\n\n if (relayTokenFee > maxRelayFee) {\n throw new Error(\n `handlePerpDeposit: Relay tokenFee ${relayTokenFee} (≈ ${\n Number(relayTokenFee) / 1e6\n } USDC) exceeds maxRelayFee ${maxRelayFee} — pass a larger ` +\n `\\`maxRelayFee\\` or increase the deposit \\`amount\\` so the fee ` +\n `becomes a smaller share of the total.`,\n );\n }\n\n // Sanity-check: Relay forwards `(totalAmount − tokenFee)` to\n // Orderly Vault. When `tokenFee >= totalAmount` the forwarded\n // amount is zero / negative and the Relay reverts on-chain with\n // `FeeExceedsAmount(fee, totalAmount)` (selector 0x536766bf),\n // which propagates as an opaque `BatchExecutor.CallFailed(1, …)`\n // revert at simulation time. Catch this early on the client so\n // the UX shows an actionable message instead of an AA21/AA34\n // bundler error wrapped around the raw selector.\n if (relayTokenFee >= request.amount) {\n const feeUsdc = Number(relayTokenFee) / 1e6;\n const amountUsdc = Number(request.amount) / 1e6;\n throw new Error(\n `handlePerpDeposit: deposit amount ${amountUsdc} USDC is below the ` +\n `Relay fee ${feeUsdc} USDC — increase \\`amount\\` to at least ` +\n `${(feeUsdc * 2).toFixed(6)} USDC so a meaningful balance reaches ` +\n `your Orderly account after the Relay charge.`,\n );\n }\n\n // USDC fee transferred FIRST in the batch (input-token rule —\n // user holds USDC at start). Recipient = pafiFeeRecipient.\n const userOp = buildPerpDepositViaRelay({\n userAddress,\n aaNonce: request.aaNonce,\n relayAddress,\n request: relayRequest,\n gasFeeUsdc: gasFeeUsdc > 0n ? gasFeeUsdc : undefined,\n gasFeeUsdcRecipient: gasFeeUsdc > 0n ? pafiFeeRecipient : undefined,\n });\n\n // Same shape, no USDC fee transfer — for the paymaster-refused\n // fallback path. The Relay still charges its own USDC token-fee\n // (compensates LayerZero ETH spend, NOT PAFI's gas).\n const userOpFallback =\n gasFeeUsdc > 0n\n ? buildPerpDepositViaRelay({\n userAddress,\n aaNonce: request.aaNonce,\n relayAddress,\n request: relayRequest,\n })\n : undefined;\n\n return {\n userOp,\n userOpFallback,\n path: \"relay\",\n layerZeroFee,\n relayTokenFee,\n accountId,\n brokerHash,\n usdcAddress,\n relayAddress,\n feeAmountUsed: gasFeeUsdc,\n feeRecipient: pafiFeeRecipient,\n };\n }\n\n // Fallback: direct Vault.deposit{value} — user wallet MUST hold\n // `layerZeroFee` as native ETH (paymaster does not sponsor msg.value).\n const userOp = buildPerpDepositWithGasDeduction({\n userAddress,\n aaNonce: request.aaNonce,\n chainId: request.chainId,\n usdcAddress,\n amount: request.amount,\n depositData,\n layerZeroFee,\n });\n\n return {\n userOp,\n path: \"vault\",\n layerZeroFee,\n relayTokenFee: 0n,\n accountId,\n brokerHash,\n usdcAddress,\n relayAddress: vault,\n // Vault path doesn't include the PT operator fee transfer (it's\n // an unsponsored path on chains without a Relay deployment, and\n // the user is paying msg.value in ETH already). Echo 0n + the\n // canonical recipient so the response shape stays consistent.\n feeAmountUsed: 0n,\n feeRecipient: pafiFeeRecipient,\n };\n }\n}\n\n/**\n * `addresses.ts` uses `0x000…<suffix>` sentinels for chains where a\n * given contract is not yet deployed. Detect them by upper-160-bits =\n * 0 so we route to the Vault fallback automatically.\n */\nfunction isPlaceholderAddress(addr: Address): boolean {\n return /^0x0{36}[0-9a-fA-F]{4}$/i.test(addr);\n}\n\n/**\n * Quote the operator gas fee in OUTPUT token raw units. Picks the right\n * underlying quoter:\n *\n * - Output is USDT (canonical address from `getContractAddresses`):\n * `quoteOperatorFeeUsdt` — pure Chainlink ETH/USD, no V4 hop.\n * - Output is anything else (PT0, PT1, ...): `quoteOperatorFeePt`\n * against the output token's V4 pool.\n *\n * Used by `handleQuote` (display the user-facing net output) and\n * `handleSwap` (auto-quote when caller didn't override\n * `gasFeeAmountOutput`).\n */\nasync function quoteOperatorFeeOutput(\n provider: PublicClient,\n chainId: number,\n outputTokenAddress: Address,\n): Promise<bigint> {\n const { usdt } = getContractAddresses(chainId);\n if (\n usdt &&\n getAddress(outputTokenAddress) === getAddress(usdt as Address)\n ) {\n return quoteOperatorFeeUsdt({ provider, chainId });\n }\n return quoteOperatorFeePt({\n provider,\n chainId,\n pointTokenAddress: outputTokenAddress,\n });\n}\n","import type { Address } from \"viem\";\nimport type { PathKey, PoolKey } from \"@pafi-dev/core\";\nimport { COMMON_POOLS, POINT_TOKEN_POOLS } from \"@pafi-dev/core\";\n\nconst ZERO_ADDRESS = \"0x0000000000000000000000000000000000000000\" as Address;\n\n/**\n * Combine point-token-specific pools and common pools for a given chain.\n * Point token pools are listed first so callers can prioritise them.\n */\nexport function combineRoutes(\n chainId: number,\n pointTokenAddress: Address,\n): PoolKey[] {\n const commonPools = COMMON_POOLS[chainId] ?? [];\n const pointPools = POINT_TOKEN_POOLS[chainId]?.[pointTokenAddress] ?? [];\n return [...pointPools, ...commonPools];\n}\n\n/**\n * Build all possible swap paths from `tokenIn` to `tokenOut` using the given\n * pools. Returns an array of PathKey[] routes (each up to `maxHops` hops).\n *\n * Supports both direct single-hop routes and multi-hop routes through\n * intermediate tokens. Each pool is used at most once per path.\n *\n * @param pools - Available pools to route through\n * @param tokenIn - Input token address\n * @param tokenOut - Desired output token address\n * @param maxHops - Maximum number of hops (default 3)\n */\nexport function buildAllPaths(\n pools: PoolKey[],\n tokenIn: Address,\n tokenOut: Address,\n maxHops = 3,\n): PathKey[][] {\n const results: PathKey[][] = [];\n\n function dfs(\n currentToken: Address,\n currentPath: PathKey[],\n usedPoolIndices: Set<number>,\n ) {\n if (currentPath.length > maxHops) return;\n\n // Check if we've reached the destination\n if (\n currentPath.length > 0 &&\n currentToken.toLowerCase() === tokenOut.toLowerCase()\n ) {\n results.push([...currentPath]);\n return;\n }\n\n for (let i = 0; i < pools.length; i++) {\n if (usedPoolIndices.has(i)) continue;\n\n const pool = pools[i]!;\n const c0 = pool.currency0.toLowerCase();\n const c1 = pool.currency1.toLowerCase();\n const curr = currentToken.toLowerCase();\n\n let nextToken: Address | null = null;\n if (curr === c0) {\n nextToken = pool.currency1 as Address;\n } else if (curr === c1) {\n nextToken = pool.currency0 as Address;\n }\n\n if (!nextToken) continue;\n\n const hop: PathKey = {\n intermediateCurrency: nextToken,\n fee: pool.fee,\n tickSpacing: pool.tickSpacing,\n hooks: pool.hooks ?? ZERO_ADDRESS,\n hookData: \"0x\",\n };\n\n usedPoolIndices.add(i);\n currentPath.push(hop);\n dfs(nextToken, currentPath, usedPoolIndices);\n currentPath.pop();\n usedPoolIndices.delete(i);\n }\n }\n\n dfs(tokenIn, [], new Set());\n return results;\n}\n","import type { Address, PublicClient } from \"viem\";\nimport type { BestQuote, PathKey, PoolKey, QuoteResult } from \"@pafi-dev/core\";\nimport { v4QuoterAbi } from \"@pafi-dev/core\";\nimport { buildAllPaths } from \"./routes\";\nimport { COMMON_POOLS, V4_QUOTER_ADDRESSES } from \"@pafi-dev/core\";\n\n/**\n * Quote exact-input for a multi-hop path.\n */\nexport async function quoteExactInput(\n client: PublicClient,\n quoterAddress: Address,\n exactCurrency: Address,\n path: PathKey[],\n exactAmount: bigint,\n): Promise<QuoteResult> {\n const [amountOut, gasEstimate] = await client.readContract({\n address: quoterAddress,\n abi: v4QuoterAbi,\n functionName: \"quoteExactInput\",\n args: [{ exactCurrency, path, exactAmount: BigInt(exactAmount) as unknown as number }],\n }) as [bigint, bigint];\n\n return { amountOut, gasEstimate, path };\n}\n\n/**\n * Quote exact-input for a single-hop swap, given an explicit PoolKey and direction.\n */\nexport async function quoteExactInputSingle(\n client: PublicClient,\n quoterAddress: Address,\n poolKey: PoolKey,\n zeroForOne: boolean,\n exactAmount: bigint,\n hookData: `0x${string}`,\n): Promise<{ amountOut: bigint; gasEstimate: bigint }> {\n const [amountOut, gasEstimate] = await client.readContract({\n address: quoterAddress,\n abi: v4QuoterAbi,\n functionName: \"quoteExactInputSingle\",\n args: [\n {\n poolKey,\n zeroForOne,\n exactAmount: BigInt(exactAmount) as unknown as number,\n hookData,\n },\n ],\n }) as [bigint, bigint];\n\n return { amountOut, gasEstimate };\n}\n\n/**\n * Try multiple PathKey[] routes and return the best quote plus all results.\n * Routes that fail (e.g. pool does not exist) are silently skipped.\n *\n * Uses viem multicall to batch all quotes into a single RPC call for speed.\n */\nexport async function quoteBestRoute(\n client: PublicClient,\n quoterAddress: Address,\n exactCurrency: Address,\n routes: PathKey[][],\n exactAmount: bigint,\n): Promise<BestQuote> {\n const results = await client.multicall({\n contracts: routes.map((path) => ({\n address: quoterAddress,\n abi: v4QuoterAbi,\n functionName: \"quoteExactInput\" as const,\n args: [\n {\n exactCurrency,\n path,\n exactAmount: BigInt(exactAmount) as unknown as number,\n },\n ],\n })),\n allowFailure: true,\n });\n\n const allRoutes: QuoteResult[] = [];\n for (let i = 0; i < results.length; i++) {\n const r = results[i]!;\n if (r.status === \"success\") {\n const [amountOut, gasEstimate] = r.result as unknown as [bigint, bigint];\n allRoutes.push({ amountOut, gasEstimate, path: routes[i]! });\n }\n }\n\n if (allRoutes.length === 0) {\n throw new Error(\"No valid routes found\");\n }\n\n const bestRoute = allRoutes.reduce((best, current) =>\n current.amountOut > best.amountOut ? current : best,\n );\n\n return { bestRoute, allRoutes };\n}\n\n/**\n * Find and quote the best swap route from `tokenIn` to `tokenOut`.\n *\n * Combines the caller's `pools` with `COMMON_POOLS[chainId]`, builds all\n * possible paths (up to `maxHops`), then quotes them all via a single\n * multicall and returns the best result.\n *\n * @param client - viem PublicClient\n * @param chainId - Chain ID (used to look up COMMON_POOLS and V4_QUOTER_ADDRESSES)\n * @param tokenIn - Input token address\n * @param tokenOut - Desired output token address\n * @param exactAmount - Exact input amount\n * @param pools - Additional pools to consider (e.g. point-token-specific)\n * @param quoterAddress - Override the default V4 Quoter address for this chain\n * @param maxHops - Maximum number of hops per path (default 3)\n */\nexport async function findBestQuote(\n client: PublicClient,\n chainId: number,\n tokenIn: Address,\n tokenOut: Address,\n exactAmount: bigint,\n pools: PoolKey[] = [],\n quoterAddress?: Address,\n maxHops = 3,\n): Promise<BestQuote> {\n const quoter = quoterAddress ?? V4_QUOTER_ADDRESSES[chainId];\n if (!quoter) {\n throw new Error(`No V4 Quoter address configured for chain ${chainId}`);\n }\n\n const commonPools = COMMON_POOLS[chainId] ?? [];\n const allPools = [...pools, ...commonPools];\n const paths = buildAllPaths(allPools, tokenIn, tokenOut, maxHops);\n\n if (paths.length === 0) {\n throw new Error(`No paths found from ${tokenIn} to ${tokenOut}`);\n }\n\n return quoteBestRoute(client, quoter, tokenIn, paths, exactAmount);\n}\n","import { encodeFunctionData } from \"viem\";\nimport type { Address, Hex, PublicClient } from \"viem\";\nimport { erc20Abi } from \"@pafi-dev/core\";\nimport { permit2Abi } from \"@pafi-dev/core\";\n\nexport async function checkAllowance(\n client: PublicClient,\n token: Address,\n owner: Address,\n spender: Address,\n): Promise<bigint> {\n return client.readContract({\n address: token,\n abi: erc20Abi,\n functionName: \"allowance\",\n args: [owner, spender],\n });\n}\n\n/**\n * Encode an ERC-20 approve(spender, amount) call.\n */\nexport function buildErc20ApprovalCalldata(\n spender: Address,\n amount: bigint,\n): Hex {\n return encodeFunctionData({\n abi: erc20Abi,\n functionName: \"approve\",\n args: [spender, amount],\n });\n}\n\n/**\n * Encode a Permit2 approve(token, spender, amount, expiration) call.\n */\nexport function buildPermit2ApprovalCalldata(\n token: Address,\n spender: Address,\n amount: bigint,\n expiration: number,\n): Hex {\n return encodeFunctionData({\n abi: permit2Abi,\n functionName: \"approve\",\n args: [token, spender, amount, expiration],\n });\n}\n","import { encodeAbiParameters, encodePacked } from \"viem\";\nimport type { Address, Hex } from \"viem\";\nimport type { PathKey, QuoteResult } from \"@pafi-dev/core\";\n\n// -------------------------------------------------------------------------\n// V4 UniversalRouter command / action constants\n// Reference: https://github.com/Uniswap/v4-periphery/blob/main/src/libraries/Actions.sol\n// -------------------------------------------------------------------------\n\n/** UniversalRouter command byte for V4 swap */\nexport const V4_SWAP = 0x10 as const;\n\n/** V4 actions */\nexport const SWAP_EXACT_IN_SINGLE = 0x06 as const;\nexport const SWAP_EXACT_IN = 0x07 as const;\nexport const SETTLE_ALL = 0x0c as const;\nexport const TAKE_ALL = 0x0f as const;\n\n// -------------------------------------------------------------------------\n// ABI type strings matching Uniswap's V4Planner encoding\n// Reference: https://github.com/Uniswap/sdks/blob/main/sdks/v4-sdk/src/utils/v4Planner.ts\n//\n// IMPORTANT: PathKey.fee is uint256 in the V4 Router ABI (not uint24).\n// -------------------------------------------------------------------------\n\n// PathKey components for V4 Router ABI encoding.\n// IMPORTANT: fee is uint256 in the V4 Router (not uint24 as in PoolKey).\n// Reference: https://github.com/Uniswap/sdks/blob/main/sdks/v4-sdk/src/utils/v4Planner.ts\nconst PATH_KEY_ABI_COMPONENTS = [\n { name: \"intermediateCurrency\", type: \"address\" },\n { name: \"fee\", type: \"uint256\" },\n { name: \"tickSpacing\", type: \"int24\" },\n { name: \"hooks\", type: \"address\" },\n { name: \"hookData\", type: \"bytes\" },\n] as const;\n\nconst EXACT_INPUT_PARAMS_ABI = [\n { name: \"currencyIn\", type: \"address\" },\n {\n name: \"path\",\n type: \"tuple[]\",\n components: PATH_KEY_ABI_COMPONENTS,\n },\n { name: \"amountIn\", type: \"uint128\" },\n { name: \"amountOutMinimum\", type: \"uint128\" },\n] as const;\n\n/**\n * Build the calldata inputs[0] (the V4_SWAP command payload) for\n * UniversalRouter.execute.\n *\n * Actions encoded: SWAP_EXACT_IN → SETTLE_ALL → TAKE_ALL\n *\n * Encoding matches the Uniswap V4 SDK's V4Planner — each action's params\n * are individually ABI-encoded, then wrapped together with the action bytes.\n */\nexport function buildV4SwapInput(\n currencyIn: Address,\n path: PathKey[],\n amountIn: bigint,\n minAmountOut: bigint,\n outputCurrency: Address,\n): Hex {\n const actions = encodePacked(\n [\"uint8\", \"uint8\", \"uint8\"],\n [SWAP_EXACT_IN, SETTLE_ALL, TAKE_ALL],\n );\n\n // Param 0: ExactInputParams — encoded as a single tuple so the CalldataDecoder\n // can locate it via a single offset pointer. fee is uint256 per V4 Router spec.\n const swapParam = encodeAbiParameters(\n [{ name: \"swap\", type: \"tuple\", components: EXACT_INPUT_PARAMS_ABI }],\n [\n {\n currencyIn,\n path: path.map((p) => ({\n intermediateCurrency: p.intermediateCurrency,\n fee: BigInt(p.fee),\n tickSpacing: p.tickSpacing,\n hooks: p.hooks,\n hookData: p.hookData,\n })),\n amountIn,\n amountOutMinimum: minAmountOut,\n },\n ],\n );\n\n // Param 1: SETTLE_ALL { currency, maxAmount }\n const settleParam = encodeAbiParameters(\n [\n { name: \"currency\", type: \"address\" },\n { name: \"maxAmount\", type: \"uint256\" },\n ],\n [currencyIn, amountIn],\n );\n\n // Param 2: TAKE_ALL { currency, minAmount }\n const takeParam = encodeAbiParameters(\n [\n { name: \"currency\", type: \"address\" },\n { name: \"minAmount\", type: \"uint256\" },\n ],\n [outputCurrency, minAmountOut],\n );\n\n return encodeAbiParameters(\n [\n { name: \"actions\", type: \"bytes\" },\n { name: \"params\", type: \"bytes[]\" },\n ],\n [actions, [swapParam, settleParam, takeParam]],\n );\n}\n\n/**\n * Build the full commands + inputs args for UniversalRouter.execute.\n */\nexport function buildUniversalRouterExecuteArgs(\n currencyIn: Address,\n path: PathKey[],\n amountIn: bigint,\n minAmountOut: bigint,\n outputCurrency: Address,\n): { commands: Hex; inputs: Hex[] } {\n const commands = encodePacked([\"uint8\"], [V4_SWAP]);\n const inputs: Hex[] = [\n buildV4SwapInput(currencyIn, path, amountIn, minAmountOut, outputCurrency),\n ];\n return { commands, inputs };\n}\n\n/**\n * Build UniversalRouter execute args from a quote result.\n *\n * Takes the output of `findBestQuote` / `quoteBestRoute` and produces\n * ready-to-use `{ commands, inputs }` for `UniversalRouter.execute`.\n *\n * @param quote - Quote result containing the path\n * @param currencyIn - Input token address\n * @param currencyOut - Output token address\n * @param amountIn - Exact input amount (same value passed to the quoter)\n * @param minAmountOut - Minimum acceptable output (caller applies slippage)\n *\n * @deprecated Since v1.4 — the Issuer App no longer handles swaps.\n * For the new PT→USDT batch call on PAFI Web (Scenario 4 with\n * EIP-7702 gas deduction), use `buildSwapWithGasDeduction()` (v1.5).\n * This helper is kept for legacy v0.2.x consumers; will be removed in v2.0.\n */\nexport function buildSwapFromQuote(params: {\n quote: QuoteResult;\n currencyIn: Address;\n currencyOut: Address;\n amountIn: bigint;\n minAmountOut: bigint;\n}): { commands: Hex; inputs: Hex[] } {\n return buildUniversalRouterExecuteArgs(\n params.currencyIn,\n params.quote.path,\n params.amountIn,\n params.minAmountOut,\n params.currencyOut,\n );\n}\n","import type { Address, Hex, PublicClient } from \"viem\";\nimport { universalRouterAbi } from \"@pafi-dev/core\";\nimport { SimulationError } from \"@pafi-dev/core\";\n\nexport interface SwapSimulationResult {\n success: boolean;\n gasEstimate: bigint;\n}\n\n/**\n * Simulate a UniversalRouter.execute swap call via eth_call (no gas spent).\n *\n * Runs the full V4 swap flow — token transfer via Permit2, swap via\n * PoolManager, output settlement — without submitting a transaction.\n * If the simulation reverts, throws a `SimulationError` with the reason.\n *\n * @param client - viem PublicClient\n * @param routerAddress - UniversalRouter contract address\n * @param commands - Packed command bytes (from buildSwapFromQuote)\n * @param inputs - Encoded inputs per command (from buildSwapFromQuote)\n * @param deadline - Unix timestamp after which the tx expires\n * @param from - Address that will execute the swap\n */\nexport async function simulateSwap(\n client: PublicClient,\n routerAddress: Address,\n commands: Hex,\n inputs: Hex[],\n deadline: bigint,\n from: Address,\n): Promise<SwapSimulationResult> {\n try {\n const gasEstimate = await client.estimateContractGas({\n address: routerAddress,\n abi: universalRouterAbi,\n functionName: \"execute\",\n args: [commands, inputs, deadline],\n account: from,\n });\n\n return { success: true, gasEstimate };\n } catch (error: unknown) {\n const message =\n error instanceof Error ? error.message : \"Unknown simulation error\";\n throw new SimulationError(\"swap\", message);\n }\n}\n","import { encodeFunctionData } from \"viem\";\nimport type { Address, Hex } from \"viem\";\nimport {\n PERMIT2_ADDRESS,\n buildPartialUserOperation,\n erc20ApproveOp,\n erc20TransferOp,\n rawCallOp,\n universalRouterAbi,\n type Operation,\n type PartialUserOperation,\n type PathKey,\n} from \"@pafi-dev/core\";\nimport { buildUniversalRouterExecuteArgs } from \"./universalRouter\";\nimport { buildPermit2ApprovalCalldata } from \"./approval\";\n\n/**\n * v0.3 — Generalized swap UserOp builder. Direction-agnostic: works\n * for **any** ERC-20 → ERC-20 pair routable through PAFI's V4 pools:\n *\n * - PT → USDT (cashout)\n * - USDT → PT (buy PT with USDT)\n * - PT0 → PT1 (same-issuer multi-token swap; routes via USDT)\n *\n * UserOp shape (atomic batch via EIP-7702 BatchExecutor):\n *\n * 1. `inputToken.approve(Permit2, amountIn)` — exactly the swap amount.\n * 2. `Permit2.approve(inputToken, router, amountIn, deadline)` — Permit2\n * authorization to UniversalRouter.\n * 3. `UniversalRouter.execute(commands, inputs, deadline)` — V4 swap\n * `inputToken → outputToken`; user receives ≥ `minAmountOut`.\n * 4. `outputToken.transfer(feeRecipient, gasFeeAmountOutput)` —\n * operator gas reimbursement, paid in OUTPUT token (omitted when 0).\n *\n * ## Fee model — output-token strategy (token-availability rule)\n *\n * Fee is charged in the **output token** AFTER the swap, because the\n * user holds output token at that point in the batch. Three benefits:\n *\n * - User holds exactly `amountIn` of input token (FE quote = chain\n * reality). No \"you need 1010 to swap 1000\" UX surprise.\n * - Quote API returns `outputNet = outputGross - feeOutput` directly,\n * matching what user actually receives.\n * - PT → USDT: PAFI receives USDT (universally liquid, clean accounting).\n *\n * Caller MUST ensure `minAmountOut >= gasFeeAmountOutput` (fee transfer\n * reverts otherwise → whole batch reverts).\n *\n * ## PAFI Hook fee\n *\n * The V4 PAFIHook charges 10% on PT → USDT direction (one-way). The\n * 10% is taken at pool level inside `UniversalRouter.execute`, so this\n * builder doesn't model it explicitly — it shows up as a smaller\n * `amountOut` from `findBestQuote`.\n *\n * - PT → USDT: pool charges 10% (output reduced)\n * - USDT → PT: no hook fee\n * - PT0 → PT1: 10% on PT0 → USDT leg, 0% on USDT → PT1 leg\n */\nexport interface BuildSwapUserOpParams {\n /** User's EOA (with EIP-7702 delegation to BatchExecutor). */\n userAddress: Address;\n /** ERC-4337 account nonce — fetched from EntryPoint by the caller. */\n aaNonce: bigint;\n\n /** Token being spent — approved to Permit2 + UniversalRouter. */\n inputTokenAddress: Address;\n /** Token user receives. */\n outputTokenAddress: Address;\n /** UniversalRouter contract address (chain-specific). */\n universalRouterAddress: Address;\n\n /** Input token units to swap. User must hold exactly this much input token. */\n amountIn: bigint;\n /**\n * Minimum output (gross) to accept from the swap. Caller applies\n * slippage against a fresh quote. MUST be ≥ `gasFeeAmountOutput` or\n * the post-swap fee transfer reverts.\n */\n minAmountOut: bigint;\n\n /**\n * V4 pool path for the swap. Single-hop = 1 PathKey, multi-hop = N.\n * Get this from `findBestQuote().bestRoute.path`.\n */\n swapPath: PathKey[];\n /** Unix seconds. After this, the router rejects the swap. */\n deadline: bigint;\n\n /**\n * Operator gas-reimbursement fee — paid in OUTPUT token. Omitted\n * from the batch when 0n. Caller quotes this via `quoteOperatorFee*`\n * helpers (USDT/PT depending on output token).\n *\n * v0.3 — switched from input-side fee (v0.2 `gasFeeAmount`) to\n * output-side fee. See \"Fee model\" comment above.\n */\n gasFeeAmountOutput: bigint;\n /**\n * Where the gas fee lands — typically the canonical PAFI fee\n * recipient from `getContractAddresses(chainId).pafiFeeRecipient`.\n */\n feeRecipient: Address;\n\n /** Override ERC-4337 gas estimates. Defaults are conservative. */\n gasLimits?: {\n callGasLimit?: bigint;\n verificationGasLimit?: bigint;\n preVerificationGas?: bigint;\n };\n}\n\n/**\n * Build an unsigned UserOp for the generalized swap flow.\n *\n * @throws when `amountIn` is non-positive, `gasFeeAmountOutput` is\n * negative, `swapPath` is empty, or `minAmountOut < gasFeeAmountOutput`.\n */\nexport function buildSwapUserOp(\n params: BuildSwapUserOpParams,\n): PartialUserOperation {\n if (params.amountIn <= 0n) {\n throw new Error(\"buildSwapUserOp: amountIn must be positive\");\n }\n if (params.minAmountOut < 0n) {\n throw new Error(\"buildSwapUserOp: minAmountOut must be non-negative\");\n }\n if (params.gasFeeAmountOutput < 0n) {\n throw new Error(\"buildSwapUserOp: gasFeeAmountOutput must be non-negative\");\n }\n if (\n params.gasFeeAmountOutput > 0n &&\n params.minAmountOut < params.gasFeeAmountOutput\n ) {\n throw new Error(\n \"buildSwapUserOp: minAmountOut must be >= gasFeeAmountOutput so the post-swap fee transfer cannot revert\",\n );\n }\n if (params.swapPath.length === 0) {\n throw new Error(\n \"buildSwapUserOp: swapPath must contain at least one PathKey\",\n );\n }\n\n const { commands, inputs } = buildUniversalRouterExecuteArgs(\n params.inputTokenAddress,\n params.swapPath,\n params.amountIn,\n params.minAmountOut,\n params.outputTokenAddress,\n );\n\n const swapCallData: Hex = encodeFunctionData({\n abi: universalRouterAbi,\n functionName: \"execute\",\n args: [commands, inputs, params.deadline],\n });\n\n // Approve only the swap amount — fee comes out of swap proceeds.\n const permit2ApproveData: Hex = buildPermit2ApprovalCalldata(\n params.inputTokenAddress,\n params.universalRouterAddress,\n params.amountIn,\n Number(params.deadline),\n );\n\n const operations: Operation[] = [\n erc20ApproveOp(params.inputTokenAddress, PERMIT2_ADDRESS, params.amountIn),\n rawCallOp(PERMIT2_ADDRESS, permit2ApproveData),\n rawCallOp(params.universalRouterAddress, swapCallData),\n ];\n\n // Fee transfer in OUTPUT token, AFTER swap, when user holds output.\n if (params.gasFeeAmountOutput > 0n) {\n operations.push(\n erc20TransferOp(\n params.outputTokenAddress,\n params.feeRecipient,\n params.gasFeeAmountOutput,\n ),\n );\n }\n\n return buildPartialUserOperation({\n sender: params.userAddress,\n nonce: params.aaNonce,\n operations,\n gasLimits: {\n callGasLimit: params.gasLimits?.callGasLimit ?? 700_000n,\n verificationGasLimit: params.gasLimits?.verificationGasLimit ?? 150_000n,\n preVerificationGas: params.gasLimits?.preVerificationGas ?? 50_000n,\n },\n });\n}\n","// Re-export from @pafi-dev/core — fetchPafiPools lives in core so all\n// SDK packages share one implementation.\nexport { fetchPafiPools, PAFI_SUBGRAPH_URL } from \"@pafi-dev/core\";\n"],"mappings":"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","names":["import_viem","import_core","import_core","import_core","import_core","import_viem","import_core","import_viem","import_core","userOp","import_core"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/api/handlers.ts","../src/quoting/routes.ts","../src/quoting/quote.ts","../src/swap/approval.ts","../src/swap/universalRouter.ts","../src/swap/simulate.ts","../src/swap/buildSwap.ts","../src/pools.ts"],"sourcesContent":["export { TradingHandlers } from \"./api/handlers\";\nexport type { TradingHandlersConfig } from \"./api/handlers\";\n\nexport type {\n ApiQuoteRequest,\n ApiQuoteResponse,\n ApiQuoteError,\n ApiSwapRequest,\n ApiSwapResponse,\n ApiPerpDepositRequest,\n ApiPerpDepositResponse,\n} from \"./api/types\";\n\nexport { fetchPafiPools, PAFI_SUBGRAPH_URL } from \"./pools\";\n\n// v1.6 — Generalized swap UserOp builder + V4 quoting (moved from\n// @pafi-dev/core 2026-04-27). Exposed as building blocks for callers\n// that compose swap flows beyond what `TradingHandlers` provides.\nexport {\n buildSwapUserOp,\n type BuildSwapUserOpParams,\n buildUniversalRouterExecuteArgs,\n buildV4SwapInput,\n buildSwapFromQuote,\n buildPermit2ApprovalCalldata,\n buildErc20ApprovalCalldata,\n checkAllowance,\n simulateSwap,\n type SwapSimulationResult,\n} from \"./swap\";\n\nexport {\n findBestQuote,\n quoteBestRoute,\n quoteExactInput,\n quoteExactInputSingle,\n buildAllPaths,\n combineRoutes,\n} from \"./quoting\";\n","import { getAddress } from \"viem\";\nimport type { Address, PublicClient } from \"viem\";\nimport {\n buildPerpDepositWithGasDeduction,\n buildPerpDepositViaRelay,\n ORDERLY_RELAY_ABI,\n getContractAddresses,\n UNIVERSAL_ROUTER_ADDRESSES,\n ORDERLY_VAULT_ABI,\n ORDERLY_VAULT_ADDRESSES,\n BROKER_HASHES,\n TOKEN_HASHES,\n computeAccountId,\n quoteOperatorFeePt,\n quoteOperatorFeeUsdt,\n} from \"@pafi-dev/core\";\nimport { findBestQuote } from \"../quoting\";\nimport { buildSwapUserOp } from \"../swap\";\nimport type {\n ApiQuoteRequest,\n ApiQuoteResponse,\n ApiSwapRequest,\n ApiSwapResponse,\n ApiPerpDepositRequest,\n ApiPerpDepositResponse,\n} from \"./types\";\n\nexport interface TradingHandlersConfig {\n provider: PublicClient;\n chainId: number;\n}\n\n/**\n * Framework-agnostic handlers for on-chain trading actions.\n *\n * All handlers are stateless — they need only a PublicClient for RPC\n * calls. No ledger, no signer, no DB. Issuers wrap these in their own\n * HTTP controllers (Express / NestJS / Hono / etc.) the same way they\n * wrap `IssuerApiHandlers` from `@pafi-dev/issuer`.\n *\n * Example (NestJS):\n *\n * const trading = new TradingHandlers({ provider, chainId });\n *\n * // GET /quote\n * const quote = await trading.handleQuote({ chainId, pointTokenAddress, amount, pools });\n *\n * // POST /swap\n * const swap = await trading.handleSwap({ chainId, userAddress, pointTokenAddress, amount, aaNonce });\n *\n * // POST /perp-deposit\n * const deposit = await trading.handlePerpDeposit({ chainId, userAddress, amount, aaNonce, brokerId });\n */\nexport class TradingHandlers {\n private readonly provider: PublicClient;\n private readonly chainId: number;\n\n constructor(config: TradingHandlersConfig) {\n this.provider = config.provider;\n this.chainId = config.chainId;\n }\n\n // =========================================================================\n // GET /quote\n // =========================================================================\n\n /**\n * Quote exact-input PT → USDT via Uniswap V4 on-chain Quoter.\n *\n * Uses multicall to batch all candidate routes into a single RPC call.\n * Returns `quoteError: \"QUOTE_UNAVAILABLE\"` when no pool/path exists\n * rather than throwing, so callers can show a soft \"unavailable\" UI\n * state without 500-ing.\n */\n async handleQuote(request: ApiQuoteRequest): Promise<ApiQuoteResponse> {\n if (request.chainId !== this.chainId) {\n throw new Error(`handleQuote: unsupported chainId ${request.chainId}`);\n }\n if (request.amount === 0n) {\n return {\n inputAmount: 0n,\n estimatedOutputAmount: 0n,\n outputNet: 0n,\n feeAmountOutput: 0n,\n gasEstimate: 0n,\n };\n }\n\n const inputTokenAddress = getAddress(request.inputTokenAddress);\n const outputTokenAddress = getAddress(request.outputTokenAddress);\n const pools = request.pools ?? [];\n\n try {\n const best = await findBestQuote(\n this.provider,\n request.chainId,\n inputTokenAddress,\n outputTokenAddress,\n request.amount,\n pools,\n );\n const feeAmountOutput = await quoteOperatorFeeOutput(\n this.provider,\n request.chainId,\n outputTokenAddress,\n ).catch(() => 0n);\n const outputNet =\n best.bestRoute.amountOut > feeAmountOutput\n ? best.bestRoute.amountOut - feeAmountOutput\n : 0n;\n return {\n inputAmount: request.amount,\n estimatedOutputAmount: best.bestRoute.amountOut,\n outputNet,\n feeAmountOutput,\n gasEstimate: best.bestRoute.gasEstimate,\n };\n } catch {\n return {\n inputAmount: request.amount,\n estimatedOutputAmount: 0n,\n outputNet: 0n,\n feeAmountOutput: 0n,\n gasEstimate: 0n,\n quoteError: \"QUOTE_UNAVAILABLE\",\n };\n }\n }\n\n // =========================================================================\n // POST /swap\n // =========================================================================\n\n /**\n * Build a swap UserOp (direction-agnostic).\n *\n * Quotes the best route, applies slippage, then encodes a 4-step\n * batch: input.approve → Permit2.approve → UniversalRouter.execute →\n * output.transfer (fee in OUTPUT token, omitted when fee = 0).\n *\n * v0.3 — Fee model is OUTPUT-side (token-availability rule). User\n * holds exactly `amountIn` of input; receives `outputGross - fee`\n * net. Quote response surfaces both `estimatedOutputAmount` (gross)\n * and `outputNet` so the FE can display reality.\n *\n * v0.3.1 — `authenticatedAddress` first param. Caller (issuer\n * controller / FE proxy) MUST pass the address extracted from the\n * verified session/JWT. Handler asserts it equals\n * `request.userAddress`. See SDK_CORE_TRADING_AUDIT.md C6.\n */\n async handleSwap(\n authenticatedAddress: Address,\n request: ApiSwapRequest,\n ): Promise<ApiSwapResponse> {\n if (\n getAddress(authenticatedAddress) !== getAddress(request.userAddress)\n ) {\n throw new Error(\n `handleSwap: authenticatedAddress (${authenticatedAddress}) does not match request.userAddress (${request.userAddress})`,\n );\n }\n if (request.chainId !== this.chainId) {\n throw new Error(`handleSwap: unsupported chainId ${request.chainId}`);\n }\n if (request.amount <= 0n) {\n throw new Error(\"handleSwap: amount must be positive\");\n }\n\n const { pafiFeeRecipient } = getContractAddresses(request.chainId);\n const universalRouter = UNIVERSAL_ROUTER_ADDRESSES[request.chainId];\n if (!universalRouter) {\n throw new Error(`handleSwap: no UniversalRouter for chainId ${request.chainId}`);\n }\n\n const inputTokenAddress = getAddress(request.inputTokenAddress);\n const outputTokenAddress = getAddress(request.outputTokenAddress);\n const userAddress = getAddress(request.userAddress);\n const pools = request.pools ?? [];\n\n let quoteResult: Awaited<ReturnType<typeof findBestQuote>>;\n try {\n quoteResult = await findBestQuote(\n this.provider,\n request.chainId,\n inputTokenAddress,\n outputTokenAddress,\n request.amount,\n pools,\n );\n } catch {\n throw new Error(\n `handleSwap: no swap path found from ${inputTokenAddress} to ${outputTokenAddress}`,\n );\n }\n\n // Resolve the operator fee in OUTPUT token units. Auto-quote uses\n // the right underlying quoter based on output token (USDT vs PT).\n const gasFeeAmountOutput =\n request.gasFeeAmountOutput !== undefined\n ? request.gasFeeAmountOutput\n : await quoteOperatorFeeOutput(\n this.provider,\n request.chainId,\n outputTokenAddress,\n ).catch(() => 0n);\n\n const hops = quoteResult.bestRoute.path.length;\n // Multi-hop routes compound slippage across legs — bias higher when\n // caller didn't override. 50 bps single-hop, 100 bps multi-hop.\n const slippageBps =\n request.slippageBps ?? (hops > 1 ? 100 : 50);\n\n const estimatedOutputAmount = quoteResult.bestRoute.amountOut;\n const minAmountOut = (estimatedOutputAmount * BigInt(10000 - slippageBps)) / 10000n;\n\n if (gasFeeAmountOutput > 0n && minAmountOut < gasFeeAmountOutput) {\n throw new Error(\n `handleSwap: minAmountOut (${minAmountOut}) below operator fee (${gasFeeAmountOutput}) — increase swap amount or accept tighter slippage`,\n );\n }\n\n const deadline = BigInt(Math.floor(Date.now() / 1000) + 5 * 60);\n\n const userOp = buildSwapUserOp({\n userAddress,\n aaNonce: request.aaNonce,\n inputTokenAddress,\n outputTokenAddress,\n universalRouterAddress: universalRouter,\n amountIn: request.amount,\n minAmountOut,\n swapPath: quoteResult.bestRoute.path,\n deadline,\n gasFeeAmountOutput,\n feeRecipient: pafiFeeRecipient,\n });\n\n // Fee-stripped fallback for paymaster-refused path.\n const userOpFallback =\n gasFeeAmountOutput > 0n\n ? buildSwapUserOp({\n userAddress,\n aaNonce: request.aaNonce,\n inputTokenAddress,\n outputTokenAddress,\n universalRouterAddress: universalRouter,\n amountIn: request.amount,\n minAmountOut,\n swapPath: quoteResult.bestRoute.path,\n deadline,\n gasFeeAmountOutput: 0n,\n feeRecipient: pafiFeeRecipient,\n })\n : undefined;\n\n return {\n userOp,\n userOpFallback,\n estimatedOutputAmount,\n minAmountOut,\n hops,\n deadline,\n feeAmountUsed: gasFeeAmountOutput,\n feeRecipient: pafiFeeRecipient,\n };\n }\n\n // =========================================================================\n // POST /perp-deposit\n // =========================================================================\n\n /**\n * Build an Orderly perp deposit UserOp.\n *\n * Default path is the **PAFI Orderly Relay** (`viaRelay: true`):\n * USDC.approve(relay) + relay.deposit(req). The Relay holds an ETH\n * reserve and pays Orderly's LayerZero `msg.value` out of it; the\n * user pays a USDC fee (quoted via `Relay.quoteTokenFee`) instead.\n * No native ETH on the user wallet is required, so paymaster\n * sponsorship of the ERC-4337 gas is sufficient end-to-end.\n *\n * Fallback path (`viaRelay: false`): direct `Vault.deposit{value}`.\n * Reserved for chains where no Relay is deployed — the user wallet\n * **must** hold `layerZeroFee` as native ETH.\n *\n * The Relay path automatically falls back to Vault when\n * `getContractAddresses(chainId).orderlyRelay` is the placeholder\n * sentinel (Relay not deployed for that chain).\n */\n async handlePerpDeposit(\n authenticatedAddress: Address,\n request: ApiPerpDepositRequest,\n ): Promise<ApiPerpDepositResponse> {\n // v0.3.1 — auth-context check. See SDK_CORE_TRADING_AUDIT.md C6.\n if (\n getAddress(authenticatedAddress) !== getAddress(request.userAddress)\n ) {\n throw new Error(\n `handlePerpDeposit: authenticatedAddress (${authenticatedAddress}) does not match request.userAddress (${request.userAddress})`,\n );\n }\n if (request.chainId !== this.chainId) {\n throw new Error(`handlePerpDeposit: unsupported chainId ${request.chainId}`);\n }\n if (request.amount <= 0n) {\n throw new Error(\"handlePerpDeposit: amount must be positive\");\n }\n\n const vault = ORDERLY_VAULT_ADDRESSES[request.chainId];\n if (!vault) {\n throw new Error(`handlePerpDeposit: no Orderly Vault for chainId ${request.chainId}`);\n }\n\n const brokerHash = BROKER_HASHES[request.brokerId as keyof typeof BROKER_HASHES];\n if (!brokerHash) {\n throw new Error(`handlePerpDeposit: unknown brokerId \"${request.brokerId}\"`);\n }\n const tokenHash = TOKEN_HASHES.USDC;\n const userAddress = getAddress(request.userAddress);\n\n const [usdcAddress, brokerAllowed] = await Promise.all([\n this.provider.readContract({\n address: vault,\n abi: ORDERLY_VAULT_ABI,\n functionName: \"getAllowedToken\",\n args: [tokenHash],\n }) as Promise<Address>,\n this.provider.readContract({\n address: vault,\n abi: ORDERLY_VAULT_ABI,\n functionName: \"getAllowedBroker\",\n args: [brokerHash],\n }) as Promise<boolean>,\n ]);\n\n if (!brokerAllowed) {\n throw new Error(\n `handlePerpDeposit: broker \"${request.brokerId}\" is not whitelisted on Orderly Vault`,\n );\n }\n\n const accountId = computeAccountId(userAddress, brokerHash);\n const depositData = {\n accountId,\n brokerHash,\n tokenHash,\n tokenAmount: request.amount,\n };\n\n // Always read layerZeroFee for response — even on the Relay path\n // it's useful informational output (lets the FE show \"Relay\n // covers ~X ETH for you\").\n const layerZeroFee = (await this.provider.readContract({\n address: vault,\n abi: ORDERLY_VAULT_ABI,\n functionName: \"getDepositFee\",\n args: [userAddress, depositData],\n })) as bigint;\n\n const useRelay = request.viaRelay !== false;\n const { orderlyRelay: relayAddress, pafiFeeRecipient } =\n getContractAddresses(request.chainId);\n const relayDeployed = !isPlaceholderAddress(relayAddress);\n\n // Resolve operator fee in USDC (input-token). Only applied to the\n // Relay path; the legacy direct-Vault path skips operator fee\n // entirely (it's already gas-heavy due to the user-paid\n // LayerZero msg.value, and PAFI doesn't sponsor that path on Base\n // anyway).\n const gasFeeUsdc =\n request.gasFeeUsdc !== undefined\n ? request.gasFeeUsdc\n : useRelay && relayDeployed\n ? await quoteOperatorFeeUsdt({\n provider: this.provider,\n chainId: request.chainId,\n }).catch(() => 0n)\n : 0n;\n\n if (useRelay && relayDeployed) {\n // Cap = max(amount * 5%, 2 USDC). The Relay fee is a flat USDC\n // amount derived from the LayerZero ETH cost at oracle price —\n // it does NOT scale with deposit size. A pure percentage cap\n // breaks for small deposits (e.g. 0.01 USDC test deposit, 5%\n // cap = 500 wei < 14k wei real fee). The 2 USDC floor covers\n // normal LayerZero pricing on Base; the 5% slope still guards\n // against oracle spikes on large deposits.\n const RELAY_FEE_FLOOR_USDC = 2_000_000n; // 2 USDC (6 decimals)\n const percentCap = (request.amount * 500n) / 10_000n;\n const maxRelayFee =\n request.maxRelayFee ??\n (percentCap > RELAY_FEE_FLOOR_USDC ? percentCap : RELAY_FEE_FLOOR_USDC);\n\n const relayRequest = {\n token: usdcAddress,\n receiver: userAddress,\n brokerHash,\n totalAmount: request.amount,\n maxFee: maxRelayFee,\n };\n\n const relayTokenFee = (await this.provider.readContract({\n address: relayAddress,\n abi: ORDERLY_RELAY_ABI,\n functionName: \"quoteTokenFee\",\n args: [relayRequest],\n })) as bigint;\n\n if (relayTokenFee > maxRelayFee) {\n throw new Error(\n `handlePerpDeposit: Relay tokenFee ${relayTokenFee} (≈ ${\n Number(relayTokenFee) / 1e6\n } USDC) exceeds maxRelayFee ${maxRelayFee} — pass a larger ` +\n `\\`maxRelayFee\\` or increase the deposit \\`amount\\` so the fee ` +\n `becomes a smaller share of the total.`,\n );\n }\n\n // Sanity-check: Relay forwards `(totalAmount − tokenFee)` to\n // Orderly Vault. When `tokenFee >= totalAmount` the forwarded\n // amount is zero / negative and the Relay reverts on-chain with\n // `FeeExceedsAmount(fee, totalAmount)` (selector 0x536766bf),\n // which propagates as an opaque `BatchExecutor.CallFailed(1, …)`\n // revert at simulation time. Catch this early on the client so\n // the UX shows an actionable message instead of an AA21/AA34\n // bundler error wrapped around the raw selector.\n if (relayTokenFee >= request.amount) {\n const feeUsdc = Number(relayTokenFee) / 1e6;\n const amountUsdc = Number(request.amount) / 1e6;\n throw new Error(\n `handlePerpDeposit: deposit amount ${amountUsdc} USDC is below the ` +\n `Relay fee ${feeUsdc} USDC — increase \\`amount\\` to at least ` +\n `${(feeUsdc * 2).toFixed(6)} USDC so a meaningful balance reaches ` +\n `your Orderly account after the Relay charge.`,\n );\n }\n\n // USDC fee transferred FIRST in the batch (input-token rule —\n // user holds USDC at start). Recipient = pafiFeeRecipient.\n const userOp = buildPerpDepositViaRelay({\n userAddress,\n aaNonce: request.aaNonce,\n relayAddress,\n request: relayRequest,\n gasFeeUsdc: gasFeeUsdc > 0n ? gasFeeUsdc : undefined,\n gasFeeUsdcRecipient: gasFeeUsdc > 0n ? pafiFeeRecipient : undefined,\n });\n\n // Same shape, no USDC fee transfer — for the paymaster-refused\n // fallback path. The Relay still charges its own USDC token-fee\n // (compensates LayerZero ETH spend, NOT PAFI's gas).\n const userOpFallback =\n gasFeeUsdc > 0n\n ? buildPerpDepositViaRelay({\n userAddress,\n aaNonce: request.aaNonce,\n relayAddress,\n request: relayRequest,\n })\n : undefined;\n\n return {\n userOp,\n userOpFallback,\n path: \"relay\",\n layerZeroFee,\n relayTokenFee,\n accountId,\n brokerHash,\n usdcAddress,\n relayAddress,\n feeAmountUsed: gasFeeUsdc,\n feeRecipient: pafiFeeRecipient,\n };\n }\n\n // Fallback: direct Vault.deposit{value} — user wallet MUST hold\n // `layerZeroFee` as native ETH (paymaster does not sponsor msg.value).\n const userOp = buildPerpDepositWithGasDeduction({\n userAddress,\n aaNonce: request.aaNonce,\n chainId: request.chainId,\n usdcAddress,\n amount: request.amount,\n depositData,\n layerZeroFee,\n });\n\n return {\n userOp,\n path: \"vault\",\n layerZeroFee,\n relayTokenFee: 0n,\n accountId,\n brokerHash,\n usdcAddress,\n relayAddress: vault,\n // Vault path doesn't include the PT operator fee transfer (it's\n // an unsponsored path on chains without a Relay deployment, and\n // the user is paying msg.value in ETH already). Echo 0n + the\n // canonical recipient so the response shape stays consistent.\n feeAmountUsed: 0n,\n feeRecipient: pafiFeeRecipient,\n };\n }\n}\n\n/**\n * `addresses.ts` uses `0x000…<suffix>` sentinels for chains where a\n * given contract is not yet deployed. Detect them by upper-160-bits =\n * 0 so we route to the Vault fallback automatically.\n */\nfunction isPlaceholderAddress(addr: Address): boolean {\n return /^0x0{36}[0-9a-fA-F]{4}$/i.test(addr);\n}\n\n/**\n * Quote the operator gas fee in OUTPUT token raw units. Picks the right\n * underlying quoter:\n *\n * - Output is USDT (canonical address from `getContractAddresses`):\n * `quoteOperatorFeeUsdt` — pure Chainlink ETH/USD, no V4 hop.\n * - Output is anything else (PT0, PT1, ...): `quoteOperatorFeePt`\n * against the output token's V4 pool.\n *\n * Used by `handleQuote` (display the user-facing net output) and\n * `handleSwap` (auto-quote when caller didn't override\n * `gasFeeAmountOutput`).\n */\nasync function quoteOperatorFeeOutput(\n provider: PublicClient,\n chainId: number,\n outputTokenAddress: Address,\n): Promise<bigint> {\n const { usdt } = getContractAddresses(chainId);\n if (\n usdt &&\n getAddress(outputTokenAddress) === getAddress(usdt as Address)\n ) {\n return quoteOperatorFeeUsdt({ provider, chainId });\n }\n return quoteOperatorFeePt({\n provider,\n chainId,\n pointTokenAddress: outputTokenAddress,\n });\n}\n","import type { Address } from \"viem\";\nimport type { PathKey, PoolKey } from \"@pafi-dev/core\";\nimport { COMMON_POOLS, POINT_TOKEN_POOLS } from \"@pafi-dev/core\";\n\nconst ZERO_ADDRESS = \"0x0000000000000000000000000000000000000000\" as Address;\n\n/**\n * Combine point-token-specific pools and common pools for a given chain.\n * Point token pools are listed first so callers can prioritise them.\n */\nexport function combineRoutes(\n chainId: number,\n pointTokenAddress: Address,\n): PoolKey[] {\n const commonPools = COMMON_POOLS[chainId] ?? [];\n const pointPools = POINT_TOKEN_POOLS[chainId]?.[pointTokenAddress] ?? [];\n return [...pointPools, ...commonPools];\n}\n\n/**\n * Build all possible swap paths from `tokenIn` to `tokenOut` using the given\n * pools. Returns an array of PathKey[] routes (each up to `maxHops` hops).\n *\n * Supports both direct single-hop routes and multi-hop routes through\n * intermediate tokens. Each pool is used at most once per path.\n *\n * @param pools - Available pools to route through\n * @param tokenIn - Input token address\n * @param tokenOut - Desired output token address\n * @param maxHops - Maximum number of hops (default 3)\n */\nexport function buildAllPaths(\n pools: PoolKey[],\n tokenIn: Address,\n tokenOut: Address,\n maxHops = 3,\n): PathKey[][] {\n const results: PathKey[][] = [];\n\n function dfs(\n currentToken: Address,\n currentPath: PathKey[],\n usedPoolIndices: Set<number>,\n ) {\n if (currentPath.length > maxHops) return;\n\n // Check if we've reached the destination\n if (\n currentPath.length > 0 &&\n currentToken.toLowerCase() === tokenOut.toLowerCase()\n ) {\n results.push([...currentPath]);\n return;\n }\n\n for (let i = 0; i < pools.length; i++) {\n if (usedPoolIndices.has(i)) continue;\n\n const pool = pools[i]!;\n const c0 = pool.currency0.toLowerCase();\n const c1 = pool.currency1.toLowerCase();\n const curr = currentToken.toLowerCase();\n\n let nextToken: Address | null = null;\n if (curr === c0) {\n nextToken = pool.currency1 as Address;\n } else if (curr === c1) {\n nextToken = pool.currency0 as Address;\n }\n\n if (!nextToken) continue;\n\n const hop: PathKey = {\n intermediateCurrency: nextToken,\n fee: pool.fee,\n tickSpacing: pool.tickSpacing,\n hooks: pool.hooks ?? ZERO_ADDRESS,\n hookData: \"0x\",\n };\n\n usedPoolIndices.add(i);\n currentPath.push(hop);\n dfs(nextToken, currentPath, usedPoolIndices);\n currentPath.pop();\n usedPoolIndices.delete(i);\n }\n }\n\n dfs(tokenIn, [], new Set());\n return results;\n}\n","import type { Address, PublicClient } from \"viem\";\nimport type { BestQuote, PathKey, PoolKey, QuoteResult } from \"@pafi-dev/core\";\nimport { v4QuoterAbi } from \"@pafi-dev/core\";\nimport { buildAllPaths } from \"./routes\";\nimport { COMMON_POOLS, V4_QUOTER_ADDRESSES } from \"@pafi-dev/core\";\n\n/**\n * Quote exact-input for a multi-hop path.\n */\nexport async function quoteExactInput(\n client: PublicClient,\n quoterAddress: Address,\n exactCurrency: Address,\n path: PathKey[],\n exactAmount: bigint,\n): Promise<QuoteResult> {\n const [amountOut, gasEstimate] = await client.readContract({\n address: quoterAddress,\n abi: v4QuoterAbi,\n functionName: \"quoteExactInput\",\n args: [{ exactCurrency, path, exactAmount: BigInt(exactAmount) as unknown as number }],\n }) as [bigint, bigint];\n\n return { amountOut, gasEstimate, path };\n}\n\n/**\n * Quote exact-input for a single-hop swap, given an explicit PoolKey and direction.\n */\nexport async function quoteExactInputSingle(\n client: PublicClient,\n quoterAddress: Address,\n poolKey: PoolKey,\n zeroForOne: boolean,\n exactAmount: bigint,\n hookData: `0x${string}`,\n): Promise<{ amountOut: bigint; gasEstimate: bigint }> {\n const [amountOut, gasEstimate] = await client.readContract({\n address: quoterAddress,\n abi: v4QuoterAbi,\n functionName: \"quoteExactInputSingle\",\n args: [\n {\n poolKey,\n zeroForOne,\n exactAmount: BigInt(exactAmount) as unknown as number,\n hookData,\n },\n ],\n }) as [bigint, bigint];\n\n return { amountOut, gasEstimate };\n}\n\n/**\n * Try multiple PathKey[] routes and return the best quote plus all results.\n * Routes that fail (e.g. pool does not exist) are silently skipped.\n *\n * Uses viem multicall to batch all quotes into a single RPC call for speed.\n */\nexport async function quoteBestRoute(\n client: PublicClient,\n quoterAddress: Address,\n exactCurrency: Address,\n routes: PathKey[][],\n exactAmount: bigint,\n): Promise<BestQuote> {\n const results = await client.multicall({\n contracts: routes.map((path) => ({\n address: quoterAddress,\n abi: v4QuoterAbi,\n functionName: \"quoteExactInput\" as const,\n args: [\n {\n exactCurrency,\n path,\n exactAmount: BigInt(exactAmount) as unknown as number,\n },\n ],\n })),\n allowFailure: true,\n });\n\n const allRoutes: QuoteResult[] = [];\n for (let i = 0; i < results.length; i++) {\n const r = results[i]!;\n if (r.status === \"success\") {\n const [amountOut, gasEstimate] = r.result as unknown as [bigint, bigint];\n allRoutes.push({ amountOut, gasEstimate, path: routes[i]! });\n }\n }\n\n if (allRoutes.length === 0) {\n throw new Error(\"No valid routes found\");\n }\n\n const bestRoute = allRoutes.reduce((best, current) =>\n current.amountOut > best.amountOut ? current : best,\n );\n\n return { bestRoute, allRoutes };\n}\n\n/**\n * Find and quote the best swap route from `tokenIn` to `tokenOut`.\n *\n * Combines the caller's `pools` with `COMMON_POOLS[chainId]`, builds all\n * possible paths (up to `maxHops`), then quotes them all via a single\n * multicall and returns the best result.\n *\n * @param client - viem PublicClient\n * @param chainId - Chain ID (used to look up COMMON_POOLS and V4_QUOTER_ADDRESSES)\n * @param tokenIn - Input token address\n * @param tokenOut - Desired output token address\n * @param exactAmount - Exact input amount\n * @param pools - Additional pools to consider (e.g. point-token-specific)\n * @param quoterAddress - Override the default V4 Quoter address for this chain\n * @param maxHops - Maximum number of hops per path (default 3)\n */\nexport async function findBestQuote(\n client: PublicClient,\n chainId: number,\n tokenIn: Address,\n tokenOut: Address,\n exactAmount: bigint,\n pools: PoolKey[] = [],\n quoterAddress?: Address,\n maxHops = 3,\n): Promise<BestQuote> {\n const quoter = quoterAddress ?? V4_QUOTER_ADDRESSES[chainId];\n if (!quoter) {\n throw new Error(`No V4 Quoter address configured for chain ${chainId}`);\n }\n\n const commonPools = COMMON_POOLS[chainId] ?? [];\n const allPools = [...pools, ...commonPools];\n const paths = buildAllPaths(allPools, tokenIn, tokenOut, maxHops);\n\n if (paths.length === 0) {\n throw new Error(`No paths found from ${tokenIn} to ${tokenOut}`);\n }\n\n return quoteBestRoute(client, quoter, tokenIn, paths, exactAmount);\n}\n","import { encodeFunctionData } from \"viem\";\nimport type { Address, Hex, PublicClient } from \"viem\";\nimport { erc20Abi } from \"@pafi-dev/core\";\nimport { permit2Abi } from \"@pafi-dev/core\";\n\nexport async function checkAllowance(\n client: PublicClient,\n token: Address,\n owner: Address,\n spender: Address,\n): Promise<bigint> {\n return client.readContract({\n address: token,\n abi: erc20Abi,\n functionName: \"allowance\",\n args: [owner, spender],\n });\n}\n\n/**\n * Encode an ERC-20 approve(spender, amount) call.\n */\nexport function buildErc20ApprovalCalldata(\n spender: Address,\n amount: bigint,\n): Hex {\n return encodeFunctionData({\n abi: erc20Abi,\n functionName: \"approve\",\n args: [spender, amount],\n });\n}\n\n/**\n * Encode a Permit2 approve(token, spender, amount, expiration) call.\n */\nexport function buildPermit2ApprovalCalldata(\n token: Address,\n spender: Address,\n amount: bigint,\n expiration: number,\n): Hex {\n return encodeFunctionData({\n abi: permit2Abi,\n functionName: \"approve\",\n args: [token, spender, amount, expiration],\n });\n}\n","import { encodeAbiParameters, encodePacked } from \"viem\";\nimport type { Address, Hex } from \"viem\";\nimport type { PathKey, QuoteResult } from \"@pafi-dev/core\";\n\n// -------------------------------------------------------------------------\n// V4 UniversalRouter command / action constants\n// Reference: https://github.com/Uniswap/v4-periphery/blob/main/src/libraries/Actions.sol\n// -------------------------------------------------------------------------\n\n/** UniversalRouter command byte for V4 swap */\nexport const V4_SWAP = 0x10 as const;\n\n/** V4 actions */\nexport const SWAP_EXACT_IN_SINGLE = 0x06 as const;\nexport const SWAP_EXACT_IN = 0x07 as const;\nexport const SETTLE_ALL = 0x0c as const;\nexport const TAKE_ALL = 0x0f as const;\n\n// -------------------------------------------------------------------------\n// ABI type strings matching Uniswap's V4Planner encoding\n// Reference: https://github.com/Uniswap/sdks/blob/main/sdks/v4-sdk/src/utils/v4Planner.ts\n//\n// IMPORTANT: PathKey.fee is uint256 in the V4 Router ABI (not uint24).\n// -------------------------------------------------------------------------\n\n// PathKey components for V4 Router ABI encoding.\n// IMPORTANT: fee is uint256 in the V4 Router (not uint24 as in PoolKey).\n// Reference: https://github.com/Uniswap/sdks/blob/main/sdks/v4-sdk/src/utils/v4Planner.ts\nconst PATH_KEY_ABI_COMPONENTS = [\n { name: \"intermediateCurrency\", type: \"address\" },\n { name: \"fee\", type: \"uint256\" },\n { name: \"tickSpacing\", type: \"int24\" },\n { name: \"hooks\", type: \"address\" },\n { name: \"hookData\", type: \"bytes\" },\n] as const;\n\nconst EXACT_INPUT_PARAMS_ABI = [\n { name: \"currencyIn\", type: \"address\" },\n {\n name: \"path\",\n type: \"tuple[]\",\n components: PATH_KEY_ABI_COMPONENTS,\n },\n { name: \"amountIn\", type: \"uint128\" },\n { name: \"amountOutMinimum\", type: \"uint128\" },\n] as const;\n\n/**\n * Build the calldata inputs[0] (the V4_SWAP command payload) for\n * UniversalRouter.execute.\n *\n * Actions encoded: SWAP_EXACT_IN → SETTLE_ALL → TAKE_ALL\n *\n * Encoding matches the Uniswap V4 SDK's V4Planner — each action's params\n * are individually ABI-encoded, then wrapped together with the action bytes.\n */\nexport function buildV4SwapInput(\n currencyIn: Address,\n path: PathKey[],\n amountIn: bigint,\n minAmountOut: bigint,\n outputCurrency: Address,\n): Hex {\n const actions = encodePacked(\n [\"uint8\", \"uint8\", \"uint8\"],\n [SWAP_EXACT_IN, SETTLE_ALL, TAKE_ALL],\n );\n\n // Param 0: ExactInputParams — encoded as a single tuple so the CalldataDecoder\n // can locate it via a single offset pointer. fee is uint256 per V4 Router spec.\n const swapParam = encodeAbiParameters(\n [{ name: \"swap\", type: \"tuple\", components: EXACT_INPUT_PARAMS_ABI }],\n [\n {\n currencyIn,\n path: path.map((p) => ({\n intermediateCurrency: p.intermediateCurrency,\n fee: BigInt(p.fee),\n tickSpacing: p.tickSpacing,\n hooks: p.hooks,\n hookData: p.hookData,\n })),\n amountIn,\n amountOutMinimum: minAmountOut,\n },\n ],\n );\n\n // Param 1: SETTLE_ALL { currency, maxAmount }\n const settleParam = encodeAbiParameters(\n [\n { name: \"currency\", type: \"address\" },\n { name: \"maxAmount\", type: \"uint256\" },\n ],\n [currencyIn, amountIn],\n );\n\n // Param 2: TAKE_ALL { currency, minAmount }\n const takeParam = encodeAbiParameters(\n [\n { name: \"currency\", type: \"address\" },\n { name: \"minAmount\", type: \"uint256\" },\n ],\n [outputCurrency, minAmountOut],\n );\n\n return encodeAbiParameters(\n [\n { name: \"actions\", type: \"bytes\" },\n { name: \"params\", type: \"bytes[]\" },\n ],\n [actions, [swapParam, settleParam, takeParam]],\n );\n}\n\n/**\n * Build the full commands + inputs args for UniversalRouter.execute.\n */\nexport function buildUniversalRouterExecuteArgs(\n currencyIn: Address,\n path: PathKey[],\n amountIn: bigint,\n minAmountOut: bigint,\n outputCurrency: Address,\n): { commands: Hex; inputs: Hex[] } {\n const commands = encodePacked([\"uint8\"], [V4_SWAP]);\n const inputs: Hex[] = [\n buildV4SwapInput(currencyIn, path, amountIn, minAmountOut, outputCurrency),\n ];\n return { commands, inputs };\n}\n\n/**\n * Build UniversalRouter execute args from a quote result.\n *\n * Takes the output of `findBestQuote` / `quoteBestRoute` and produces\n * ready-to-use `{ commands, inputs }` for `UniversalRouter.execute`.\n *\n * @param quote - Quote result containing the path\n * @param currencyIn - Input token address\n * @param currencyOut - Output token address\n * @param amountIn - Exact input amount (same value passed to the quoter)\n * @param minAmountOut - Minimum acceptable output (caller applies slippage)\n *\n * @deprecated Since v1.4 — the Issuer App no longer handles swaps.\n * For the new PT→USDT batch call on PAFI Web (Scenario 4 with\n * EIP-7702 gas deduction), use `buildSwapWithGasDeduction()` (v1.5).\n * This helper is kept for legacy v0.2.x consumers; will be removed in v2.0.\n */\nexport function buildSwapFromQuote(params: {\n quote: QuoteResult;\n currencyIn: Address;\n currencyOut: Address;\n amountIn: bigint;\n minAmountOut: bigint;\n}): { commands: Hex; inputs: Hex[] } {\n return buildUniversalRouterExecuteArgs(\n params.currencyIn,\n params.quote.path,\n params.amountIn,\n params.minAmountOut,\n params.currencyOut,\n );\n}\n","import type { Address, Hex, PublicClient } from \"viem\";\nimport { universalRouterAbi } from \"@pafi-dev/core\";\nimport { SimulationError } from \"@pafi-dev/core\";\n\nexport interface SwapSimulationResult {\n success: boolean;\n gasEstimate: bigint;\n}\n\n/**\n * Simulate a UniversalRouter.execute swap call via eth_call (no gas spent).\n *\n * Runs the full V4 swap flow — token transfer via Permit2, swap via\n * PoolManager, output settlement — without submitting a transaction.\n * If the simulation reverts, throws a `SimulationError` with the reason.\n *\n * @param client - viem PublicClient\n * @param routerAddress - UniversalRouter contract address\n * @param commands - Packed command bytes (from buildSwapFromQuote)\n * @param inputs - Encoded inputs per command (from buildSwapFromQuote)\n * @param deadline - Unix timestamp after which the tx expires\n * @param from - Address that will execute the swap\n */\nexport async function simulateSwap(\n client: PublicClient,\n routerAddress: Address,\n commands: Hex,\n inputs: Hex[],\n deadline: bigint,\n from: Address,\n): Promise<SwapSimulationResult> {\n try {\n const gasEstimate = await client.estimateContractGas({\n address: routerAddress,\n abi: universalRouterAbi,\n functionName: \"execute\",\n args: [commands, inputs, deadline],\n account: from,\n });\n\n return { success: true, gasEstimate };\n } catch (error: unknown) {\n const message =\n error instanceof Error ? error.message : \"Unknown simulation error\";\n throw new SimulationError(\"swap\", message);\n }\n}\n","import { encodeFunctionData } from \"viem\";\nimport type { Address, Hex } from \"viem\";\nimport {\n PERMIT2_ADDRESS,\n buildPartialUserOperation,\n erc20ApproveOp,\n erc20TransferOp,\n rawCallOp,\n universalRouterAbi,\n type Operation,\n type PartialUserOperation,\n type PathKey,\n} from \"@pafi-dev/core\";\nimport { buildUniversalRouterExecuteArgs } from \"./universalRouter\";\nimport { buildPermit2ApprovalCalldata } from \"./approval\";\n\n/**\n * v0.3 — Generalized swap UserOp builder. Direction-agnostic: works\n * for **any** ERC-20 → ERC-20 pair routable through PAFI's V4 pools:\n *\n * - PT → USDT (cashout)\n * - USDT → PT (buy PT with USDT)\n * - PT0 → PT1 (same-issuer multi-token swap; routes via USDT)\n *\n * UserOp shape (atomic batch via EIP-7702 BatchExecutor):\n *\n * 1. `inputToken.approve(Permit2, amountIn)` — exactly the swap amount.\n * 2. `Permit2.approve(inputToken, router, amountIn, deadline)` — Permit2\n * authorization to UniversalRouter.\n * 3. `UniversalRouter.execute(commands, inputs, deadline)` — V4 swap\n * `inputToken → outputToken`; user receives ≥ `minAmountOut`.\n * 4. `outputToken.transfer(feeRecipient, gasFeeAmountOutput)` —\n * operator gas reimbursement, paid in OUTPUT token (omitted when 0).\n *\n * ## Fee model — output-token strategy (token-availability rule)\n *\n * Fee is charged in the **output token** AFTER the swap, because the\n * user holds output token at that point in the batch. Three benefits:\n *\n * - User holds exactly `amountIn` of input token (FE quote = chain\n * reality). No \"you need 1010 to swap 1000\" UX surprise.\n * - Quote API returns `outputNet = outputGross - feeOutput` directly,\n * matching what user actually receives.\n * - PT → USDT: PAFI receives USDT (universally liquid, clean accounting).\n *\n * Caller MUST ensure `minAmountOut >= gasFeeAmountOutput` (fee transfer\n * reverts otherwise → whole batch reverts).\n *\n * ## PAFI Hook fee\n *\n * The V4 PAFIHook charges 10% on PT → USDT direction (one-way). The\n * 10% is taken at pool level inside `UniversalRouter.execute`, so this\n * builder doesn't model it explicitly — it shows up as a smaller\n * `amountOut` from `findBestQuote`.\n *\n * - PT → USDT: pool charges 10% (output reduced)\n * - USDT → PT: no hook fee\n * - PT0 → PT1: 10% on PT0 → USDT leg, 0% on USDT → PT1 leg\n */\nexport interface BuildSwapUserOpParams {\n /** User's EOA (with EIP-7702 delegation to BatchExecutor). */\n userAddress: Address;\n /** ERC-4337 account nonce — fetched from EntryPoint by the caller. */\n aaNonce: bigint;\n\n /** Token being spent — approved to Permit2 + UniversalRouter. */\n inputTokenAddress: Address;\n /** Token user receives. */\n outputTokenAddress: Address;\n /** UniversalRouter contract address (chain-specific). */\n universalRouterAddress: Address;\n\n /** Input token units to swap. User must hold exactly this much input token. */\n amountIn: bigint;\n /**\n * Minimum output (gross) to accept from the swap. Caller applies\n * slippage against a fresh quote. MUST be ≥ `gasFeeAmountOutput` or\n * the post-swap fee transfer reverts.\n */\n minAmountOut: bigint;\n\n /**\n * V4 pool path for the swap. Single-hop = 1 PathKey, multi-hop = N.\n * Get this from `findBestQuote().bestRoute.path`.\n */\n swapPath: PathKey[];\n /** Unix seconds. After this, the router rejects the swap. */\n deadline: bigint;\n\n /**\n * Operator gas-reimbursement fee — paid in OUTPUT token. Omitted\n * from the batch when 0n. Caller quotes this via `quoteOperatorFee*`\n * helpers (USDT/PT depending on output token).\n *\n * v0.3 — switched from input-side fee (v0.2 `gasFeeAmount`) to\n * output-side fee. See \"Fee model\" comment above.\n */\n gasFeeAmountOutput: bigint;\n /**\n * Where the gas fee lands — typically the canonical PAFI fee\n * recipient from `getContractAddresses(chainId).pafiFeeRecipient`.\n */\n feeRecipient: Address;\n\n /** Override ERC-4337 gas estimates. Defaults are conservative. */\n gasLimits?: {\n callGasLimit?: bigint;\n verificationGasLimit?: bigint;\n preVerificationGas?: bigint;\n };\n}\n\n/**\n * Build an unsigned UserOp for the generalized swap flow.\n *\n * @throws when `amountIn` is non-positive, `gasFeeAmountOutput` is\n * negative, `swapPath` is empty, or `minAmountOut < gasFeeAmountOutput`.\n */\nexport function buildSwapUserOp(\n params: BuildSwapUserOpParams,\n): PartialUserOperation {\n if (params.amountIn <= 0n) {\n throw new Error(\"buildSwapUserOp: amountIn must be positive\");\n }\n if (params.minAmountOut < 0n) {\n throw new Error(\"buildSwapUserOp: minAmountOut must be non-negative\");\n }\n if (params.gasFeeAmountOutput < 0n) {\n throw new Error(\"buildSwapUserOp: gasFeeAmountOutput must be non-negative\");\n }\n if (\n params.gasFeeAmountOutput > 0n &&\n params.minAmountOut < params.gasFeeAmountOutput\n ) {\n throw new Error(\n \"buildSwapUserOp: minAmountOut must be >= gasFeeAmountOutput so the post-swap fee transfer cannot revert\",\n );\n }\n if (params.swapPath.length === 0) {\n throw new Error(\n \"buildSwapUserOp: swapPath must contain at least one PathKey\",\n );\n }\n\n const { commands, inputs } = buildUniversalRouterExecuteArgs(\n params.inputTokenAddress,\n params.swapPath,\n params.amountIn,\n params.minAmountOut,\n params.outputTokenAddress,\n );\n\n const swapCallData: Hex = encodeFunctionData({\n abi: universalRouterAbi,\n functionName: \"execute\",\n args: [commands, inputs, params.deadline],\n });\n\n // Approve only the swap amount — fee comes out of swap proceeds.\n const permit2ApproveData: Hex = buildPermit2ApprovalCalldata(\n params.inputTokenAddress,\n params.universalRouterAddress,\n params.amountIn,\n Number(params.deadline),\n );\n\n const operations: Operation[] = [\n erc20ApproveOp(params.inputTokenAddress, PERMIT2_ADDRESS, params.amountIn),\n rawCallOp(PERMIT2_ADDRESS, permit2ApproveData),\n rawCallOp(params.universalRouterAddress, swapCallData),\n ];\n\n // Fee transfer in OUTPUT token, AFTER swap, when user holds output.\n if (params.gasFeeAmountOutput > 0n) {\n operations.push(\n erc20TransferOp(\n params.outputTokenAddress,\n params.feeRecipient,\n params.gasFeeAmountOutput,\n ),\n );\n }\n\n return buildPartialUserOperation({\n sender: params.userAddress,\n nonce: params.aaNonce,\n operations,\n gasLimits: {\n callGasLimit: params.gasLimits?.callGasLimit ?? 700_000n,\n verificationGasLimit: params.gasLimits?.verificationGasLimit ?? 150_000n,\n preVerificationGas: params.gasLimits?.preVerificationGas ?? 50_000n,\n },\n });\n}\n","// Re-export from @pafi-dev/core — fetchPafiPools lives in core so all\n// SDK packages share one implementation.\nexport { fetchPafiPools, PAFI_SUBGRAPH_URL } from \"@pafi-dev/core\";\n"],"mappings":"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","names":["import_viem","import_core","import_core","import_core","import_core","import_viem","import_core","import_viem","import_core","userOp","import_core"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -252,8 +252,13 @@ declare class TradingHandlers {
|
|
|
252
252
|
* holds exactly `amountIn` of input; receives `outputGross - fee`
|
|
253
253
|
* net. Quote response surfaces both `estimatedOutputAmount` (gross)
|
|
254
254
|
* and `outputNet` so the FE can display reality.
|
|
255
|
+
*
|
|
256
|
+
* v0.3.1 — `authenticatedAddress` first param. Caller (issuer
|
|
257
|
+
* controller / FE proxy) MUST pass the address extracted from the
|
|
258
|
+
* verified session/JWT. Handler asserts it equals
|
|
259
|
+
* `request.userAddress`. See SDK_CORE_TRADING_AUDIT.md C6.
|
|
255
260
|
*/
|
|
256
|
-
handleSwap(request: ApiSwapRequest): Promise<ApiSwapResponse>;
|
|
261
|
+
handleSwap(authenticatedAddress: Address, request: ApiSwapRequest): Promise<ApiSwapResponse>;
|
|
257
262
|
/**
|
|
258
263
|
* Build an Orderly perp deposit UserOp.
|
|
259
264
|
*
|
|
@@ -272,7 +277,7 @@ declare class TradingHandlers {
|
|
|
272
277
|
* `getContractAddresses(chainId).orderlyRelay` is the placeholder
|
|
273
278
|
* sentinel (Relay not deployed for that chain).
|
|
274
279
|
*/
|
|
275
|
-
handlePerpDeposit(request: ApiPerpDepositRequest): Promise<ApiPerpDepositResponse>;
|
|
280
|
+
handlePerpDeposit(authenticatedAddress: Address, request: ApiPerpDepositRequest): Promise<ApiPerpDepositResponse>;
|
|
276
281
|
}
|
|
277
282
|
|
|
278
283
|
declare function checkAllowance(client: PublicClient, token: Address, owner: Address, spender: Address): Promise<bigint>;
|
package/dist/index.d.ts
CHANGED
|
@@ -252,8 +252,13 @@ declare class TradingHandlers {
|
|
|
252
252
|
* holds exactly `amountIn` of input; receives `outputGross - fee`
|
|
253
253
|
* net. Quote response surfaces both `estimatedOutputAmount` (gross)
|
|
254
254
|
* and `outputNet` so the FE can display reality.
|
|
255
|
+
*
|
|
256
|
+
* v0.3.1 — `authenticatedAddress` first param. Caller (issuer
|
|
257
|
+
* controller / FE proxy) MUST pass the address extracted from the
|
|
258
|
+
* verified session/JWT. Handler asserts it equals
|
|
259
|
+
* `request.userAddress`. See SDK_CORE_TRADING_AUDIT.md C6.
|
|
255
260
|
*/
|
|
256
|
-
handleSwap(request: ApiSwapRequest): Promise<ApiSwapResponse>;
|
|
261
|
+
handleSwap(authenticatedAddress: Address, request: ApiSwapRequest): Promise<ApiSwapResponse>;
|
|
257
262
|
/**
|
|
258
263
|
* Build an Orderly perp deposit UserOp.
|
|
259
264
|
*
|
|
@@ -272,7 +277,7 @@ declare class TradingHandlers {
|
|
|
272
277
|
* `getContractAddresses(chainId).orderlyRelay` is the placeholder
|
|
273
278
|
* sentinel (Relay not deployed for that chain).
|
|
274
279
|
*/
|
|
275
|
-
handlePerpDeposit(request: ApiPerpDepositRequest): Promise<ApiPerpDepositResponse>;
|
|
280
|
+
handlePerpDeposit(authenticatedAddress: Address, request: ApiPerpDepositRequest): Promise<ApiPerpDepositResponse>;
|
|
276
281
|
}
|
|
277
282
|
|
|
278
283
|
declare function checkAllowance(client: PublicClient, token: Address, owner: Address, spender: Address): Promise<bigint>;
|
package/dist/index.js
CHANGED
|
@@ -422,8 +422,18 @@ var TradingHandlers = class {
|
|
|
422
422
|
* holds exactly `amountIn` of input; receives `outputGross - fee`
|
|
423
423
|
* net. Quote response surfaces both `estimatedOutputAmount` (gross)
|
|
424
424
|
* and `outputNet` so the FE can display reality.
|
|
425
|
+
*
|
|
426
|
+
* v0.3.1 — `authenticatedAddress` first param. Caller (issuer
|
|
427
|
+
* controller / FE proxy) MUST pass the address extracted from the
|
|
428
|
+
* verified session/JWT. Handler asserts it equals
|
|
429
|
+
* `request.userAddress`. See SDK_CORE_TRADING_AUDIT.md C6.
|
|
425
430
|
*/
|
|
426
|
-
async handleSwap(request) {
|
|
431
|
+
async handleSwap(authenticatedAddress, request) {
|
|
432
|
+
if (getAddress(authenticatedAddress) !== getAddress(request.userAddress)) {
|
|
433
|
+
throw new Error(
|
|
434
|
+
`handleSwap: authenticatedAddress (${authenticatedAddress}) does not match request.userAddress (${request.userAddress})`
|
|
435
|
+
);
|
|
436
|
+
}
|
|
427
437
|
if (request.chainId !== this.chainId) {
|
|
428
438
|
throw new Error(`handleSwap: unsupported chainId ${request.chainId}`);
|
|
429
439
|
}
|
|
@@ -527,7 +537,12 @@ var TradingHandlers = class {
|
|
|
527
537
|
* `getContractAddresses(chainId).orderlyRelay` is the placeholder
|
|
528
538
|
* sentinel (Relay not deployed for that chain).
|
|
529
539
|
*/
|
|
530
|
-
async handlePerpDeposit(request) {
|
|
540
|
+
async handlePerpDeposit(authenticatedAddress, request) {
|
|
541
|
+
if (getAddress(authenticatedAddress) !== getAddress(request.userAddress)) {
|
|
542
|
+
throw new Error(
|
|
543
|
+
`handlePerpDeposit: authenticatedAddress (${authenticatedAddress}) does not match request.userAddress (${request.userAddress})`
|
|
544
|
+
);
|
|
545
|
+
}
|
|
531
546
|
if (request.chainId !== this.chainId) {
|
|
532
547
|
throw new Error(`handlePerpDeposit: unsupported chainId ${request.chainId}`);
|
|
533
548
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/api/handlers.ts","../src/quoting/routes.ts","../src/quoting/quote.ts","../src/swap/approval.ts","../src/swap/universalRouter.ts","../src/swap/simulate.ts","../src/swap/buildSwap.ts","../src/pools.ts"],"sourcesContent":["import { getAddress } from \"viem\";\nimport type { Address, PublicClient } from \"viem\";\nimport {\n buildPerpDepositWithGasDeduction,\n buildPerpDepositViaRelay,\n ORDERLY_RELAY_ABI,\n getContractAddresses,\n UNIVERSAL_ROUTER_ADDRESSES,\n ORDERLY_VAULT_ABI,\n ORDERLY_VAULT_ADDRESSES,\n BROKER_HASHES,\n TOKEN_HASHES,\n computeAccountId,\n quoteOperatorFeePt,\n quoteOperatorFeeUsdt,\n} from \"@pafi-dev/core\";\nimport { findBestQuote } from \"../quoting\";\nimport { buildSwapUserOp } from \"../swap\";\nimport type {\n ApiQuoteRequest,\n ApiQuoteResponse,\n ApiSwapRequest,\n ApiSwapResponse,\n ApiPerpDepositRequest,\n ApiPerpDepositResponse,\n} from \"./types\";\n\nexport interface TradingHandlersConfig {\n provider: PublicClient;\n chainId: number;\n}\n\n/**\n * Framework-agnostic handlers for on-chain trading actions.\n *\n * All handlers are stateless — they need only a PublicClient for RPC\n * calls. No ledger, no signer, no DB. Issuers wrap these in their own\n * HTTP controllers (Express / NestJS / Hono / etc.) the same way they\n * wrap `IssuerApiHandlers` from `@pafi-dev/issuer`.\n *\n * Example (NestJS):\n *\n * const trading = new TradingHandlers({ provider, chainId });\n *\n * // GET /quote\n * const quote = await trading.handleQuote({ chainId, pointTokenAddress, amount, pools });\n *\n * // POST /swap\n * const swap = await trading.handleSwap({ chainId, userAddress, pointTokenAddress, amount, aaNonce });\n *\n * // POST /perp-deposit\n * const deposit = await trading.handlePerpDeposit({ chainId, userAddress, amount, aaNonce, brokerId });\n */\nexport class TradingHandlers {\n private readonly provider: PublicClient;\n private readonly chainId: number;\n\n constructor(config: TradingHandlersConfig) {\n this.provider = config.provider;\n this.chainId = config.chainId;\n }\n\n // =========================================================================\n // GET /quote\n // =========================================================================\n\n /**\n * Quote exact-input PT → USDT via Uniswap V4 on-chain Quoter.\n *\n * Uses multicall to batch all candidate routes into a single RPC call.\n * Returns `quoteError: \"QUOTE_UNAVAILABLE\"` when no pool/path exists\n * rather than throwing, so callers can show a soft \"unavailable\" UI\n * state without 500-ing.\n */\n async handleQuote(request: ApiQuoteRequest): Promise<ApiQuoteResponse> {\n if (request.chainId !== this.chainId) {\n throw new Error(`handleQuote: unsupported chainId ${request.chainId}`);\n }\n if (request.amount === 0n) {\n return {\n inputAmount: 0n,\n estimatedOutputAmount: 0n,\n outputNet: 0n,\n feeAmountOutput: 0n,\n gasEstimate: 0n,\n };\n }\n\n const inputTokenAddress = getAddress(request.inputTokenAddress);\n const outputTokenAddress = getAddress(request.outputTokenAddress);\n const pools = request.pools ?? [];\n\n try {\n const best = await findBestQuote(\n this.provider,\n request.chainId,\n inputTokenAddress,\n outputTokenAddress,\n request.amount,\n pools,\n );\n const feeAmountOutput = await quoteOperatorFeeOutput(\n this.provider,\n request.chainId,\n outputTokenAddress,\n ).catch(() => 0n);\n const outputNet =\n best.bestRoute.amountOut > feeAmountOutput\n ? best.bestRoute.amountOut - feeAmountOutput\n : 0n;\n return {\n inputAmount: request.amount,\n estimatedOutputAmount: best.bestRoute.amountOut,\n outputNet,\n feeAmountOutput,\n gasEstimate: best.bestRoute.gasEstimate,\n };\n } catch {\n return {\n inputAmount: request.amount,\n estimatedOutputAmount: 0n,\n outputNet: 0n,\n feeAmountOutput: 0n,\n gasEstimate: 0n,\n quoteError: \"QUOTE_UNAVAILABLE\",\n };\n }\n }\n\n // =========================================================================\n // POST /swap\n // =========================================================================\n\n /**\n * Build a swap UserOp (direction-agnostic).\n *\n * Quotes the best route, applies slippage, then encodes a 4-step\n * batch: input.approve → Permit2.approve → UniversalRouter.execute →\n * output.transfer (fee in OUTPUT token, omitted when fee = 0).\n *\n * v0.3 — Fee model is OUTPUT-side (token-availability rule). User\n * holds exactly `amountIn` of input; receives `outputGross - fee`\n * net. Quote response surfaces both `estimatedOutputAmount` (gross)\n * and `outputNet` so the FE can display reality.\n */\n async handleSwap(request: ApiSwapRequest): Promise<ApiSwapResponse> {\n if (request.chainId !== this.chainId) {\n throw new Error(`handleSwap: unsupported chainId ${request.chainId}`);\n }\n if (request.amount <= 0n) {\n throw new Error(\"handleSwap: amount must be positive\");\n }\n\n const { pafiFeeRecipient } = getContractAddresses(request.chainId);\n const universalRouter = UNIVERSAL_ROUTER_ADDRESSES[request.chainId];\n if (!universalRouter) {\n throw new Error(`handleSwap: no UniversalRouter for chainId ${request.chainId}`);\n }\n\n const inputTokenAddress = getAddress(request.inputTokenAddress);\n const outputTokenAddress = getAddress(request.outputTokenAddress);\n const userAddress = getAddress(request.userAddress);\n const pools = request.pools ?? [];\n\n let quoteResult: Awaited<ReturnType<typeof findBestQuote>>;\n try {\n quoteResult = await findBestQuote(\n this.provider,\n request.chainId,\n inputTokenAddress,\n outputTokenAddress,\n request.amount,\n pools,\n );\n } catch {\n throw new Error(\n `handleSwap: no swap path found from ${inputTokenAddress} to ${outputTokenAddress}`,\n );\n }\n\n // Resolve the operator fee in OUTPUT token units. Auto-quote uses\n // the right underlying quoter based on output token (USDT vs PT).\n const gasFeeAmountOutput =\n request.gasFeeAmountOutput !== undefined\n ? request.gasFeeAmountOutput\n : await quoteOperatorFeeOutput(\n this.provider,\n request.chainId,\n outputTokenAddress,\n ).catch(() => 0n);\n\n const hops = quoteResult.bestRoute.path.length;\n // Multi-hop routes compound slippage across legs — bias higher when\n // caller didn't override. 50 bps single-hop, 100 bps multi-hop.\n const slippageBps =\n request.slippageBps ?? (hops > 1 ? 100 : 50);\n\n const estimatedOutputAmount = quoteResult.bestRoute.amountOut;\n const minAmountOut = (estimatedOutputAmount * BigInt(10000 - slippageBps)) / 10000n;\n\n if (gasFeeAmountOutput > 0n && minAmountOut < gasFeeAmountOutput) {\n throw new Error(\n `handleSwap: minAmountOut (${minAmountOut}) below operator fee (${gasFeeAmountOutput}) — increase swap amount or accept tighter slippage`,\n );\n }\n\n const deadline = BigInt(Math.floor(Date.now() / 1000) + 5 * 60);\n\n const userOp = buildSwapUserOp({\n userAddress,\n aaNonce: request.aaNonce,\n inputTokenAddress,\n outputTokenAddress,\n universalRouterAddress: universalRouter,\n amountIn: request.amount,\n minAmountOut,\n swapPath: quoteResult.bestRoute.path,\n deadline,\n gasFeeAmountOutput,\n feeRecipient: pafiFeeRecipient,\n });\n\n // Fee-stripped fallback for paymaster-refused path.\n const userOpFallback =\n gasFeeAmountOutput > 0n\n ? buildSwapUserOp({\n userAddress,\n aaNonce: request.aaNonce,\n inputTokenAddress,\n outputTokenAddress,\n universalRouterAddress: universalRouter,\n amountIn: request.amount,\n minAmountOut,\n swapPath: quoteResult.bestRoute.path,\n deadline,\n gasFeeAmountOutput: 0n,\n feeRecipient: pafiFeeRecipient,\n })\n : undefined;\n\n return {\n userOp,\n userOpFallback,\n estimatedOutputAmount,\n minAmountOut,\n hops,\n deadline,\n feeAmountUsed: gasFeeAmountOutput,\n feeRecipient: pafiFeeRecipient,\n };\n }\n\n // =========================================================================\n // POST /perp-deposit\n // =========================================================================\n\n /**\n * Build an Orderly perp deposit UserOp.\n *\n * Default path is the **PAFI Orderly Relay** (`viaRelay: true`):\n * USDC.approve(relay) + relay.deposit(req). The Relay holds an ETH\n * reserve and pays Orderly's LayerZero `msg.value` out of it; the\n * user pays a USDC fee (quoted via `Relay.quoteTokenFee`) instead.\n * No native ETH on the user wallet is required, so paymaster\n * sponsorship of the ERC-4337 gas is sufficient end-to-end.\n *\n * Fallback path (`viaRelay: false`): direct `Vault.deposit{value}`.\n * Reserved for chains where no Relay is deployed — the user wallet\n * **must** hold `layerZeroFee` as native ETH.\n *\n * The Relay path automatically falls back to Vault when\n * `getContractAddresses(chainId).orderlyRelay` is the placeholder\n * sentinel (Relay not deployed for that chain).\n */\n async handlePerpDeposit(request: ApiPerpDepositRequest): Promise<ApiPerpDepositResponse> {\n if (request.chainId !== this.chainId) {\n throw new Error(`handlePerpDeposit: unsupported chainId ${request.chainId}`);\n }\n if (request.amount <= 0n) {\n throw new Error(\"handlePerpDeposit: amount must be positive\");\n }\n\n const vault = ORDERLY_VAULT_ADDRESSES[request.chainId];\n if (!vault) {\n throw new Error(`handlePerpDeposit: no Orderly Vault for chainId ${request.chainId}`);\n }\n\n const brokerHash = BROKER_HASHES[request.brokerId as keyof typeof BROKER_HASHES];\n if (!brokerHash) {\n throw new Error(`handlePerpDeposit: unknown brokerId \"${request.brokerId}\"`);\n }\n const tokenHash = TOKEN_HASHES.USDC;\n const userAddress = getAddress(request.userAddress);\n\n const [usdcAddress, brokerAllowed] = await Promise.all([\n this.provider.readContract({\n address: vault,\n abi: ORDERLY_VAULT_ABI,\n functionName: \"getAllowedToken\",\n args: [tokenHash],\n }) as Promise<Address>,\n this.provider.readContract({\n address: vault,\n abi: ORDERLY_VAULT_ABI,\n functionName: \"getAllowedBroker\",\n args: [brokerHash],\n }) as Promise<boolean>,\n ]);\n\n if (!brokerAllowed) {\n throw new Error(\n `handlePerpDeposit: broker \"${request.brokerId}\" is not whitelisted on Orderly Vault`,\n );\n }\n\n const accountId = computeAccountId(userAddress, brokerHash);\n const depositData = {\n accountId,\n brokerHash,\n tokenHash,\n tokenAmount: request.amount,\n };\n\n // Always read layerZeroFee for response — even on the Relay path\n // it's useful informational output (lets the FE show \"Relay\n // covers ~X ETH for you\").\n const layerZeroFee = (await this.provider.readContract({\n address: vault,\n abi: ORDERLY_VAULT_ABI,\n functionName: \"getDepositFee\",\n args: [userAddress, depositData],\n })) as bigint;\n\n const useRelay = request.viaRelay !== false;\n const { orderlyRelay: relayAddress, pafiFeeRecipient } =\n getContractAddresses(request.chainId);\n const relayDeployed = !isPlaceholderAddress(relayAddress);\n\n // Resolve operator fee in USDC (input-token). Only applied to the\n // Relay path; the legacy direct-Vault path skips operator fee\n // entirely (it's already gas-heavy due to the user-paid\n // LayerZero msg.value, and PAFI doesn't sponsor that path on Base\n // anyway).\n const gasFeeUsdc =\n request.gasFeeUsdc !== undefined\n ? request.gasFeeUsdc\n : useRelay && relayDeployed\n ? await quoteOperatorFeeUsdt({\n provider: this.provider,\n chainId: request.chainId,\n }).catch(() => 0n)\n : 0n;\n\n if (useRelay && relayDeployed) {\n // Cap = max(amount * 5%, 2 USDC). The Relay fee is a flat USDC\n // amount derived from the LayerZero ETH cost at oracle price —\n // it does NOT scale with deposit size. A pure percentage cap\n // breaks for small deposits (e.g. 0.01 USDC test deposit, 5%\n // cap = 500 wei < 14k wei real fee). The 2 USDC floor covers\n // normal LayerZero pricing on Base; the 5% slope still guards\n // against oracle spikes on large deposits.\n const RELAY_FEE_FLOOR_USDC = 2_000_000n; // 2 USDC (6 decimals)\n const percentCap = (request.amount * 500n) / 10_000n;\n const maxRelayFee =\n request.maxRelayFee ??\n (percentCap > RELAY_FEE_FLOOR_USDC ? percentCap : RELAY_FEE_FLOOR_USDC);\n\n const relayRequest = {\n token: usdcAddress,\n receiver: userAddress,\n brokerHash,\n totalAmount: request.amount,\n maxFee: maxRelayFee,\n };\n\n const relayTokenFee = (await this.provider.readContract({\n address: relayAddress,\n abi: ORDERLY_RELAY_ABI,\n functionName: \"quoteTokenFee\",\n args: [relayRequest],\n })) as bigint;\n\n if (relayTokenFee > maxRelayFee) {\n throw new Error(\n `handlePerpDeposit: Relay tokenFee ${relayTokenFee} (≈ ${\n Number(relayTokenFee) / 1e6\n } USDC) exceeds maxRelayFee ${maxRelayFee} — pass a larger ` +\n `\\`maxRelayFee\\` or increase the deposit \\`amount\\` so the fee ` +\n `becomes a smaller share of the total.`,\n );\n }\n\n // Sanity-check: Relay forwards `(totalAmount − tokenFee)` to\n // Orderly Vault. When `tokenFee >= totalAmount` the forwarded\n // amount is zero / negative and the Relay reverts on-chain with\n // `FeeExceedsAmount(fee, totalAmount)` (selector 0x536766bf),\n // which propagates as an opaque `BatchExecutor.CallFailed(1, …)`\n // revert at simulation time. Catch this early on the client so\n // the UX shows an actionable message instead of an AA21/AA34\n // bundler error wrapped around the raw selector.\n if (relayTokenFee >= request.amount) {\n const feeUsdc = Number(relayTokenFee) / 1e6;\n const amountUsdc = Number(request.amount) / 1e6;\n throw new Error(\n `handlePerpDeposit: deposit amount ${amountUsdc} USDC is below the ` +\n `Relay fee ${feeUsdc} USDC — increase \\`amount\\` to at least ` +\n `${(feeUsdc * 2).toFixed(6)} USDC so a meaningful balance reaches ` +\n `your Orderly account after the Relay charge.`,\n );\n }\n\n // USDC fee transferred FIRST in the batch (input-token rule —\n // user holds USDC at start). Recipient = pafiFeeRecipient.\n const userOp = buildPerpDepositViaRelay({\n userAddress,\n aaNonce: request.aaNonce,\n relayAddress,\n request: relayRequest,\n gasFeeUsdc: gasFeeUsdc > 0n ? gasFeeUsdc : undefined,\n gasFeeUsdcRecipient: gasFeeUsdc > 0n ? pafiFeeRecipient : undefined,\n });\n\n // Same shape, no USDC fee transfer — for the paymaster-refused\n // fallback path. The Relay still charges its own USDC token-fee\n // (compensates LayerZero ETH spend, NOT PAFI's gas).\n const userOpFallback =\n gasFeeUsdc > 0n\n ? buildPerpDepositViaRelay({\n userAddress,\n aaNonce: request.aaNonce,\n relayAddress,\n request: relayRequest,\n })\n : undefined;\n\n return {\n userOp,\n userOpFallback,\n path: \"relay\",\n layerZeroFee,\n relayTokenFee,\n accountId,\n brokerHash,\n usdcAddress,\n relayAddress,\n feeAmountUsed: gasFeeUsdc,\n feeRecipient: pafiFeeRecipient,\n };\n }\n\n // Fallback: direct Vault.deposit{value} — user wallet MUST hold\n // `layerZeroFee` as native ETH (paymaster does not sponsor msg.value).\n const userOp = buildPerpDepositWithGasDeduction({\n userAddress,\n aaNonce: request.aaNonce,\n chainId: request.chainId,\n usdcAddress,\n amount: request.amount,\n depositData,\n layerZeroFee,\n });\n\n return {\n userOp,\n path: \"vault\",\n layerZeroFee,\n relayTokenFee: 0n,\n accountId,\n brokerHash,\n usdcAddress,\n relayAddress: vault,\n // Vault path doesn't include the PT operator fee transfer (it's\n // an unsponsored path on chains without a Relay deployment, and\n // the user is paying msg.value in ETH already). Echo 0n + the\n // canonical recipient so the response shape stays consistent.\n feeAmountUsed: 0n,\n feeRecipient: pafiFeeRecipient,\n };\n }\n}\n\n/**\n * `addresses.ts` uses `0x000…<suffix>` sentinels for chains where a\n * given contract is not yet deployed. Detect them by upper-160-bits =\n * 0 so we route to the Vault fallback automatically.\n */\nfunction isPlaceholderAddress(addr: Address): boolean {\n return /^0x0{36}[0-9a-fA-F]{4}$/i.test(addr);\n}\n\n/**\n * Quote the operator gas fee in OUTPUT token raw units. Picks the right\n * underlying quoter:\n *\n * - Output is USDT (canonical address from `getContractAddresses`):\n * `quoteOperatorFeeUsdt` — pure Chainlink ETH/USD, no V4 hop.\n * - Output is anything else (PT0, PT1, ...): `quoteOperatorFeePt`\n * against the output token's V4 pool.\n *\n * Used by `handleQuote` (display the user-facing net output) and\n * `handleSwap` (auto-quote when caller didn't override\n * `gasFeeAmountOutput`).\n */\nasync function quoteOperatorFeeOutput(\n provider: PublicClient,\n chainId: number,\n outputTokenAddress: Address,\n): Promise<bigint> {\n const { usdt } = getContractAddresses(chainId);\n if (\n usdt &&\n getAddress(outputTokenAddress) === getAddress(usdt as Address)\n ) {\n return quoteOperatorFeeUsdt({ provider, chainId });\n }\n return quoteOperatorFeePt({\n provider,\n chainId,\n pointTokenAddress: outputTokenAddress,\n });\n}\n","import type { Address } from \"viem\";\nimport type { PathKey, PoolKey } from \"@pafi-dev/core\";\nimport { COMMON_POOLS, POINT_TOKEN_POOLS } from \"@pafi-dev/core\";\n\nconst ZERO_ADDRESS = \"0x0000000000000000000000000000000000000000\" as Address;\n\n/**\n * Combine point-token-specific pools and common pools for a given chain.\n * Point token pools are listed first so callers can prioritise them.\n */\nexport function combineRoutes(\n chainId: number,\n pointTokenAddress: Address,\n): PoolKey[] {\n const commonPools = COMMON_POOLS[chainId] ?? [];\n const pointPools = POINT_TOKEN_POOLS[chainId]?.[pointTokenAddress] ?? [];\n return [...pointPools, ...commonPools];\n}\n\n/**\n * Build all possible swap paths from `tokenIn` to `tokenOut` using the given\n * pools. Returns an array of PathKey[] routes (each up to `maxHops` hops).\n *\n * Supports both direct single-hop routes and multi-hop routes through\n * intermediate tokens. Each pool is used at most once per path.\n *\n * @param pools - Available pools to route through\n * @param tokenIn - Input token address\n * @param tokenOut - Desired output token address\n * @param maxHops - Maximum number of hops (default 3)\n */\nexport function buildAllPaths(\n pools: PoolKey[],\n tokenIn: Address,\n tokenOut: Address,\n maxHops = 3,\n): PathKey[][] {\n const results: PathKey[][] = [];\n\n function dfs(\n currentToken: Address,\n currentPath: PathKey[],\n usedPoolIndices: Set<number>,\n ) {\n if (currentPath.length > maxHops) return;\n\n // Check if we've reached the destination\n if (\n currentPath.length > 0 &&\n currentToken.toLowerCase() === tokenOut.toLowerCase()\n ) {\n results.push([...currentPath]);\n return;\n }\n\n for (let i = 0; i < pools.length; i++) {\n if (usedPoolIndices.has(i)) continue;\n\n const pool = pools[i]!;\n const c0 = pool.currency0.toLowerCase();\n const c1 = pool.currency1.toLowerCase();\n const curr = currentToken.toLowerCase();\n\n let nextToken: Address | null = null;\n if (curr === c0) {\n nextToken = pool.currency1 as Address;\n } else if (curr === c1) {\n nextToken = pool.currency0 as Address;\n }\n\n if (!nextToken) continue;\n\n const hop: PathKey = {\n intermediateCurrency: nextToken,\n fee: pool.fee,\n tickSpacing: pool.tickSpacing,\n hooks: pool.hooks ?? ZERO_ADDRESS,\n hookData: \"0x\",\n };\n\n usedPoolIndices.add(i);\n currentPath.push(hop);\n dfs(nextToken, currentPath, usedPoolIndices);\n currentPath.pop();\n usedPoolIndices.delete(i);\n }\n }\n\n dfs(tokenIn, [], new Set());\n return results;\n}\n","import type { Address, PublicClient } from \"viem\";\nimport type { BestQuote, PathKey, PoolKey, QuoteResult } from \"@pafi-dev/core\";\nimport { v4QuoterAbi } from \"@pafi-dev/core\";\nimport { buildAllPaths } from \"./routes\";\nimport { COMMON_POOLS, V4_QUOTER_ADDRESSES } from \"@pafi-dev/core\";\n\n/**\n * Quote exact-input for a multi-hop path.\n */\nexport async function quoteExactInput(\n client: PublicClient,\n quoterAddress: Address,\n exactCurrency: Address,\n path: PathKey[],\n exactAmount: bigint,\n): Promise<QuoteResult> {\n const [amountOut, gasEstimate] = await client.readContract({\n address: quoterAddress,\n abi: v4QuoterAbi,\n functionName: \"quoteExactInput\",\n args: [{ exactCurrency, path, exactAmount: BigInt(exactAmount) as unknown as number }],\n }) as [bigint, bigint];\n\n return { amountOut, gasEstimate, path };\n}\n\n/**\n * Quote exact-input for a single-hop swap, given an explicit PoolKey and direction.\n */\nexport async function quoteExactInputSingle(\n client: PublicClient,\n quoterAddress: Address,\n poolKey: PoolKey,\n zeroForOne: boolean,\n exactAmount: bigint,\n hookData: `0x${string}`,\n): Promise<{ amountOut: bigint; gasEstimate: bigint }> {\n const [amountOut, gasEstimate] = await client.readContract({\n address: quoterAddress,\n abi: v4QuoterAbi,\n functionName: \"quoteExactInputSingle\",\n args: [\n {\n poolKey,\n zeroForOne,\n exactAmount: BigInt(exactAmount) as unknown as number,\n hookData,\n },\n ],\n }) as [bigint, bigint];\n\n return { amountOut, gasEstimate };\n}\n\n/**\n * Try multiple PathKey[] routes and return the best quote plus all results.\n * Routes that fail (e.g. pool does not exist) are silently skipped.\n *\n * Uses viem multicall to batch all quotes into a single RPC call for speed.\n */\nexport async function quoteBestRoute(\n client: PublicClient,\n quoterAddress: Address,\n exactCurrency: Address,\n routes: PathKey[][],\n exactAmount: bigint,\n): Promise<BestQuote> {\n const results = await client.multicall({\n contracts: routes.map((path) => ({\n address: quoterAddress,\n abi: v4QuoterAbi,\n functionName: \"quoteExactInput\" as const,\n args: [\n {\n exactCurrency,\n path,\n exactAmount: BigInt(exactAmount) as unknown as number,\n },\n ],\n })),\n allowFailure: true,\n });\n\n const allRoutes: QuoteResult[] = [];\n for (let i = 0; i < results.length; i++) {\n const r = results[i]!;\n if (r.status === \"success\") {\n const [amountOut, gasEstimate] = r.result as unknown as [bigint, bigint];\n allRoutes.push({ amountOut, gasEstimate, path: routes[i]! });\n }\n }\n\n if (allRoutes.length === 0) {\n throw new Error(\"No valid routes found\");\n }\n\n const bestRoute = allRoutes.reduce((best, current) =>\n current.amountOut > best.amountOut ? current : best,\n );\n\n return { bestRoute, allRoutes };\n}\n\n/**\n * Find and quote the best swap route from `tokenIn` to `tokenOut`.\n *\n * Combines the caller's `pools` with `COMMON_POOLS[chainId]`, builds all\n * possible paths (up to `maxHops`), then quotes them all via a single\n * multicall and returns the best result.\n *\n * @param client - viem PublicClient\n * @param chainId - Chain ID (used to look up COMMON_POOLS and V4_QUOTER_ADDRESSES)\n * @param tokenIn - Input token address\n * @param tokenOut - Desired output token address\n * @param exactAmount - Exact input amount\n * @param pools - Additional pools to consider (e.g. point-token-specific)\n * @param quoterAddress - Override the default V4 Quoter address for this chain\n * @param maxHops - Maximum number of hops per path (default 3)\n */\nexport async function findBestQuote(\n client: PublicClient,\n chainId: number,\n tokenIn: Address,\n tokenOut: Address,\n exactAmount: bigint,\n pools: PoolKey[] = [],\n quoterAddress?: Address,\n maxHops = 3,\n): Promise<BestQuote> {\n const quoter = quoterAddress ?? V4_QUOTER_ADDRESSES[chainId];\n if (!quoter) {\n throw new Error(`No V4 Quoter address configured for chain ${chainId}`);\n }\n\n const commonPools = COMMON_POOLS[chainId] ?? [];\n const allPools = [...pools, ...commonPools];\n const paths = buildAllPaths(allPools, tokenIn, tokenOut, maxHops);\n\n if (paths.length === 0) {\n throw new Error(`No paths found from ${tokenIn} to ${tokenOut}`);\n }\n\n return quoteBestRoute(client, quoter, tokenIn, paths, exactAmount);\n}\n","import { encodeFunctionData } from \"viem\";\nimport type { Address, Hex, PublicClient } from \"viem\";\nimport { erc20Abi } from \"@pafi-dev/core\";\nimport { permit2Abi } from \"@pafi-dev/core\";\n\nexport async function checkAllowance(\n client: PublicClient,\n token: Address,\n owner: Address,\n spender: Address,\n): Promise<bigint> {\n return client.readContract({\n address: token,\n abi: erc20Abi,\n functionName: \"allowance\",\n args: [owner, spender],\n });\n}\n\n/**\n * Encode an ERC-20 approve(spender, amount) call.\n */\nexport function buildErc20ApprovalCalldata(\n spender: Address,\n amount: bigint,\n): Hex {\n return encodeFunctionData({\n abi: erc20Abi,\n functionName: \"approve\",\n args: [spender, amount],\n });\n}\n\n/**\n * Encode a Permit2 approve(token, spender, amount, expiration) call.\n */\nexport function buildPermit2ApprovalCalldata(\n token: Address,\n spender: Address,\n amount: bigint,\n expiration: number,\n): Hex {\n return encodeFunctionData({\n abi: permit2Abi,\n functionName: \"approve\",\n args: [token, spender, amount, expiration],\n });\n}\n","import { encodeAbiParameters, encodePacked } from \"viem\";\nimport type { Address, Hex } from \"viem\";\nimport type { PathKey, QuoteResult } from \"@pafi-dev/core\";\n\n// -------------------------------------------------------------------------\n// V4 UniversalRouter command / action constants\n// Reference: https://github.com/Uniswap/v4-periphery/blob/main/src/libraries/Actions.sol\n// -------------------------------------------------------------------------\n\n/** UniversalRouter command byte for V4 swap */\nexport const V4_SWAP = 0x10 as const;\n\n/** V4 actions */\nexport const SWAP_EXACT_IN_SINGLE = 0x06 as const;\nexport const SWAP_EXACT_IN = 0x07 as const;\nexport const SETTLE_ALL = 0x0c as const;\nexport const TAKE_ALL = 0x0f as const;\n\n// -------------------------------------------------------------------------\n// ABI type strings matching Uniswap's V4Planner encoding\n// Reference: https://github.com/Uniswap/sdks/blob/main/sdks/v4-sdk/src/utils/v4Planner.ts\n//\n// IMPORTANT: PathKey.fee is uint256 in the V4 Router ABI (not uint24).\n// -------------------------------------------------------------------------\n\n// PathKey components for V4 Router ABI encoding.\n// IMPORTANT: fee is uint256 in the V4 Router (not uint24 as in PoolKey).\n// Reference: https://github.com/Uniswap/sdks/blob/main/sdks/v4-sdk/src/utils/v4Planner.ts\nconst PATH_KEY_ABI_COMPONENTS = [\n { name: \"intermediateCurrency\", type: \"address\" },\n { name: \"fee\", type: \"uint256\" },\n { name: \"tickSpacing\", type: \"int24\" },\n { name: \"hooks\", type: \"address\" },\n { name: \"hookData\", type: \"bytes\" },\n] as const;\n\nconst EXACT_INPUT_PARAMS_ABI = [\n { name: \"currencyIn\", type: \"address\" },\n {\n name: \"path\",\n type: \"tuple[]\",\n components: PATH_KEY_ABI_COMPONENTS,\n },\n { name: \"amountIn\", type: \"uint128\" },\n { name: \"amountOutMinimum\", type: \"uint128\" },\n] as const;\n\n/**\n * Build the calldata inputs[0] (the V4_SWAP command payload) for\n * UniversalRouter.execute.\n *\n * Actions encoded: SWAP_EXACT_IN → SETTLE_ALL → TAKE_ALL\n *\n * Encoding matches the Uniswap V4 SDK's V4Planner — each action's params\n * are individually ABI-encoded, then wrapped together with the action bytes.\n */\nexport function buildV4SwapInput(\n currencyIn: Address,\n path: PathKey[],\n amountIn: bigint,\n minAmountOut: bigint,\n outputCurrency: Address,\n): Hex {\n const actions = encodePacked(\n [\"uint8\", \"uint8\", \"uint8\"],\n [SWAP_EXACT_IN, SETTLE_ALL, TAKE_ALL],\n );\n\n // Param 0: ExactInputParams — encoded as a single tuple so the CalldataDecoder\n // can locate it via a single offset pointer. fee is uint256 per V4 Router spec.\n const swapParam = encodeAbiParameters(\n [{ name: \"swap\", type: \"tuple\", components: EXACT_INPUT_PARAMS_ABI }],\n [\n {\n currencyIn,\n path: path.map((p) => ({\n intermediateCurrency: p.intermediateCurrency,\n fee: BigInt(p.fee),\n tickSpacing: p.tickSpacing,\n hooks: p.hooks,\n hookData: p.hookData,\n })),\n amountIn,\n amountOutMinimum: minAmountOut,\n },\n ],\n );\n\n // Param 1: SETTLE_ALL { currency, maxAmount }\n const settleParam = encodeAbiParameters(\n [\n { name: \"currency\", type: \"address\" },\n { name: \"maxAmount\", type: \"uint256\" },\n ],\n [currencyIn, amountIn],\n );\n\n // Param 2: TAKE_ALL { currency, minAmount }\n const takeParam = encodeAbiParameters(\n [\n { name: \"currency\", type: \"address\" },\n { name: \"minAmount\", type: \"uint256\" },\n ],\n [outputCurrency, minAmountOut],\n );\n\n return encodeAbiParameters(\n [\n { name: \"actions\", type: \"bytes\" },\n { name: \"params\", type: \"bytes[]\" },\n ],\n [actions, [swapParam, settleParam, takeParam]],\n );\n}\n\n/**\n * Build the full commands + inputs args for UniversalRouter.execute.\n */\nexport function buildUniversalRouterExecuteArgs(\n currencyIn: Address,\n path: PathKey[],\n amountIn: bigint,\n minAmountOut: bigint,\n outputCurrency: Address,\n): { commands: Hex; inputs: Hex[] } {\n const commands = encodePacked([\"uint8\"], [V4_SWAP]);\n const inputs: Hex[] = [\n buildV4SwapInput(currencyIn, path, amountIn, minAmountOut, outputCurrency),\n ];\n return { commands, inputs };\n}\n\n/**\n * Build UniversalRouter execute args from a quote result.\n *\n * Takes the output of `findBestQuote` / `quoteBestRoute` and produces\n * ready-to-use `{ commands, inputs }` for `UniversalRouter.execute`.\n *\n * @param quote - Quote result containing the path\n * @param currencyIn - Input token address\n * @param currencyOut - Output token address\n * @param amountIn - Exact input amount (same value passed to the quoter)\n * @param minAmountOut - Minimum acceptable output (caller applies slippage)\n *\n * @deprecated Since v1.4 — the Issuer App no longer handles swaps.\n * For the new PT→USDT batch call on PAFI Web (Scenario 4 with\n * EIP-7702 gas deduction), use `buildSwapWithGasDeduction()` (v1.5).\n * This helper is kept for legacy v0.2.x consumers; will be removed in v2.0.\n */\nexport function buildSwapFromQuote(params: {\n quote: QuoteResult;\n currencyIn: Address;\n currencyOut: Address;\n amountIn: bigint;\n minAmountOut: bigint;\n}): { commands: Hex; inputs: Hex[] } {\n return buildUniversalRouterExecuteArgs(\n params.currencyIn,\n params.quote.path,\n params.amountIn,\n params.minAmountOut,\n params.currencyOut,\n );\n}\n","import type { Address, Hex, PublicClient } from \"viem\";\nimport { universalRouterAbi } from \"@pafi-dev/core\";\nimport { SimulationError } from \"@pafi-dev/core\";\n\nexport interface SwapSimulationResult {\n success: boolean;\n gasEstimate: bigint;\n}\n\n/**\n * Simulate a UniversalRouter.execute swap call via eth_call (no gas spent).\n *\n * Runs the full V4 swap flow — token transfer via Permit2, swap via\n * PoolManager, output settlement — without submitting a transaction.\n * If the simulation reverts, throws a `SimulationError` with the reason.\n *\n * @param client - viem PublicClient\n * @param routerAddress - UniversalRouter contract address\n * @param commands - Packed command bytes (from buildSwapFromQuote)\n * @param inputs - Encoded inputs per command (from buildSwapFromQuote)\n * @param deadline - Unix timestamp after which the tx expires\n * @param from - Address that will execute the swap\n */\nexport async function simulateSwap(\n client: PublicClient,\n routerAddress: Address,\n commands: Hex,\n inputs: Hex[],\n deadline: bigint,\n from: Address,\n): Promise<SwapSimulationResult> {\n try {\n const gasEstimate = await client.estimateContractGas({\n address: routerAddress,\n abi: universalRouterAbi,\n functionName: \"execute\",\n args: [commands, inputs, deadline],\n account: from,\n });\n\n return { success: true, gasEstimate };\n } catch (error: unknown) {\n const message =\n error instanceof Error ? error.message : \"Unknown simulation error\";\n throw new SimulationError(\"swap\", message);\n }\n}\n","import { encodeFunctionData } from \"viem\";\nimport type { Address, Hex } from \"viem\";\nimport {\n PERMIT2_ADDRESS,\n buildPartialUserOperation,\n erc20ApproveOp,\n erc20TransferOp,\n rawCallOp,\n universalRouterAbi,\n type Operation,\n type PartialUserOperation,\n type PathKey,\n} from \"@pafi-dev/core\";\nimport { buildUniversalRouterExecuteArgs } from \"./universalRouter\";\nimport { buildPermit2ApprovalCalldata } from \"./approval\";\n\n/**\n * v0.3 — Generalized swap UserOp builder. Direction-agnostic: works\n * for **any** ERC-20 → ERC-20 pair routable through PAFI's V4 pools:\n *\n * - PT → USDT (cashout)\n * - USDT → PT (buy PT with USDT)\n * - PT0 → PT1 (same-issuer multi-token swap; routes via USDT)\n *\n * UserOp shape (atomic batch via EIP-7702 BatchExecutor):\n *\n * 1. `inputToken.approve(Permit2, amountIn)` — exactly the swap amount.\n * 2. `Permit2.approve(inputToken, router, amountIn, deadline)` — Permit2\n * authorization to UniversalRouter.\n * 3. `UniversalRouter.execute(commands, inputs, deadline)` — V4 swap\n * `inputToken → outputToken`; user receives ≥ `minAmountOut`.\n * 4. `outputToken.transfer(feeRecipient, gasFeeAmountOutput)` —\n * operator gas reimbursement, paid in OUTPUT token (omitted when 0).\n *\n * ## Fee model — output-token strategy (token-availability rule)\n *\n * Fee is charged in the **output token** AFTER the swap, because the\n * user holds output token at that point in the batch. Three benefits:\n *\n * - User holds exactly `amountIn` of input token (FE quote = chain\n * reality). No \"you need 1010 to swap 1000\" UX surprise.\n * - Quote API returns `outputNet = outputGross - feeOutput` directly,\n * matching what user actually receives.\n * - PT → USDT: PAFI receives USDT (universally liquid, clean accounting).\n *\n * Caller MUST ensure `minAmountOut >= gasFeeAmountOutput` (fee transfer\n * reverts otherwise → whole batch reverts).\n *\n * ## PAFI Hook fee\n *\n * The V4 PAFIHook charges 10% on PT → USDT direction (one-way). The\n * 10% is taken at pool level inside `UniversalRouter.execute`, so this\n * builder doesn't model it explicitly — it shows up as a smaller\n * `amountOut` from `findBestQuote`.\n *\n * - PT → USDT: pool charges 10% (output reduced)\n * - USDT → PT: no hook fee\n * - PT0 → PT1: 10% on PT0 → USDT leg, 0% on USDT → PT1 leg\n */\nexport interface BuildSwapUserOpParams {\n /** User's EOA (with EIP-7702 delegation to BatchExecutor). */\n userAddress: Address;\n /** ERC-4337 account nonce — fetched from EntryPoint by the caller. */\n aaNonce: bigint;\n\n /** Token being spent — approved to Permit2 + UniversalRouter. */\n inputTokenAddress: Address;\n /** Token user receives. */\n outputTokenAddress: Address;\n /** UniversalRouter contract address (chain-specific). */\n universalRouterAddress: Address;\n\n /** Input token units to swap. User must hold exactly this much input token. */\n amountIn: bigint;\n /**\n * Minimum output (gross) to accept from the swap. Caller applies\n * slippage against a fresh quote. MUST be ≥ `gasFeeAmountOutput` or\n * the post-swap fee transfer reverts.\n */\n minAmountOut: bigint;\n\n /**\n * V4 pool path for the swap. Single-hop = 1 PathKey, multi-hop = N.\n * Get this from `findBestQuote().bestRoute.path`.\n */\n swapPath: PathKey[];\n /** Unix seconds. After this, the router rejects the swap. */\n deadline: bigint;\n\n /**\n * Operator gas-reimbursement fee — paid in OUTPUT token. Omitted\n * from the batch when 0n. Caller quotes this via `quoteOperatorFee*`\n * helpers (USDT/PT depending on output token).\n *\n * v0.3 — switched from input-side fee (v0.2 `gasFeeAmount`) to\n * output-side fee. See \"Fee model\" comment above.\n */\n gasFeeAmountOutput: bigint;\n /**\n * Where the gas fee lands — typically the canonical PAFI fee\n * recipient from `getContractAddresses(chainId).pafiFeeRecipient`.\n */\n feeRecipient: Address;\n\n /** Override ERC-4337 gas estimates. Defaults are conservative. */\n gasLimits?: {\n callGasLimit?: bigint;\n verificationGasLimit?: bigint;\n preVerificationGas?: bigint;\n };\n}\n\n/**\n * Build an unsigned UserOp for the generalized swap flow.\n *\n * @throws when `amountIn` is non-positive, `gasFeeAmountOutput` is\n * negative, `swapPath` is empty, or `minAmountOut < gasFeeAmountOutput`.\n */\nexport function buildSwapUserOp(\n params: BuildSwapUserOpParams,\n): PartialUserOperation {\n if (params.amountIn <= 0n) {\n throw new Error(\"buildSwapUserOp: amountIn must be positive\");\n }\n if (params.minAmountOut < 0n) {\n throw new Error(\"buildSwapUserOp: minAmountOut must be non-negative\");\n }\n if (params.gasFeeAmountOutput < 0n) {\n throw new Error(\"buildSwapUserOp: gasFeeAmountOutput must be non-negative\");\n }\n if (\n params.gasFeeAmountOutput > 0n &&\n params.minAmountOut < params.gasFeeAmountOutput\n ) {\n throw new Error(\n \"buildSwapUserOp: minAmountOut must be >= gasFeeAmountOutput so the post-swap fee transfer cannot revert\",\n );\n }\n if (params.swapPath.length === 0) {\n throw new Error(\n \"buildSwapUserOp: swapPath must contain at least one PathKey\",\n );\n }\n\n const { commands, inputs } = buildUniversalRouterExecuteArgs(\n params.inputTokenAddress,\n params.swapPath,\n params.amountIn,\n params.minAmountOut,\n params.outputTokenAddress,\n );\n\n const swapCallData: Hex = encodeFunctionData({\n abi: universalRouterAbi,\n functionName: \"execute\",\n args: [commands, inputs, params.deadline],\n });\n\n // Approve only the swap amount — fee comes out of swap proceeds.\n const permit2ApproveData: Hex = buildPermit2ApprovalCalldata(\n params.inputTokenAddress,\n params.universalRouterAddress,\n params.amountIn,\n Number(params.deadline),\n );\n\n const operations: Operation[] = [\n erc20ApproveOp(params.inputTokenAddress, PERMIT2_ADDRESS, params.amountIn),\n rawCallOp(PERMIT2_ADDRESS, permit2ApproveData),\n rawCallOp(params.universalRouterAddress, swapCallData),\n ];\n\n // Fee transfer in OUTPUT token, AFTER swap, when user holds output.\n if (params.gasFeeAmountOutput > 0n) {\n operations.push(\n erc20TransferOp(\n params.outputTokenAddress,\n params.feeRecipient,\n params.gasFeeAmountOutput,\n ),\n );\n }\n\n return buildPartialUserOperation({\n sender: params.userAddress,\n nonce: params.aaNonce,\n operations,\n gasLimits: {\n callGasLimit: params.gasLimits?.callGasLimit ?? 700_000n,\n verificationGasLimit: params.gasLimits?.verificationGasLimit ?? 150_000n,\n preVerificationGas: params.gasLimits?.preVerificationGas ?? 50_000n,\n },\n });\n}\n","// Re-export from @pafi-dev/core — fetchPafiPools lives in core so all\n// SDK packages share one implementation.\nexport { fetchPafiPools, PAFI_SUBGRAPH_URL } from \"@pafi-dev/core\";\n"],"mappings":"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","names":["COMMON_POOLS","encodeFunctionData","universalRouterAbi","encodeFunctionData","universalRouterAbi","userOp"]}
|
|
1
|
+
{"version":3,"sources":["../src/api/handlers.ts","../src/quoting/routes.ts","../src/quoting/quote.ts","../src/swap/approval.ts","../src/swap/universalRouter.ts","../src/swap/simulate.ts","../src/swap/buildSwap.ts","../src/pools.ts"],"sourcesContent":["import { getAddress } from \"viem\";\nimport type { Address, PublicClient } from \"viem\";\nimport {\n buildPerpDepositWithGasDeduction,\n buildPerpDepositViaRelay,\n ORDERLY_RELAY_ABI,\n getContractAddresses,\n UNIVERSAL_ROUTER_ADDRESSES,\n ORDERLY_VAULT_ABI,\n ORDERLY_VAULT_ADDRESSES,\n BROKER_HASHES,\n TOKEN_HASHES,\n computeAccountId,\n quoteOperatorFeePt,\n quoteOperatorFeeUsdt,\n} from \"@pafi-dev/core\";\nimport { findBestQuote } from \"../quoting\";\nimport { buildSwapUserOp } from \"../swap\";\nimport type {\n ApiQuoteRequest,\n ApiQuoteResponse,\n ApiSwapRequest,\n ApiSwapResponse,\n ApiPerpDepositRequest,\n ApiPerpDepositResponse,\n} from \"./types\";\n\nexport interface TradingHandlersConfig {\n provider: PublicClient;\n chainId: number;\n}\n\n/**\n * Framework-agnostic handlers for on-chain trading actions.\n *\n * All handlers are stateless — they need only a PublicClient for RPC\n * calls. No ledger, no signer, no DB. Issuers wrap these in their own\n * HTTP controllers (Express / NestJS / Hono / etc.) the same way they\n * wrap `IssuerApiHandlers` from `@pafi-dev/issuer`.\n *\n * Example (NestJS):\n *\n * const trading = new TradingHandlers({ provider, chainId });\n *\n * // GET /quote\n * const quote = await trading.handleQuote({ chainId, pointTokenAddress, amount, pools });\n *\n * // POST /swap\n * const swap = await trading.handleSwap({ chainId, userAddress, pointTokenAddress, amount, aaNonce });\n *\n * // POST /perp-deposit\n * const deposit = await trading.handlePerpDeposit({ chainId, userAddress, amount, aaNonce, brokerId });\n */\nexport class TradingHandlers {\n private readonly provider: PublicClient;\n private readonly chainId: number;\n\n constructor(config: TradingHandlersConfig) {\n this.provider = config.provider;\n this.chainId = config.chainId;\n }\n\n // =========================================================================\n // GET /quote\n // =========================================================================\n\n /**\n * Quote exact-input PT → USDT via Uniswap V4 on-chain Quoter.\n *\n * Uses multicall to batch all candidate routes into a single RPC call.\n * Returns `quoteError: \"QUOTE_UNAVAILABLE\"` when no pool/path exists\n * rather than throwing, so callers can show a soft \"unavailable\" UI\n * state without 500-ing.\n */\n async handleQuote(request: ApiQuoteRequest): Promise<ApiQuoteResponse> {\n if (request.chainId !== this.chainId) {\n throw new Error(`handleQuote: unsupported chainId ${request.chainId}`);\n }\n if (request.amount === 0n) {\n return {\n inputAmount: 0n,\n estimatedOutputAmount: 0n,\n outputNet: 0n,\n feeAmountOutput: 0n,\n gasEstimate: 0n,\n };\n }\n\n const inputTokenAddress = getAddress(request.inputTokenAddress);\n const outputTokenAddress = getAddress(request.outputTokenAddress);\n const pools = request.pools ?? [];\n\n try {\n const best = await findBestQuote(\n this.provider,\n request.chainId,\n inputTokenAddress,\n outputTokenAddress,\n request.amount,\n pools,\n );\n const feeAmountOutput = await quoteOperatorFeeOutput(\n this.provider,\n request.chainId,\n outputTokenAddress,\n ).catch(() => 0n);\n const outputNet =\n best.bestRoute.amountOut > feeAmountOutput\n ? best.bestRoute.amountOut - feeAmountOutput\n : 0n;\n return {\n inputAmount: request.amount,\n estimatedOutputAmount: best.bestRoute.amountOut,\n outputNet,\n feeAmountOutput,\n gasEstimate: best.bestRoute.gasEstimate,\n };\n } catch {\n return {\n inputAmount: request.amount,\n estimatedOutputAmount: 0n,\n outputNet: 0n,\n feeAmountOutput: 0n,\n gasEstimate: 0n,\n quoteError: \"QUOTE_UNAVAILABLE\",\n };\n }\n }\n\n // =========================================================================\n // POST /swap\n // =========================================================================\n\n /**\n * Build a swap UserOp (direction-agnostic).\n *\n * Quotes the best route, applies slippage, then encodes a 4-step\n * batch: input.approve → Permit2.approve → UniversalRouter.execute →\n * output.transfer (fee in OUTPUT token, omitted when fee = 0).\n *\n * v0.3 — Fee model is OUTPUT-side (token-availability rule). User\n * holds exactly `amountIn` of input; receives `outputGross - fee`\n * net. Quote response surfaces both `estimatedOutputAmount` (gross)\n * and `outputNet` so the FE can display reality.\n *\n * v0.3.1 — `authenticatedAddress` first param. Caller (issuer\n * controller / FE proxy) MUST pass the address extracted from the\n * verified session/JWT. Handler asserts it equals\n * `request.userAddress`. See SDK_CORE_TRADING_AUDIT.md C6.\n */\n async handleSwap(\n authenticatedAddress: Address,\n request: ApiSwapRequest,\n ): Promise<ApiSwapResponse> {\n if (\n getAddress(authenticatedAddress) !== getAddress(request.userAddress)\n ) {\n throw new Error(\n `handleSwap: authenticatedAddress (${authenticatedAddress}) does not match request.userAddress (${request.userAddress})`,\n );\n }\n if (request.chainId !== this.chainId) {\n throw new Error(`handleSwap: unsupported chainId ${request.chainId}`);\n }\n if (request.amount <= 0n) {\n throw new Error(\"handleSwap: amount must be positive\");\n }\n\n const { pafiFeeRecipient } = getContractAddresses(request.chainId);\n const universalRouter = UNIVERSAL_ROUTER_ADDRESSES[request.chainId];\n if (!universalRouter) {\n throw new Error(`handleSwap: no UniversalRouter for chainId ${request.chainId}`);\n }\n\n const inputTokenAddress = getAddress(request.inputTokenAddress);\n const outputTokenAddress = getAddress(request.outputTokenAddress);\n const userAddress = getAddress(request.userAddress);\n const pools = request.pools ?? [];\n\n let quoteResult: Awaited<ReturnType<typeof findBestQuote>>;\n try {\n quoteResult = await findBestQuote(\n this.provider,\n request.chainId,\n inputTokenAddress,\n outputTokenAddress,\n request.amount,\n pools,\n );\n } catch {\n throw new Error(\n `handleSwap: no swap path found from ${inputTokenAddress} to ${outputTokenAddress}`,\n );\n }\n\n // Resolve the operator fee in OUTPUT token units. Auto-quote uses\n // the right underlying quoter based on output token (USDT vs PT).\n const gasFeeAmountOutput =\n request.gasFeeAmountOutput !== undefined\n ? request.gasFeeAmountOutput\n : await quoteOperatorFeeOutput(\n this.provider,\n request.chainId,\n outputTokenAddress,\n ).catch(() => 0n);\n\n const hops = quoteResult.bestRoute.path.length;\n // Multi-hop routes compound slippage across legs — bias higher when\n // caller didn't override. 50 bps single-hop, 100 bps multi-hop.\n const slippageBps =\n request.slippageBps ?? (hops > 1 ? 100 : 50);\n\n const estimatedOutputAmount = quoteResult.bestRoute.amountOut;\n const minAmountOut = (estimatedOutputAmount * BigInt(10000 - slippageBps)) / 10000n;\n\n if (gasFeeAmountOutput > 0n && minAmountOut < gasFeeAmountOutput) {\n throw new Error(\n `handleSwap: minAmountOut (${minAmountOut}) below operator fee (${gasFeeAmountOutput}) — increase swap amount or accept tighter slippage`,\n );\n }\n\n const deadline = BigInt(Math.floor(Date.now() / 1000) + 5 * 60);\n\n const userOp = buildSwapUserOp({\n userAddress,\n aaNonce: request.aaNonce,\n inputTokenAddress,\n outputTokenAddress,\n universalRouterAddress: universalRouter,\n amountIn: request.amount,\n minAmountOut,\n swapPath: quoteResult.bestRoute.path,\n deadline,\n gasFeeAmountOutput,\n feeRecipient: pafiFeeRecipient,\n });\n\n // Fee-stripped fallback for paymaster-refused path.\n const userOpFallback =\n gasFeeAmountOutput > 0n\n ? buildSwapUserOp({\n userAddress,\n aaNonce: request.aaNonce,\n inputTokenAddress,\n outputTokenAddress,\n universalRouterAddress: universalRouter,\n amountIn: request.amount,\n minAmountOut,\n swapPath: quoteResult.bestRoute.path,\n deadline,\n gasFeeAmountOutput: 0n,\n feeRecipient: pafiFeeRecipient,\n })\n : undefined;\n\n return {\n userOp,\n userOpFallback,\n estimatedOutputAmount,\n minAmountOut,\n hops,\n deadline,\n feeAmountUsed: gasFeeAmountOutput,\n feeRecipient: pafiFeeRecipient,\n };\n }\n\n // =========================================================================\n // POST /perp-deposit\n // =========================================================================\n\n /**\n * Build an Orderly perp deposit UserOp.\n *\n * Default path is the **PAFI Orderly Relay** (`viaRelay: true`):\n * USDC.approve(relay) + relay.deposit(req). The Relay holds an ETH\n * reserve and pays Orderly's LayerZero `msg.value` out of it; the\n * user pays a USDC fee (quoted via `Relay.quoteTokenFee`) instead.\n * No native ETH on the user wallet is required, so paymaster\n * sponsorship of the ERC-4337 gas is sufficient end-to-end.\n *\n * Fallback path (`viaRelay: false`): direct `Vault.deposit{value}`.\n * Reserved for chains where no Relay is deployed — the user wallet\n * **must** hold `layerZeroFee` as native ETH.\n *\n * The Relay path automatically falls back to Vault when\n * `getContractAddresses(chainId).orderlyRelay` is the placeholder\n * sentinel (Relay not deployed for that chain).\n */\n async handlePerpDeposit(\n authenticatedAddress: Address,\n request: ApiPerpDepositRequest,\n ): Promise<ApiPerpDepositResponse> {\n // v0.3.1 — auth-context check. See SDK_CORE_TRADING_AUDIT.md C6.\n if (\n getAddress(authenticatedAddress) !== getAddress(request.userAddress)\n ) {\n throw new Error(\n `handlePerpDeposit: authenticatedAddress (${authenticatedAddress}) does not match request.userAddress (${request.userAddress})`,\n );\n }\n if (request.chainId !== this.chainId) {\n throw new Error(`handlePerpDeposit: unsupported chainId ${request.chainId}`);\n }\n if (request.amount <= 0n) {\n throw new Error(\"handlePerpDeposit: amount must be positive\");\n }\n\n const vault = ORDERLY_VAULT_ADDRESSES[request.chainId];\n if (!vault) {\n throw new Error(`handlePerpDeposit: no Orderly Vault for chainId ${request.chainId}`);\n }\n\n const brokerHash = BROKER_HASHES[request.brokerId as keyof typeof BROKER_HASHES];\n if (!brokerHash) {\n throw new Error(`handlePerpDeposit: unknown brokerId \"${request.brokerId}\"`);\n }\n const tokenHash = TOKEN_HASHES.USDC;\n const userAddress = getAddress(request.userAddress);\n\n const [usdcAddress, brokerAllowed] = await Promise.all([\n this.provider.readContract({\n address: vault,\n abi: ORDERLY_VAULT_ABI,\n functionName: \"getAllowedToken\",\n args: [tokenHash],\n }) as Promise<Address>,\n this.provider.readContract({\n address: vault,\n abi: ORDERLY_VAULT_ABI,\n functionName: \"getAllowedBroker\",\n args: [brokerHash],\n }) as Promise<boolean>,\n ]);\n\n if (!brokerAllowed) {\n throw new Error(\n `handlePerpDeposit: broker \"${request.brokerId}\" is not whitelisted on Orderly Vault`,\n );\n }\n\n const accountId = computeAccountId(userAddress, brokerHash);\n const depositData = {\n accountId,\n brokerHash,\n tokenHash,\n tokenAmount: request.amount,\n };\n\n // Always read layerZeroFee for response — even on the Relay path\n // it's useful informational output (lets the FE show \"Relay\n // covers ~X ETH for you\").\n const layerZeroFee = (await this.provider.readContract({\n address: vault,\n abi: ORDERLY_VAULT_ABI,\n functionName: \"getDepositFee\",\n args: [userAddress, depositData],\n })) as bigint;\n\n const useRelay = request.viaRelay !== false;\n const { orderlyRelay: relayAddress, pafiFeeRecipient } =\n getContractAddresses(request.chainId);\n const relayDeployed = !isPlaceholderAddress(relayAddress);\n\n // Resolve operator fee in USDC (input-token). Only applied to the\n // Relay path; the legacy direct-Vault path skips operator fee\n // entirely (it's already gas-heavy due to the user-paid\n // LayerZero msg.value, and PAFI doesn't sponsor that path on Base\n // anyway).\n const gasFeeUsdc =\n request.gasFeeUsdc !== undefined\n ? request.gasFeeUsdc\n : useRelay && relayDeployed\n ? await quoteOperatorFeeUsdt({\n provider: this.provider,\n chainId: request.chainId,\n }).catch(() => 0n)\n : 0n;\n\n if (useRelay && relayDeployed) {\n // Cap = max(amount * 5%, 2 USDC). The Relay fee is a flat USDC\n // amount derived from the LayerZero ETH cost at oracle price —\n // it does NOT scale with deposit size. A pure percentage cap\n // breaks for small deposits (e.g. 0.01 USDC test deposit, 5%\n // cap = 500 wei < 14k wei real fee). The 2 USDC floor covers\n // normal LayerZero pricing on Base; the 5% slope still guards\n // against oracle spikes on large deposits.\n const RELAY_FEE_FLOOR_USDC = 2_000_000n; // 2 USDC (6 decimals)\n const percentCap = (request.amount * 500n) / 10_000n;\n const maxRelayFee =\n request.maxRelayFee ??\n (percentCap > RELAY_FEE_FLOOR_USDC ? percentCap : RELAY_FEE_FLOOR_USDC);\n\n const relayRequest = {\n token: usdcAddress,\n receiver: userAddress,\n brokerHash,\n totalAmount: request.amount,\n maxFee: maxRelayFee,\n };\n\n const relayTokenFee = (await this.provider.readContract({\n address: relayAddress,\n abi: ORDERLY_RELAY_ABI,\n functionName: \"quoteTokenFee\",\n args: [relayRequest],\n })) as bigint;\n\n if (relayTokenFee > maxRelayFee) {\n throw new Error(\n `handlePerpDeposit: Relay tokenFee ${relayTokenFee} (≈ ${\n Number(relayTokenFee) / 1e6\n } USDC) exceeds maxRelayFee ${maxRelayFee} — pass a larger ` +\n `\\`maxRelayFee\\` or increase the deposit \\`amount\\` so the fee ` +\n `becomes a smaller share of the total.`,\n );\n }\n\n // Sanity-check: Relay forwards `(totalAmount − tokenFee)` to\n // Orderly Vault. When `tokenFee >= totalAmount` the forwarded\n // amount is zero / negative and the Relay reverts on-chain with\n // `FeeExceedsAmount(fee, totalAmount)` (selector 0x536766bf),\n // which propagates as an opaque `BatchExecutor.CallFailed(1, …)`\n // revert at simulation time. Catch this early on the client so\n // the UX shows an actionable message instead of an AA21/AA34\n // bundler error wrapped around the raw selector.\n if (relayTokenFee >= request.amount) {\n const feeUsdc = Number(relayTokenFee) / 1e6;\n const amountUsdc = Number(request.amount) / 1e6;\n throw new Error(\n `handlePerpDeposit: deposit amount ${amountUsdc} USDC is below the ` +\n `Relay fee ${feeUsdc} USDC — increase \\`amount\\` to at least ` +\n `${(feeUsdc * 2).toFixed(6)} USDC so a meaningful balance reaches ` +\n `your Orderly account after the Relay charge.`,\n );\n }\n\n // USDC fee transferred FIRST in the batch (input-token rule —\n // user holds USDC at start). Recipient = pafiFeeRecipient.\n const userOp = buildPerpDepositViaRelay({\n userAddress,\n aaNonce: request.aaNonce,\n relayAddress,\n request: relayRequest,\n gasFeeUsdc: gasFeeUsdc > 0n ? gasFeeUsdc : undefined,\n gasFeeUsdcRecipient: gasFeeUsdc > 0n ? pafiFeeRecipient : undefined,\n });\n\n // Same shape, no USDC fee transfer — for the paymaster-refused\n // fallback path. The Relay still charges its own USDC token-fee\n // (compensates LayerZero ETH spend, NOT PAFI's gas).\n const userOpFallback =\n gasFeeUsdc > 0n\n ? buildPerpDepositViaRelay({\n userAddress,\n aaNonce: request.aaNonce,\n relayAddress,\n request: relayRequest,\n })\n : undefined;\n\n return {\n userOp,\n userOpFallback,\n path: \"relay\",\n layerZeroFee,\n relayTokenFee,\n accountId,\n brokerHash,\n usdcAddress,\n relayAddress,\n feeAmountUsed: gasFeeUsdc,\n feeRecipient: pafiFeeRecipient,\n };\n }\n\n // Fallback: direct Vault.deposit{value} — user wallet MUST hold\n // `layerZeroFee` as native ETH (paymaster does not sponsor msg.value).\n const userOp = buildPerpDepositWithGasDeduction({\n userAddress,\n aaNonce: request.aaNonce,\n chainId: request.chainId,\n usdcAddress,\n amount: request.amount,\n depositData,\n layerZeroFee,\n });\n\n return {\n userOp,\n path: \"vault\",\n layerZeroFee,\n relayTokenFee: 0n,\n accountId,\n brokerHash,\n usdcAddress,\n relayAddress: vault,\n // Vault path doesn't include the PT operator fee transfer (it's\n // an unsponsored path on chains without a Relay deployment, and\n // the user is paying msg.value in ETH already). Echo 0n + the\n // canonical recipient so the response shape stays consistent.\n feeAmountUsed: 0n,\n feeRecipient: pafiFeeRecipient,\n };\n }\n}\n\n/**\n * `addresses.ts` uses `0x000…<suffix>` sentinels for chains where a\n * given contract is not yet deployed. Detect them by upper-160-bits =\n * 0 so we route to the Vault fallback automatically.\n */\nfunction isPlaceholderAddress(addr: Address): boolean {\n return /^0x0{36}[0-9a-fA-F]{4}$/i.test(addr);\n}\n\n/**\n * Quote the operator gas fee in OUTPUT token raw units. Picks the right\n * underlying quoter:\n *\n * - Output is USDT (canonical address from `getContractAddresses`):\n * `quoteOperatorFeeUsdt` — pure Chainlink ETH/USD, no V4 hop.\n * - Output is anything else (PT0, PT1, ...): `quoteOperatorFeePt`\n * against the output token's V4 pool.\n *\n * Used by `handleQuote` (display the user-facing net output) and\n * `handleSwap` (auto-quote when caller didn't override\n * `gasFeeAmountOutput`).\n */\nasync function quoteOperatorFeeOutput(\n provider: PublicClient,\n chainId: number,\n outputTokenAddress: Address,\n): Promise<bigint> {\n const { usdt } = getContractAddresses(chainId);\n if (\n usdt &&\n getAddress(outputTokenAddress) === getAddress(usdt as Address)\n ) {\n return quoteOperatorFeeUsdt({ provider, chainId });\n }\n return quoteOperatorFeePt({\n provider,\n chainId,\n pointTokenAddress: outputTokenAddress,\n });\n}\n","import type { Address } from \"viem\";\nimport type { PathKey, PoolKey } from \"@pafi-dev/core\";\nimport { COMMON_POOLS, POINT_TOKEN_POOLS } from \"@pafi-dev/core\";\n\nconst ZERO_ADDRESS = \"0x0000000000000000000000000000000000000000\" as Address;\n\n/**\n * Combine point-token-specific pools and common pools for a given chain.\n * Point token pools are listed first so callers can prioritise them.\n */\nexport function combineRoutes(\n chainId: number,\n pointTokenAddress: Address,\n): PoolKey[] {\n const commonPools = COMMON_POOLS[chainId] ?? [];\n const pointPools = POINT_TOKEN_POOLS[chainId]?.[pointTokenAddress] ?? [];\n return [...pointPools, ...commonPools];\n}\n\n/**\n * Build all possible swap paths from `tokenIn` to `tokenOut` using the given\n * pools. Returns an array of PathKey[] routes (each up to `maxHops` hops).\n *\n * Supports both direct single-hop routes and multi-hop routes through\n * intermediate tokens. Each pool is used at most once per path.\n *\n * @param pools - Available pools to route through\n * @param tokenIn - Input token address\n * @param tokenOut - Desired output token address\n * @param maxHops - Maximum number of hops (default 3)\n */\nexport function buildAllPaths(\n pools: PoolKey[],\n tokenIn: Address,\n tokenOut: Address,\n maxHops = 3,\n): PathKey[][] {\n const results: PathKey[][] = [];\n\n function dfs(\n currentToken: Address,\n currentPath: PathKey[],\n usedPoolIndices: Set<number>,\n ) {\n if (currentPath.length > maxHops) return;\n\n // Check if we've reached the destination\n if (\n currentPath.length > 0 &&\n currentToken.toLowerCase() === tokenOut.toLowerCase()\n ) {\n results.push([...currentPath]);\n return;\n }\n\n for (let i = 0; i < pools.length; i++) {\n if (usedPoolIndices.has(i)) continue;\n\n const pool = pools[i]!;\n const c0 = pool.currency0.toLowerCase();\n const c1 = pool.currency1.toLowerCase();\n const curr = currentToken.toLowerCase();\n\n let nextToken: Address | null = null;\n if (curr === c0) {\n nextToken = pool.currency1 as Address;\n } else if (curr === c1) {\n nextToken = pool.currency0 as Address;\n }\n\n if (!nextToken) continue;\n\n const hop: PathKey = {\n intermediateCurrency: nextToken,\n fee: pool.fee,\n tickSpacing: pool.tickSpacing,\n hooks: pool.hooks ?? ZERO_ADDRESS,\n hookData: \"0x\",\n };\n\n usedPoolIndices.add(i);\n currentPath.push(hop);\n dfs(nextToken, currentPath, usedPoolIndices);\n currentPath.pop();\n usedPoolIndices.delete(i);\n }\n }\n\n dfs(tokenIn, [], new Set());\n return results;\n}\n","import type { Address, PublicClient } from \"viem\";\nimport type { BestQuote, PathKey, PoolKey, QuoteResult } from \"@pafi-dev/core\";\nimport { v4QuoterAbi } from \"@pafi-dev/core\";\nimport { buildAllPaths } from \"./routes\";\nimport { COMMON_POOLS, V4_QUOTER_ADDRESSES } from \"@pafi-dev/core\";\n\n/**\n * Quote exact-input for a multi-hop path.\n */\nexport async function quoteExactInput(\n client: PublicClient,\n quoterAddress: Address,\n exactCurrency: Address,\n path: PathKey[],\n exactAmount: bigint,\n): Promise<QuoteResult> {\n const [amountOut, gasEstimate] = await client.readContract({\n address: quoterAddress,\n abi: v4QuoterAbi,\n functionName: \"quoteExactInput\",\n args: [{ exactCurrency, path, exactAmount: BigInt(exactAmount) as unknown as number }],\n }) as [bigint, bigint];\n\n return { amountOut, gasEstimate, path };\n}\n\n/**\n * Quote exact-input for a single-hop swap, given an explicit PoolKey and direction.\n */\nexport async function quoteExactInputSingle(\n client: PublicClient,\n quoterAddress: Address,\n poolKey: PoolKey,\n zeroForOne: boolean,\n exactAmount: bigint,\n hookData: `0x${string}`,\n): Promise<{ amountOut: bigint; gasEstimate: bigint }> {\n const [amountOut, gasEstimate] = await client.readContract({\n address: quoterAddress,\n abi: v4QuoterAbi,\n functionName: \"quoteExactInputSingle\",\n args: [\n {\n poolKey,\n zeroForOne,\n exactAmount: BigInt(exactAmount) as unknown as number,\n hookData,\n },\n ],\n }) as [bigint, bigint];\n\n return { amountOut, gasEstimate };\n}\n\n/**\n * Try multiple PathKey[] routes and return the best quote plus all results.\n * Routes that fail (e.g. pool does not exist) are silently skipped.\n *\n * Uses viem multicall to batch all quotes into a single RPC call for speed.\n */\nexport async function quoteBestRoute(\n client: PublicClient,\n quoterAddress: Address,\n exactCurrency: Address,\n routes: PathKey[][],\n exactAmount: bigint,\n): Promise<BestQuote> {\n const results = await client.multicall({\n contracts: routes.map((path) => ({\n address: quoterAddress,\n abi: v4QuoterAbi,\n functionName: \"quoteExactInput\" as const,\n args: [\n {\n exactCurrency,\n path,\n exactAmount: BigInt(exactAmount) as unknown as number,\n },\n ],\n })),\n allowFailure: true,\n });\n\n const allRoutes: QuoteResult[] = [];\n for (let i = 0; i < results.length; i++) {\n const r = results[i]!;\n if (r.status === \"success\") {\n const [amountOut, gasEstimate] = r.result as unknown as [bigint, bigint];\n allRoutes.push({ amountOut, gasEstimate, path: routes[i]! });\n }\n }\n\n if (allRoutes.length === 0) {\n throw new Error(\"No valid routes found\");\n }\n\n const bestRoute = allRoutes.reduce((best, current) =>\n current.amountOut > best.amountOut ? current : best,\n );\n\n return { bestRoute, allRoutes };\n}\n\n/**\n * Find and quote the best swap route from `tokenIn` to `tokenOut`.\n *\n * Combines the caller's `pools` with `COMMON_POOLS[chainId]`, builds all\n * possible paths (up to `maxHops`), then quotes them all via a single\n * multicall and returns the best result.\n *\n * @param client - viem PublicClient\n * @param chainId - Chain ID (used to look up COMMON_POOLS and V4_QUOTER_ADDRESSES)\n * @param tokenIn - Input token address\n * @param tokenOut - Desired output token address\n * @param exactAmount - Exact input amount\n * @param pools - Additional pools to consider (e.g. point-token-specific)\n * @param quoterAddress - Override the default V4 Quoter address for this chain\n * @param maxHops - Maximum number of hops per path (default 3)\n */\nexport async function findBestQuote(\n client: PublicClient,\n chainId: number,\n tokenIn: Address,\n tokenOut: Address,\n exactAmount: bigint,\n pools: PoolKey[] = [],\n quoterAddress?: Address,\n maxHops = 3,\n): Promise<BestQuote> {\n const quoter = quoterAddress ?? V4_QUOTER_ADDRESSES[chainId];\n if (!quoter) {\n throw new Error(`No V4 Quoter address configured for chain ${chainId}`);\n }\n\n const commonPools = COMMON_POOLS[chainId] ?? [];\n const allPools = [...pools, ...commonPools];\n const paths = buildAllPaths(allPools, tokenIn, tokenOut, maxHops);\n\n if (paths.length === 0) {\n throw new Error(`No paths found from ${tokenIn} to ${tokenOut}`);\n }\n\n return quoteBestRoute(client, quoter, tokenIn, paths, exactAmount);\n}\n","import { encodeFunctionData } from \"viem\";\nimport type { Address, Hex, PublicClient } from \"viem\";\nimport { erc20Abi } from \"@pafi-dev/core\";\nimport { permit2Abi } from \"@pafi-dev/core\";\n\nexport async function checkAllowance(\n client: PublicClient,\n token: Address,\n owner: Address,\n spender: Address,\n): Promise<bigint> {\n return client.readContract({\n address: token,\n abi: erc20Abi,\n functionName: \"allowance\",\n args: [owner, spender],\n });\n}\n\n/**\n * Encode an ERC-20 approve(spender, amount) call.\n */\nexport function buildErc20ApprovalCalldata(\n spender: Address,\n amount: bigint,\n): Hex {\n return encodeFunctionData({\n abi: erc20Abi,\n functionName: \"approve\",\n args: [spender, amount],\n });\n}\n\n/**\n * Encode a Permit2 approve(token, spender, amount, expiration) call.\n */\nexport function buildPermit2ApprovalCalldata(\n token: Address,\n spender: Address,\n amount: bigint,\n expiration: number,\n): Hex {\n return encodeFunctionData({\n abi: permit2Abi,\n functionName: \"approve\",\n args: [token, spender, amount, expiration],\n });\n}\n","import { encodeAbiParameters, encodePacked } from \"viem\";\nimport type { Address, Hex } from \"viem\";\nimport type { PathKey, QuoteResult } from \"@pafi-dev/core\";\n\n// -------------------------------------------------------------------------\n// V4 UniversalRouter command / action constants\n// Reference: https://github.com/Uniswap/v4-periphery/blob/main/src/libraries/Actions.sol\n// -------------------------------------------------------------------------\n\n/** UniversalRouter command byte for V4 swap */\nexport const V4_SWAP = 0x10 as const;\n\n/** V4 actions */\nexport const SWAP_EXACT_IN_SINGLE = 0x06 as const;\nexport const SWAP_EXACT_IN = 0x07 as const;\nexport const SETTLE_ALL = 0x0c as const;\nexport const TAKE_ALL = 0x0f as const;\n\n// -------------------------------------------------------------------------\n// ABI type strings matching Uniswap's V4Planner encoding\n// Reference: https://github.com/Uniswap/sdks/blob/main/sdks/v4-sdk/src/utils/v4Planner.ts\n//\n// IMPORTANT: PathKey.fee is uint256 in the V4 Router ABI (not uint24).\n// -------------------------------------------------------------------------\n\n// PathKey components for V4 Router ABI encoding.\n// IMPORTANT: fee is uint256 in the V4 Router (not uint24 as in PoolKey).\n// Reference: https://github.com/Uniswap/sdks/blob/main/sdks/v4-sdk/src/utils/v4Planner.ts\nconst PATH_KEY_ABI_COMPONENTS = [\n { name: \"intermediateCurrency\", type: \"address\" },\n { name: \"fee\", type: \"uint256\" },\n { name: \"tickSpacing\", type: \"int24\" },\n { name: \"hooks\", type: \"address\" },\n { name: \"hookData\", type: \"bytes\" },\n] as const;\n\nconst EXACT_INPUT_PARAMS_ABI = [\n { name: \"currencyIn\", type: \"address\" },\n {\n name: \"path\",\n type: \"tuple[]\",\n components: PATH_KEY_ABI_COMPONENTS,\n },\n { name: \"amountIn\", type: \"uint128\" },\n { name: \"amountOutMinimum\", type: \"uint128\" },\n] as const;\n\n/**\n * Build the calldata inputs[0] (the V4_SWAP command payload) for\n * UniversalRouter.execute.\n *\n * Actions encoded: SWAP_EXACT_IN → SETTLE_ALL → TAKE_ALL\n *\n * Encoding matches the Uniswap V4 SDK's V4Planner — each action's params\n * are individually ABI-encoded, then wrapped together with the action bytes.\n */\nexport function buildV4SwapInput(\n currencyIn: Address,\n path: PathKey[],\n amountIn: bigint,\n minAmountOut: bigint,\n outputCurrency: Address,\n): Hex {\n const actions = encodePacked(\n [\"uint8\", \"uint8\", \"uint8\"],\n [SWAP_EXACT_IN, SETTLE_ALL, TAKE_ALL],\n );\n\n // Param 0: ExactInputParams — encoded as a single tuple so the CalldataDecoder\n // can locate it via a single offset pointer. fee is uint256 per V4 Router spec.\n const swapParam = encodeAbiParameters(\n [{ name: \"swap\", type: \"tuple\", components: EXACT_INPUT_PARAMS_ABI }],\n [\n {\n currencyIn,\n path: path.map((p) => ({\n intermediateCurrency: p.intermediateCurrency,\n fee: BigInt(p.fee),\n tickSpacing: p.tickSpacing,\n hooks: p.hooks,\n hookData: p.hookData,\n })),\n amountIn,\n amountOutMinimum: minAmountOut,\n },\n ],\n );\n\n // Param 1: SETTLE_ALL { currency, maxAmount }\n const settleParam = encodeAbiParameters(\n [\n { name: \"currency\", type: \"address\" },\n { name: \"maxAmount\", type: \"uint256\" },\n ],\n [currencyIn, amountIn],\n );\n\n // Param 2: TAKE_ALL { currency, minAmount }\n const takeParam = encodeAbiParameters(\n [\n { name: \"currency\", type: \"address\" },\n { name: \"minAmount\", type: \"uint256\" },\n ],\n [outputCurrency, minAmountOut],\n );\n\n return encodeAbiParameters(\n [\n { name: \"actions\", type: \"bytes\" },\n { name: \"params\", type: \"bytes[]\" },\n ],\n [actions, [swapParam, settleParam, takeParam]],\n );\n}\n\n/**\n * Build the full commands + inputs args for UniversalRouter.execute.\n */\nexport function buildUniversalRouterExecuteArgs(\n currencyIn: Address,\n path: PathKey[],\n amountIn: bigint,\n minAmountOut: bigint,\n outputCurrency: Address,\n): { commands: Hex; inputs: Hex[] } {\n const commands = encodePacked([\"uint8\"], [V4_SWAP]);\n const inputs: Hex[] = [\n buildV4SwapInput(currencyIn, path, amountIn, minAmountOut, outputCurrency),\n ];\n return { commands, inputs };\n}\n\n/**\n * Build UniversalRouter execute args from a quote result.\n *\n * Takes the output of `findBestQuote` / `quoteBestRoute` and produces\n * ready-to-use `{ commands, inputs }` for `UniversalRouter.execute`.\n *\n * @param quote - Quote result containing the path\n * @param currencyIn - Input token address\n * @param currencyOut - Output token address\n * @param amountIn - Exact input amount (same value passed to the quoter)\n * @param minAmountOut - Minimum acceptable output (caller applies slippage)\n *\n * @deprecated Since v1.4 — the Issuer App no longer handles swaps.\n * For the new PT→USDT batch call on PAFI Web (Scenario 4 with\n * EIP-7702 gas deduction), use `buildSwapWithGasDeduction()` (v1.5).\n * This helper is kept for legacy v0.2.x consumers; will be removed in v2.0.\n */\nexport function buildSwapFromQuote(params: {\n quote: QuoteResult;\n currencyIn: Address;\n currencyOut: Address;\n amountIn: bigint;\n minAmountOut: bigint;\n}): { commands: Hex; inputs: Hex[] } {\n return buildUniversalRouterExecuteArgs(\n params.currencyIn,\n params.quote.path,\n params.amountIn,\n params.minAmountOut,\n params.currencyOut,\n );\n}\n","import type { Address, Hex, PublicClient } from \"viem\";\nimport { universalRouterAbi } from \"@pafi-dev/core\";\nimport { SimulationError } from \"@pafi-dev/core\";\n\nexport interface SwapSimulationResult {\n success: boolean;\n gasEstimate: bigint;\n}\n\n/**\n * Simulate a UniversalRouter.execute swap call via eth_call (no gas spent).\n *\n * Runs the full V4 swap flow — token transfer via Permit2, swap via\n * PoolManager, output settlement — without submitting a transaction.\n * If the simulation reverts, throws a `SimulationError` with the reason.\n *\n * @param client - viem PublicClient\n * @param routerAddress - UniversalRouter contract address\n * @param commands - Packed command bytes (from buildSwapFromQuote)\n * @param inputs - Encoded inputs per command (from buildSwapFromQuote)\n * @param deadline - Unix timestamp after which the tx expires\n * @param from - Address that will execute the swap\n */\nexport async function simulateSwap(\n client: PublicClient,\n routerAddress: Address,\n commands: Hex,\n inputs: Hex[],\n deadline: bigint,\n from: Address,\n): Promise<SwapSimulationResult> {\n try {\n const gasEstimate = await client.estimateContractGas({\n address: routerAddress,\n abi: universalRouterAbi,\n functionName: \"execute\",\n args: [commands, inputs, deadline],\n account: from,\n });\n\n return { success: true, gasEstimate };\n } catch (error: unknown) {\n const message =\n error instanceof Error ? error.message : \"Unknown simulation error\";\n throw new SimulationError(\"swap\", message);\n }\n}\n","import { encodeFunctionData } from \"viem\";\nimport type { Address, Hex } from \"viem\";\nimport {\n PERMIT2_ADDRESS,\n buildPartialUserOperation,\n erc20ApproveOp,\n erc20TransferOp,\n rawCallOp,\n universalRouterAbi,\n type Operation,\n type PartialUserOperation,\n type PathKey,\n} from \"@pafi-dev/core\";\nimport { buildUniversalRouterExecuteArgs } from \"./universalRouter\";\nimport { buildPermit2ApprovalCalldata } from \"./approval\";\n\n/**\n * v0.3 — Generalized swap UserOp builder. Direction-agnostic: works\n * for **any** ERC-20 → ERC-20 pair routable through PAFI's V4 pools:\n *\n * - PT → USDT (cashout)\n * - USDT → PT (buy PT with USDT)\n * - PT0 → PT1 (same-issuer multi-token swap; routes via USDT)\n *\n * UserOp shape (atomic batch via EIP-7702 BatchExecutor):\n *\n * 1. `inputToken.approve(Permit2, amountIn)` — exactly the swap amount.\n * 2. `Permit2.approve(inputToken, router, amountIn, deadline)` — Permit2\n * authorization to UniversalRouter.\n * 3. `UniversalRouter.execute(commands, inputs, deadline)` — V4 swap\n * `inputToken → outputToken`; user receives ≥ `minAmountOut`.\n * 4. `outputToken.transfer(feeRecipient, gasFeeAmountOutput)` —\n * operator gas reimbursement, paid in OUTPUT token (omitted when 0).\n *\n * ## Fee model — output-token strategy (token-availability rule)\n *\n * Fee is charged in the **output token** AFTER the swap, because the\n * user holds output token at that point in the batch. Three benefits:\n *\n * - User holds exactly `amountIn` of input token (FE quote = chain\n * reality). No \"you need 1010 to swap 1000\" UX surprise.\n * - Quote API returns `outputNet = outputGross - feeOutput` directly,\n * matching what user actually receives.\n * - PT → USDT: PAFI receives USDT (universally liquid, clean accounting).\n *\n * Caller MUST ensure `minAmountOut >= gasFeeAmountOutput` (fee transfer\n * reverts otherwise → whole batch reverts).\n *\n * ## PAFI Hook fee\n *\n * The V4 PAFIHook charges 10% on PT → USDT direction (one-way). The\n * 10% is taken at pool level inside `UniversalRouter.execute`, so this\n * builder doesn't model it explicitly — it shows up as a smaller\n * `amountOut` from `findBestQuote`.\n *\n * - PT → USDT: pool charges 10% (output reduced)\n * - USDT → PT: no hook fee\n * - PT0 → PT1: 10% on PT0 → USDT leg, 0% on USDT → PT1 leg\n */\nexport interface BuildSwapUserOpParams {\n /** User's EOA (with EIP-7702 delegation to BatchExecutor). */\n userAddress: Address;\n /** ERC-4337 account nonce — fetched from EntryPoint by the caller. */\n aaNonce: bigint;\n\n /** Token being spent — approved to Permit2 + UniversalRouter. */\n inputTokenAddress: Address;\n /** Token user receives. */\n outputTokenAddress: Address;\n /** UniversalRouter contract address (chain-specific). */\n universalRouterAddress: Address;\n\n /** Input token units to swap. User must hold exactly this much input token. */\n amountIn: bigint;\n /**\n * Minimum output (gross) to accept from the swap. Caller applies\n * slippage against a fresh quote. MUST be ≥ `gasFeeAmountOutput` or\n * the post-swap fee transfer reverts.\n */\n minAmountOut: bigint;\n\n /**\n * V4 pool path for the swap. Single-hop = 1 PathKey, multi-hop = N.\n * Get this from `findBestQuote().bestRoute.path`.\n */\n swapPath: PathKey[];\n /** Unix seconds. After this, the router rejects the swap. */\n deadline: bigint;\n\n /**\n * Operator gas-reimbursement fee — paid in OUTPUT token. Omitted\n * from the batch when 0n. Caller quotes this via `quoteOperatorFee*`\n * helpers (USDT/PT depending on output token).\n *\n * v0.3 — switched from input-side fee (v0.2 `gasFeeAmount`) to\n * output-side fee. See \"Fee model\" comment above.\n */\n gasFeeAmountOutput: bigint;\n /**\n * Where the gas fee lands — typically the canonical PAFI fee\n * recipient from `getContractAddresses(chainId).pafiFeeRecipient`.\n */\n feeRecipient: Address;\n\n /** Override ERC-4337 gas estimates. Defaults are conservative. */\n gasLimits?: {\n callGasLimit?: bigint;\n verificationGasLimit?: bigint;\n preVerificationGas?: bigint;\n };\n}\n\n/**\n * Build an unsigned UserOp for the generalized swap flow.\n *\n * @throws when `amountIn` is non-positive, `gasFeeAmountOutput` is\n * negative, `swapPath` is empty, or `minAmountOut < gasFeeAmountOutput`.\n */\nexport function buildSwapUserOp(\n params: BuildSwapUserOpParams,\n): PartialUserOperation {\n if (params.amountIn <= 0n) {\n throw new Error(\"buildSwapUserOp: amountIn must be positive\");\n }\n if (params.minAmountOut < 0n) {\n throw new Error(\"buildSwapUserOp: minAmountOut must be non-negative\");\n }\n if (params.gasFeeAmountOutput < 0n) {\n throw new Error(\"buildSwapUserOp: gasFeeAmountOutput must be non-negative\");\n }\n if (\n params.gasFeeAmountOutput > 0n &&\n params.minAmountOut < params.gasFeeAmountOutput\n ) {\n throw new Error(\n \"buildSwapUserOp: minAmountOut must be >= gasFeeAmountOutput so the post-swap fee transfer cannot revert\",\n );\n }\n if (params.swapPath.length === 0) {\n throw new Error(\n \"buildSwapUserOp: swapPath must contain at least one PathKey\",\n );\n }\n\n const { commands, inputs } = buildUniversalRouterExecuteArgs(\n params.inputTokenAddress,\n params.swapPath,\n params.amountIn,\n params.minAmountOut,\n params.outputTokenAddress,\n );\n\n const swapCallData: Hex = encodeFunctionData({\n abi: universalRouterAbi,\n functionName: \"execute\",\n args: [commands, inputs, params.deadline],\n });\n\n // Approve only the swap amount — fee comes out of swap proceeds.\n const permit2ApproveData: Hex = buildPermit2ApprovalCalldata(\n params.inputTokenAddress,\n params.universalRouterAddress,\n params.amountIn,\n Number(params.deadline),\n );\n\n const operations: Operation[] = [\n erc20ApproveOp(params.inputTokenAddress, PERMIT2_ADDRESS, params.amountIn),\n rawCallOp(PERMIT2_ADDRESS, permit2ApproveData),\n rawCallOp(params.universalRouterAddress, swapCallData),\n ];\n\n // Fee transfer in OUTPUT token, AFTER swap, when user holds output.\n if (params.gasFeeAmountOutput > 0n) {\n operations.push(\n erc20TransferOp(\n params.outputTokenAddress,\n params.feeRecipient,\n params.gasFeeAmountOutput,\n ),\n );\n }\n\n return buildPartialUserOperation({\n sender: params.userAddress,\n nonce: params.aaNonce,\n operations,\n gasLimits: {\n callGasLimit: params.gasLimits?.callGasLimit ?? 700_000n,\n verificationGasLimit: params.gasLimits?.verificationGasLimit ?? 150_000n,\n preVerificationGas: params.gasLimits?.preVerificationGas ?? 50_000n,\n },\n });\n}\n","// Re-export from @pafi-dev/core — fetchPafiPools lives in core so all\n// SDK packages share one implementation.\nexport { fetchPafiPools, PAFI_SUBGRAPH_URL } from \"@pafi-dev/core\";\n"],"mappings":"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","names":["COMMON_POOLS","encodeFunctionData","universalRouterAbi","encodeFunctionData","universalRouterAbi","userOp"]}
|