@pafi-dev/trading 0.3.2 → 0.3.3
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 +89 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +90 -22
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -129,6 +129,12 @@ async function quoteBestRoute(client, quoterAddress, exactCurrency, routes, exac
|
|
|
129
129
|
{
|
|
130
130
|
exactCurrency,
|
|
131
131
|
path,
|
|
132
|
+
// `as unknown as number` cast — the V4 quoter's solidity
|
|
133
|
+
// signature takes `uint256` (a bigint at the wire level), but
|
|
134
|
+
// our pinned ABI types it as `number`. The runtime is fine
|
|
135
|
+
// (bigint serializes correctly) — this cast tells TS to skip
|
|
136
|
+
// the structural check until we re-generate the ABI with the
|
|
137
|
+
// correct uint256 type. Tracked: SDK_CORE_TRADING_AUDIT.md H11.
|
|
132
138
|
exactAmount: BigInt(exactAmount)
|
|
133
139
|
}
|
|
134
140
|
]
|
|
@@ -136,15 +142,21 @@ async function quoteBestRoute(client, quoterAddress, exactCurrency, routes, exac
|
|
|
136
142
|
allowFailure: true
|
|
137
143
|
});
|
|
138
144
|
const allRoutes = [];
|
|
145
|
+
let firstFailure;
|
|
139
146
|
for (let i = 0; i < results.length; i++) {
|
|
140
147
|
const r = results[i];
|
|
141
148
|
if (r.status === "success") {
|
|
142
149
|
const [amountOut, gasEstimate] = r.result;
|
|
143
150
|
allRoutes.push({ amountOut, gasEstimate, path: routes[i] });
|
|
151
|
+
} else if (firstFailure === void 0) {
|
|
152
|
+
const errMsg = r.error instanceof Error ? r.error.message : String(r.error ?? "unknown");
|
|
153
|
+
firstFailure = errMsg;
|
|
144
154
|
}
|
|
145
155
|
}
|
|
146
156
|
if (allRoutes.length === 0) {
|
|
147
|
-
throw new Error(
|
|
157
|
+
throw new Error(
|
|
158
|
+
`No valid routes found from ${exactCurrency} (${routes.length} candidates probed)` + (firstFailure ? `; first failure: ${firstFailure}` : "")
|
|
159
|
+
);
|
|
148
160
|
}
|
|
149
161
|
const bestRoute = allRoutes.reduce(
|
|
150
162
|
(best, current) => current.amountOut > best.amountOut ? current : best
|
|
@@ -318,6 +330,12 @@ function buildSwapUserOp(params) {
|
|
|
318
330
|
"buildSwapUserOp: swapPath must contain at least one PathKey"
|
|
319
331
|
);
|
|
320
332
|
}
|
|
333
|
+
const PERMIT2_EXPIRATION_MAX = 2n ** 48n - 1n;
|
|
334
|
+
if (params.deadline <= 0n || params.deadline > PERMIT2_EXPIRATION_MAX) {
|
|
335
|
+
throw new Error(
|
|
336
|
+
`buildSwapUserOp: deadline (${params.deadline}) must be unix seconds in (0, 2^48-1]. Did you accidentally pass milliseconds?`
|
|
337
|
+
);
|
|
338
|
+
}
|
|
321
339
|
const { commands, inputs } = buildUniversalRouterExecuteArgs(
|
|
322
340
|
params.inputTokenAddress,
|
|
323
341
|
params.swapPath,
|
|
@@ -383,7 +401,11 @@ var TradingHandlers = class {
|
|
|
383
401
|
*/
|
|
384
402
|
async handleQuote(request) {
|
|
385
403
|
if (request.chainId !== this.chainId) {
|
|
386
|
-
throw new
|
|
404
|
+
throw new import_core9.ValidationError(
|
|
405
|
+
"UNSUPPORTED_CHAIN_ID",
|
|
406
|
+
`handleQuote: unsupported chainId ${request.chainId}`,
|
|
407
|
+
{ requested: request.chainId, supported: this.chainId }
|
|
408
|
+
);
|
|
387
409
|
}
|
|
388
410
|
if (request.amount === 0n) {
|
|
389
411
|
return {
|
|
@@ -452,20 +474,31 @@ var TradingHandlers = class {
|
|
|
452
474
|
*/
|
|
453
475
|
async handleSwap(authenticatedAddress, request) {
|
|
454
476
|
if ((0, import_viem4.getAddress)(authenticatedAddress) !== (0, import_viem4.getAddress)(request.userAddress)) {
|
|
455
|
-
throw new
|
|
477
|
+
throw new import_core9.ValidationError(
|
|
478
|
+
"USER_ADDRESS_MISMATCH",
|
|
456
479
|
`handleSwap: authenticatedAddress (${authenticatedAddress}) does not match request.userAddress (${request.userAddress})`
|
|
457
480
|
);
|
|
458
481
|
}
|
|
459
482
|
if (request.chainId !== this.chainId) {
|
|
460
|
-
throw new
|
|
483
|
+
throw new import_core9.ValidationError(
|
|
484
|
+
"UNSUPPORTED_CHAIN_ID",
|
|
485
|
+
`handleSwap: unsupported chainId ${request.chainId}`,
|
|
486
|
+
{ requested: request.chainId, supported: this.chainId }
|
|
487
|
+
);
|
|
461
488
|
}
|
|
462
489
|
if (request.amount <= 0n) {
|
|
463
|
-
throw new
|
|
490
|
+
throw new import_core9.ValidationError(
|
|
491
|
+
"INVALID_AMOUNT",
|
|
492
|
+
"handleSwap: amount must be positive"
|
|
493
|
+
);
|
|
464
494
|
}
|
|
465
495
|
const { pafiFeeRecipient } = (0, import_core9.getContractAddresses)(request.chainId);
|
|
466
496
|
const universalRouter = import_core9.UNIVERSAL_ROUTER_ADDRESSES[request.chainId];
|
|
467
497
|
if (!universalRouter) {
|
|
468
|
-
throw new
|
|
498
|
+
throw new import_core9.ValidationError(
|
|
499
|
+
"ROUTER_NOT_DEPLOYED",
|
|
500
|
+
`handleSwap: no UniversalRouter for chainId ${request.chainId}`
|
|
501
|
+
);
|
|
469
502
|
}
|
|
470
503
|
const inputTokenAddress = (0, import_viem4.getAddress)(request.inputTokenAddress);
|
|
471
504
|
const outputTokenAddress = (0, import_viem4.getAddress)(request.outputTokenAddress);
|
|
@@ -481,9 +514,11 @@ var TradingHandlers = class {
|
|
|
481
514
|
request.amount,
|
|
482
515
|
pools
|
|
483
516
|
);
|
|
484
|
-
} catch {
|
|
485
|
-
|
|
486
|
-
|
|
517
|
+
} catch (err) {
|
|
518
|
+
const cause = err instanceof Error ? err.message : String(err);
|
|
519
|
+
throw new import_core9.ValidationError(
|
|
520
|
+
"NO_SWAP_PATH",
|
|
521
|
+
`handleSwap: no swap path found from ${inputTokenAddress} to ${outputTokenAddress} (cause: ${cause})`
|
|
487
522
|
);
|
|
488
523
|
}
|
|
489
524
|
const gasFeeAmountOutput = request.gasFeeAmountOutput !== void 0 ? request.gasFeeAmountOutput : await quoteOperatorFeeOutput(
|
|
@@ -496,8 +531,13 @@ var TradingHandlers = class {
|
|
|
496
531
|
const estimatedOutputAmount = quoteResult.bestRoute.amountOut;
|
|
497
532
|
const minAmountOut = estimatedOutputAmount * BigInt(1e4 - slippageBps) / 10000n;
|
|
498
533
|
if (gasFeeAmountOutput > 0n && minAmountOut < gasFeeAmountOutput) {
|
|
499
|
-
throw new
|
|
500
|
-
|
|
534
|
+
throw new import_core9.ValidationError(
|
|
535
|
+
"AMOUNT_TOO_SMALL_FOR_FEE",
|
|
536
|
+
`handleSwap: minAmountOut (${minAmountOut}) below operator fee (${gasFeeAmountOutput}) \u2014 increase swap amount or accept tighter slippage`,
|
|
537
|
+
{
|
|
538
|
+
minAmountOut: minAmountOut.toString(),
|
|
539
|
+
gasFeeAmountOutput: gasFeeAmountOutput.toString()
|
|
540
|
+
}
|
|
501
541
|
);
|
|
502
542
|
}
|
|
503
543
|
const deadline = BigInt(Math.floor(Date.now() / 1e3) + 5 * 60);
|
|
@@ -561,23 +601,38 @@ var TradingHandlers = class {
|
|
|
561
601
|
*/
|
|
562
602
|
async handlePerpDeposit(authenticatedAddress, request) {
|
|
563
603
|
if ((0, import_viem4.getAddress)(authenticatedAddress) !== (0, import_viem4.getAddress)(request.userAddress)) {
|
|
564
|
-
throw new
|
|
604
|
+
throw new import_core9.ValidationError(
|
|
605
|
+
"USER_ADDRESS_MISMATCH",
|
|
565
606
|
`handlePerpDeposit: authenticatedAddress (${authenticatedAddress}) does not match request.userAddress (${request.userAddress})`
|
|
566
607
|
);
|
|
567
608
|
}
|
|
568
609
|
if (request.chainId !== this.chainId) {
|
|
569
|
-
throw new
|
|
610
|
+
throw new import_core9.ValidationError(
|
|
611
|
+
"UNSUPPORTED_CHAIN_ID",
|
|
612
|
+
`handlePerpDeposit: unsupported chainId ${request.chainId}`,
|
|
613
|
+
{ requested: request.chainId, supported: this.chainId }
|
|
614
|
+
);
|
|
570
615
|
}
|
|
571
616
|
if (request.amount <= 0n) {
|
|
572
|
-
throw new
|
|
617
|
+
throw new import_core9.ValidationError(
|
|
618
|
+
"INVALID_AMOUNT",
|
|
619
|
+
"handlePerpDeposit: amount must be positive"
|
|
620
|
+
);
|
|
573
621
|
}
|
|
574
622
|
const vault = import_core9.ORDERLY_VAULT_ADDRESSES[request.chainId];
|
|
575
623
|
if (!vault) {
|
|
576
|
-
throw new
|
|
624
|
+
throw new import_core9.ValidationError(
|
|
625
|
+
"VAULT_NOT_DEPLOYED",
|
|
626
|
+
`handlePerpDeposit: no Orderly Vault for chainId ${request.chainId}`
|
|
627
|
+
);
|
|
577
628
|
}
|
|
578
629
|
const brokerHash = import_core9.BROKER_HASHES[request.brokerId];
|
|
579
630
|
if (!brokerHash) {
|
|
580
|
-
throw new
|
|
631
|
+
throw new import_core9.ValidationError(
|
|
632
|
+
"UNKNOWN_BROKER_ID",
|
|
633
|
+
`handlePerpDeposit: unknown brokerId "${request.brokerId}"`,
|
|
634
|
+
{ brokerId: request.brokerId }
|
|
635
|
+
);
|
|
581
636
|
}
|
|
582
637
|
const tokenHash = import_core9.TOKEN_HASHES.USDC;
|
|
583
638
|
const userAddress = (0, import_viem4.getAddress)(request.userAddress);
|
|
@@ -596,8 +651,10 @@ var TradingHandlers = class {
|
|
|
596
651
|
})
|
|
597
652
|
]);
|
|
598
653
|
if (!brokerAllowed) {
|
|
599
|
-
throw new
|
|
600
|
-
|
|
654
|
+
throw new import_core9.ValidationError(
|
|
655
|
+
"BROKER_NOT_WHITELISTED",
|
|
656
|
+
`handlePerpDeposit: broker "${request.brokerId}" is not whitelisted on Orderly Vault`,
|
|
657
|
+
{ brokerId: request.brokerId, brokerHash }
|
|
601
658
|
);
|
|
602
659
|
}
|
|
603
660
|
const accountId = (0, import_core9.computeAccountId)(userAddress, brokerHash);
|
|
@@ -638,15 +695,25 @@ var TradingHandlers = class {
|
|
|
638
695
|
args: [relayRequest]
|
|
639
696
|
});
|
|
640
697
|
if (relayTokenFee > maxRelayFee) {
|
|
641
|
-
throw new
|
|
642
|
-
|
|
698
|
+
throw new import_core9.ValidationError(
|
|
699
|
+
"RELAY_FEE_EXCEEDS_MAX",
|
|
700
|
+
`handlePerpDeposit: Relay tokenFee ${relayTokenFee} (\u2248 ${Number(relayTokenFee) / 1e6} USDC) exceeds maxRelayFee ${maxRelayFee} \u2014 pass a larger \`maxRelayFee\` or increase the deposit \`amount\` so the fee becomes a smaller share of the total.`,
|
|
701
|
+
{
|
|
702
|
+
relayTokenFee: relayTokenFee.toString(),
|
|
703
|
+
maxRelayFee: maxRelayFee.toString()
|
|
704
|
+
}
|
|
643
705
|
);
|
|
644
706
|
}
|
|
645
707
|
if (relayTokenFee >= request.amount) {
|
|
646
708
|
const feeUsdc = Number(relayTokenFee) / 1e6;
|
|
647
709
|
const amountUsdc = Number(request.amount) / 1e6;
|
|
648
|
-
throw new
|
|
649
|
-
|
|
710
|
+
throw new import_core9.ValidationError(
|
|
711
|
+
"RELAY_FEE_EXCEEDS_AMOUNT",
|
|
712
|
+
`handlePerpDeposit: deposit amount ${amountUsdc} USDC is below the Relay fee ${feeUsdc} USDC \u2014 increase \`amount\` to at least ${(feeUsdc * 2).toFixed(6)} USDC so a meaningful balance reaches your Orderly account after the Relay charge.`,
|
|
713
|
+
{
|
|
714
|
+
relayTokenFee: relayTokenFee.toString(),
|
|
715
|
+
amount: request.amount.toString()
|
|
716
|
+
}
|
|
650
717
|
);
|
|
651
718
|
}
|
|
652
719
|
const userOp2 = (0, import_core9.buildPerpDepositViaRelay)({
|
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 * 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"]}
|
|
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 ValidationError,\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 ValidationError(\n \"UNSUPPORTED_CHAIN_ID\",\n `handleQuote: unsupported chainId ${request.chainId}`,\n { requested: request.chainId, supported: this.chainId },\n );\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 ValidationError(\n \"USER_ADDRESS_MISMATCH\",\n `handleSwap: authenticatedAddress (${authenticatedAddress}) does not match request.userAddress (${request.userAddress})`,\n );\n }\n if (request.chainId !== this.chainId) {\n throw new ValidationError(\n \"UNSUPPORTED_CHAIN_ID\",\n `handleSwap: unsupported chainId ${request.chainId}`,\n { requested: request.chainId, supported: this.chainId },\n );\n }\n if (request.amount <= 0n) {\n throw new ValidationError(\n \"INVALID_AMOUNT\",\n \"handleSwap: amount must be positive\",\n );\n }\n\n const { pafiFeeRecipient } = getContractAddresses(request.chainId);\n const universalRouter = UNIVERSAL_ROUTER_ADDRESSES[request.chainId];\n if (!universalRouter) {\n throw new ValidationError(\n \"ROUTER_NOT_DEPLOYED\",\n `handleSwap: no UniversalRouter for chainId ${request.chainId}`,\n );\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 (err) {\n const cause = err instanceof Error ? err.message : String(err);\n throw new ValidationError(\n \"NO_SWAP_PATH\",\n `handleSwap: no swap path found from ${inputTokenAddress} to ${outputTokenAddress} (cause: ${cause})`,\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 ValidationError(\n \"AMOUNT_TOO_SMALL_FOR_FEE\",\n `handleSwap: minAmountOut (${minAmountOut}) below operator fee (${gasFeeAmountOutput}) — increase swap amount or accept tighter slippage`,\n {\n minAmountOut: minAmountOut.toString(),\n gasFeeAmountOutput: gasFeeAmountOutput.toString(),\n },\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 ValidationError(\n \"USER_ADDRESS_MISMATCH\",\n `handlePerpDeposit: authenticatedAddress (${authenticatedAddress}) does not match request.userAddress (${request.userAddress})`,\n );\n }\n if (request.chainId !== this.chainId) {\n throw new ValidationError(\n \"UNSUPPORTED_CHAIN_ID\",\n `handlePerpDeposit: unsupported chainId ${request.chainId}`,\n { requested: request.chainId, supported: this.chainId },\n );\n }\n if (request.amount <= 0n) {\n throw new ValidationError(\n \"INVALID_AMOUNT\",\n \"handlePerpDeposit: amount must be positive\",\n );\n }\n\n const vault = ORDERLY_VAULT_ADDRESSES[request.chainId];\n if (!vault) {\n throw new ValidationError(\n \"VAULT_NOT_DEPLOYED\",\n `handlePerpDeposit: no Orderly Vault for chainId ${request.chainId}`,\n );\n }\n\n const brokerHash = BROKER_HASHES[request.brokerId as keyof typeof BROKER_HASHES];\n if (!brokerHash) {\n throw new ValidationError(\n \"UNKNOWN_BROKER_ID\",\n `handlePerpDeposit: unknown brokerId \"${request.brokerId}\"`,\n { brokerId: request.brokerId },\n );\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 ValidationError(\n \"BROKER_NOT_WHITELISTED\",\n `handlePerpDeposit: broker \"${request.brokerId}\" is not whitelisted on Orderly Vault`,\n { brokerId: request.brokerId, brokerHash },\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 ValidationError(\n \"RELAY_FEE_EXCEEDS_MAX\",\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 relayTokenFee: relayTokenFee.toString(),\n maxRelayFee: maxRelayFee.toString(),\n },\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 ValidationError(\n \"RELAY_FEE_EXCEEDS_AMOUNT\",\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 relayTokenFee: relayTokenFee.toString(),\n amount: request.amount.toString(),\n },\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 // `as unknown as number` cast — the V4 quoter's solidity\n // signature takes `uint256` (a bigint at the wire level), but\n // our pinned ABI types it as `number`. The runtime is fine\n // (bigint serializes correctly) — this cast tells TS to skip\n // the structural check until we re-generate the ABI with the\n // correct uint256 type. Tracked: SDK_CORE_TRADING_AUDIT.md H11.\n exactAmount: BigInt(exactAmount) as unknown as number,\n },\n ],\n })),\n allowFailure: true,\n });\n\n const allRoutes: QuoteResult[] = [];\n // v0.7.4 — collect first-failure reason so the thrown error carries\n // root cause when EVERY route fails. Previous behaviour swallowed\n // each route's revert via `allowFailure: true` then threw a generic\n // \"No valid routes found\", losing the cause (RPC down, pool removed,\n // hook revert, etc.). See SDK_CORE_TRADING_AUDIT.md H4.\n let firstFailure: string | undefined;\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 } else if (firstFailure === undefined) {\n const errMsg =\n r.error instanceof Error\n ? r.error.message\n : String((r as { error?: unknown }).error ?? \"unknown\");\n firstFailure = errMsg;\n }\n }\n\n if (allRoutes.length === 0) {\n throw new Error(\n `No valid routes found from ${exactCurrency} (${routes.length} candidates probed)` +\n (firstFailure ? `; first failure: ${firstFailure}` : \"\"),\n );\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 // v0.7.1 — assert deadline fits Permit2's uint48 expiration field\n // (max 2^48 - 1 ≈ year 8,921,478 in unix seconds). Catches the\n // common bug where a caller passes deadline in milliseconds; also\n // guards against any future caller passing > 2^53 (Number cast lossy).\n // See SDK_CORE_TRADING_AUDIT.md H10.\n const PERMIT2_EXPIRATION_MAX = 2n ** 48n - 1n;\n if (params.deadline <= 0n || params.deadline > PERMIT2_EXPIRATION_MAX) {\n throw new Error(\n `buildSwapUserOp: deadline (${params.deadline}) must be unix seconds in (0, 2^48-1]. ` +\n `Did you accidentally pass milliseconds?`,\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.js
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
ORDERLY_VAULT_ADDRESSES,
|
|
11
11
|
BROKER_HASHES,
|
|
12
12
|
TOKEN_HASHES,
|
|
13
|
+
ValidationError,
|
|
13
14
|
computeAccountId,
|
|
14
15
|
quoteOperatorFeePt,
|
|
15
16
|
quoteOperatorFeeUsdt
|
|
@@ -100,6 +101,12 @@ async function quoteBestRoute(client, quoterAddress, exactCurrency, routes, exac
|
|
|
100
101
|
{
|
|
101
102
|
exactCurrency,
|
|
102
103
|
path,
|
|
104
|
+
// `as unknown as number` cast — the V4 quoter's solidity
|
|
105
|
+
// signature takes `uint256` (a bigint at the wire level), but
|
|
106
|
+
// our pinned ABI types it as `number`. The runtime is fine
|
|
107
|
+
// (bigint serializes correctly) — this cast tells TS to skip
|
|
108
|
+
// the structural check until we re-generate the ABI with the
|
|
109
|
+
// correct uint256 type. Tracked: SDK_CORE_TRADING_AUDIT.md H11.
|
|
103
110
|
exactAmount: BigInt(exactAmount)
|
|
104
111
|
}
|
|
105
112
|
]
|
|
@@ -107,15 +114,21 @@ async function quoteBestRoute(client, quoterAddress, exactCurrency, routes, exac
|
|
|
107
114
|
allowFailure: true
|
|
108
115
|
});
|
|
109
116
|
const allRoutes = [];
|
|
117
|
+
let firstFailure;
|
|
110
118
|
for (let i = 0; i < results.length; i++) {
|
|
111
119
|
const r = results[i];
|
|
112
120
|
if (r.status === "success") {
|
|
113
121
|
const [amountOut, gasEstimate] = r.result;
|
|
114
122
|
allRoutes.push({ amountOut, gasEstimate, path: routes[i] });
|
|
123
|
+
} else if (firstFailure === void 0) {
|
|
124
|
+
const errMsg = r.error instanceof Error ? r.error.message : String(r.error ?? "unknown");
|
|
125
|
+
firstFailure = errMsg;
|
|
115
126
|
}
|
|
116
127
|
}
|
|
117
128
|
if (allRoutes.length === 0) {
|
|
118
|
-
throw new Error(
|
|
129
|
+
throw new Error(
|
|
130
|
+
`No valid routes found from ${exactCurrency} (${routes.length} candidates probed)` + (firstFailure ? `; first failure: ${firstFailure}` : "")
|
|
131
|
+
);
|
|
119
132
|
}
|
|
120
133
|
const bestRoute = allRoutes.reduce(
|
|
121
134
|
(best, current) => current.amountOut > best.amountOut ? current : best
|
|
@@ -296,6 +309,12 @@ function buildSwapUserOp(params) {
|
|
|
296
309
|
"buildSwapUserOp: swapPath must contain at least one PathKey"
|
|
297
310
|
);
|
|
298
311
|
}
|
|
312
|
+
const PERMIT2_EXPIRATION_MAX = 2n ** 48n - 1n;
|
|
313
|
+
if (params.deadline <= 0n || params.deadline > PERMIT2_EXPIRATION_MAX) {
|
|
314
|
+
throw new Error(
|
|
315
|
+
`buildSwapUserOp: deadline (${params.deadline}) must be unix seconds in (0, 2^48-1]. Did you accidentally pass milliseconds?`
|
|
316
|
+
);
|
|
317
|
+
}
|
|
299
318
|
const { commands, inputs } = buildUniversalRouterExecuteArgs(
|
|
300
319
|
params.inputTokenAddress,
|
|
301
320
|
params.swapPath,
|
|
@@ -361,7 +380,11 @@ var TradingHandlers = class {
|
|
|
361
380
|
*/
|
|
362
381
|
async handleQuote(request) {
|
|
363
382
|
if (request.chainId !== this.chainId) {
|
|
364
|
-
throw new
|
|
383
|
+
throw new ValidationError(
|
|
384
|
+
"UNSUPPORTED_CHAIN_ID",
|
|
385
|
+
`handleQuote: unsupported chainId ${request.chainId}`,
|
|
386
|
+
{ requested: request.chainId, supported: this.chainId }
|
|
387
|
+
);
|
|
365
388
|
}
|
|
366
389
|
if (request.amount === 0n) {
|
|
367
390
|
return {
|
|
@@ -430,20 +453,31 @@ var TradingHandlers = class {
|
|
|
430
453
|
*/
|
|
431
454
|
async handleSwap(authenticatedAddress, request) {
|
|
432
455
|
if (getAddress(authenticatedAddress) !== getAddress(request.userAddress)) {
|
|
433
|
-
throw new
|
|
456
|
+
throw new ValidationError(
|
|
457
|
+
"USER_ADDRESS_MISMATCH",
|
|
434
458
|
`handleSwap: authenticatedAddress (${authenticatedAddress}) does not match request.userAddress (${request.userAddress})`
|
|
435
459
|
);
|
|
436
460
|
}
|
|
437
461
|
if (request.chainId !== this.chainId) {
|
|
438
|
-
throw new
|
|
462
|
+
throw new ValidationError(
|
|
463
|
+
"UNSUPPORTED_CHAIN_ID",
|
|
464
|
+
`handleSwap: unsupported chainId ${request.chainId}`,
|
|
465
|
+
{ requested: request.chainId, supported: this.chainId }
|
|
466
|
+
);
|
|
439
467
|
}
|
|
440
468
|
if (request.amount <= 0n) {
|
|
441
|
-
throw new
|
|
469
|
+
throw new ValidationError(
|
|
470
|
+
"INVALID_AMOUNT",
|
|
471
|
+
"handleSwap: amount must be positive"
|
|
472
|
+
);
|
|
442
473
|
}
|
|
443
474
|
const { pafiFeeRecipient } = getContractAddresses(request.chainId);
|
|
444
475
|
const universalRouter = UNIVERSAL_ROUTER_ADDRESSES[request.chainId];
|
|
445
476
|
if (!universalRouter) {
|
|
446
|
-
throw new
|
|
477
|
+
throw new ValidationError(
|
|
478
|
+
"ROUTER_NOT_DEPLOYED",
|
|
479
|
+
`handleSwap: no UniversalRouter for chainId ${request.chainId}`
|
|
480
|
+
);
|
|
447
481
|
}
|
|
448
482
|
const inputTokenAddress = getAddress(request.inputTokenAddress);
|
|
449
483
|
const outputTokenAddress = getAddress(request.outputTokenAddress);
|
|
@@ -459,9 +493,11 @@ var TradingHandlers = class {
|
|
|
459
493
|
request.amount,
|
|
460
494
|
pools
|
|
461
495
|
);
|
|
462
|
-
} catch {
|
|
463
|
-
|
|
464
|
-
|
|
496
|
+
} catch (err) {
|
|
497
|
+
const cause = err instanceof Error ? err.message : String(err);
|
|
498
|
+
throw new ValidationError(
|
|
499
|
+
"NO_SWAP_PATH",
|
|
500
|
+
`handleSwap: no swap path found from ${inputTokenAddress} to ${outputTokenAddress} (cause: ${cause})`
|
|
465
501
|
);
|
|
466
502
|
}
|
|
467
503
|
const gasFeeAmountOutput = request.gasFeeAmountOutput !== void 0 ? request.gasFeeAmountOutput : await quoteOperatorFeeOutput(
|
|
@@ -474,8 +510,13 @@ var TradingHandlers = class {
|
|
|
474
510
|
const estimatedOutputAmount = quoteResult.bestRoute.amountOut;
|
|
475
511
|
const minAmountOut = estimatedOutputAmount * BigInt(1e4 - slippageBps) / 10000n;
|
|
476
512
|
if (gasFeeAmountOutput > 0n && minAmountOut < gasFeeAmountOutput) {
|
|
477
|
-
throw new
|
|
478
|
-
|
|
513
|
+
throw new ValidationError(
|
|
514
|
+
"AMOUNT_TOO_SMALL_FOR_FEE",
|
|
515
|
+
`handleSwap: minAmountOut (${minAmountOut}) below operator fee (${gasFeeAmountOutput}) \u2014 increase swap amount or accept tighter slippage`,
|
|
516
|
+
{
|
|
517
|
+
minAmountOut: minAmountOut.toString(),
|
|
518
|
+
gasFeeAmountOutput: gasFeeAmountOutput.toString()
|
|
519
|
+
}
|
|
479
520
|
);
|
|
480
521
|
}
|
|
481
522
|
const deadline = BigInt(Math.floor(Date.now() / 1e3) + 5 * 60);
|
|
@@ -539,23 +580,38 @@ var TradingHandlers = class {
|
|
|
539
580
|
*/
|
|
540
581
|
async handlePerpDeposit(authenticatedAddress, request) {
|
|
541
582
|
if (getAddress(authenticatedAddress) !== getAddress(request.userAddress)) {
|
|
542
|
-
throw new
|
|
583
|
+
throw new ValidationError(
|
|
584
|
+
"USER_ADDRESS_MISMATCH",
|
|
543
585
|
`handlePerpDeposit: authenticatedAddress (${authenticatedAddress}) does not match request.userAddress (${request.userAddress})`
|
|
544
586
|
);
|
|
545
587
|
}
|
|
546
588
|
if (request.chainId !== this.chainId) {
|
|
547
|
-
throw new
|
|
589
|
+
throw new ValidationError(
|
|
590
|
+
"UNSUPPORTED_CHAIN_ID",
|
|
591
|
+
`handlePerpDeposit: unsupported chainId ${request.chainId}`,
|
|
592
|
+
{ requested: request.chainId, supported: this.chainId }
|
|
593
|
+
);
|
|
548
594
|
}
|
|
549
595
|
if (request.amount <= 0n) {
|
|
550
|
-
throw new
|
|
596
|
+
throw new ValidationError(
|
|
597
|
+
"INVALID_AMOUNT",
|
|
598
|
+
"handlePerpDeposit: amount must be positive"
|
|
599
|
+
);
|
|
551
600
|
}
|
|
552
601
|
const vault = ORDERLY_VAULT_ADDRESSES[request.chainId];
|
|
553
602
|
if (!vault) {
|
|
554
|
-
throw new
|
|
603
|
+
throw new ValidationError(
|
|
604
|
+
"VAULT_NOT_DEPLOYED",
|
|
605
|
+
`handlePerpDeposit: no Orderly Vault for chainId ${request.chainId}`
|
|
606
|
+
);
|
|
555
607
|
}
|
|
556
608
|
const brokerHash = BROKER_HASHES[request.brokerId];
|
|
557
609
|
if (!brokerHash) {
|
|
558
|
-
throw new
|
|
610
|
+
throw new ValidationError(
|
|
611
|
+
"UNKNOWN_BROKER_ID",
|
|
612
|
+
`handlePerpDeposit: unknown brokerId "${request.brokerId}"`,
|
|
613
|
+
{ brokerId: request.brokerId }
|
|
614
|
+
);
|
|
559
615
|
}
|
|
560
616
|
const tokenHash = TOKEN_HASHES.USDC;
|
|
561
617
|
const userAddress = getAddress(request.userAddress);
|
|
@@ -574,8 +630,10 @@ var TradingHandlers = class {
|
|
|
574
630
|
})
|
|
575
631
|
]);
|
|
576
632
|
if (!brokerAllowed) {
|
|
577
|
-
throw new
|
|
578
|
-
|
|
633
|
+
throw new ValidationError(
|
|
634
|
+
"BROKER_NOT_WHITELISTED",
|
|
635
|
+
`handlePerpDeposit: broker "${request.brokerId}" is not whitelisted on Orderly Vault`,
|
|
636
|
+
{ brokerId: request.brokerId, brokerHash }
|
|
579
637
|
);
|
|
580
638
|
}
|
|
581
639
|
const accountId = computeAccountId(userAddress, brokerHash);
|
|
@@ -616,15 +674,25 @@ var TradingHandlers = class {
|
|
|
616
674
|
args: [relayRequest]
|
|
617
675
|
});
|
|
618
676
|
if (relayTokenFee > maxRelayFee) {
|
|
619
|
-
throw new
|
|
620
|
-
|
|
677
|
+
throw new ValidationError(
|
|
678
|
+
"RELAY_FEE_EXCEEDS_MAX",
|
|
679
|
+
`handlePerpDeposit: Relay tokenFee ${relayTokenFee} (\u2248 ${Number(relayTokenFee) / 1e6} USDC) exceeds maxRelayFee ${maxRelayFee} \u2014 pass a larger \`maxRelayFee\` or increase the deposit \`amount\` so the fee becomes a smaller share of the total.`,
|
|
680
|
+
{
|
|
681
|
+
relayTokenFee: relayTokenFee.toString(),
|
|
682
|
+
maxRelayFee: maxRelayFee.toString()
|
|
683
|
+
}
|
|
621
684
|
);
|
|
622
685
|
}
|
|
623
686
|
if (relayTokenFee >= request.amount) {
|
|
624
687
|
const feeUsdc = Number(relayTokenFee) / 1e6;
|
|
625
688
|
const amountUsdc = Number(request.amount) / 1e6;
|
|
626
|
-
throw new
|
|
627
|
-
|
|
689
|
+
throw new ValidationError(
|
|
690
|
+
"RELAY_FEE_EXCEEDS_AMOUNT",
|
|
691
|
+
`handlePerpDeposit: deposit amount ${amountUsdc} USDC is below the Relay fee ${feeUsdc} USDC \u2014 increase \`amount\` to at least ${(feeUsdc * 2).toFixed(6)} USDC so a meaningful balance reaches your Orderly account after the Relay charge.`,
|
|
692
|
+
{
|
|
693
|
+
relayTokenFee: relayTokenFee.toString(),
|
|
694
|
+
amount: request.amount.toString()
|
|
695
|
+
}
|
|
628
696
|
);
|
|
629
697
|
}
|
|
630
698
|
const userOp2 = buildPerpDepositViaRelay({
|
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 * 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"]}
|
|
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 ValidationError,\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 ValidationError(\n \"UNSUPPORTED_CHAIN_ID\",\n `handleQuote: unsupported chainId ${request.chainId}`,\n { requested: request.chainId, supported: this.chainId },\n );\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 ValidationError(\n \"USER_ADDRESS_MISMATCH\",\n `handleSwap: authenticatedAddress (${authenticatedAddress}) does not match request.userAddress (${request.userAddress})`,\n );\n }\n if (request.chainId !== this.chainId) {\n throw new ValidationError(\n \"UNSUPPORTED_CHAIN_ID\",\n `handleSwap: unsupported chainId ${request.chainId}`,\n { requested: request.chainId, supported: this.chainId },\n );\n }\n if (request.amount <= 0n) {\n throw new ValidationError(\n \"INVALID_AMOUNT\",\n \"handleSwap: amount must be positive\",\n );\n }\n\n const { pafiFeeRecipient } = getContractAddresses(request.chainId);\n const universalRouter = UNIVERSAL_ROUTER_ADDRESSES[request.chainId];\n if (!universalRouter) {\n throw new ValidationError(\n \"ROUTER_NOT_DEPLOYED\",\n `handleSwap: no UniversalRouter for chainId ${request.chainId}`,\n );\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 (err) {\n const cause = err instanceof Error ? err.message : String(err);\n throw new ValidationError(\n \"NO_SWAP_PATH\",\n `handleSwap: no swap path found from ${inputTokenAddress} to ${outputTokenAddress} (cause: ${cause})`,\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 ValidationError(\n \"AMOUNT_TOO_SMALL_FOR_FEE\",\n `handleSwap: minAmountOut (${minAmountOut}) below operator fee (${gasFeeAmountOutput}) — increase swap amount or accept tighter slippage`,\n {\n minAmountOut: minAmountOut.toString(),\n gasFeeAmountOutput: gasFeeAmountOutput.toString(),\n },\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 ValidationError(\n \"USER_ADDRESS_MISMATCH\",\n `handlePerpDeposit: authenticatedAddress (${authenticatedAddress}) does not match request.userAddress (${request.userAddress})`,\n );\n }\n if (request.chainId !== this.chainId) {\n throw new ValidationError(\n \"UNSUPPORTED_CHAIN_ID\",\n `handlePerpDeposit: unsupported chainId ${request.chainId}`,\n { requested: request.chainId, supported: this.chainId },\n );\n }\n if (request.amount <= 0n) {\n throw new ValidationError(\n \"INVALID_AMOUNT\",\n \"handlePerpDeposit: amount must be positive\",\n );\n }\n\n const vault = ORDERLY_VAULT_ADDRESSES[request.chainId];\n if (!vault) {\n throw new ValidationError(\n \"VAULT_NOT_DEPLOYED\",\n `handlePerpDeposit: no Orderly Vault for chainId ${request.chainId}`,\n );\n }\n\n const brokerHash = BROKER_HASHES[request.brokerId as keyof typeof BROKER_HASHES];\n if (!brokerHash) {\n throw new ValidationError(\n \"UNKNOWN_BROKER_ID\",\n `handlePerpDeposit: unknown brokerId \"${request.brokerId}\"`,\n { brokerId: request.brokerId },\n );\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 ValidationError(\n \"BROKER_NOT_WHITELISTED\",\n `handlePerpDeposit: broker \"${request.brokerId}\" is not whitelisted on Orderly Vault`,\n { brokerId: request.brokerId, brokerHash },\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 ValidationError(\n \"RELAY_FEE_EXCEEDS_MAX\",\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 relayTokenFee: relayTokenFee.toString(),\n maxRelayFee: maxRelayFee.toString(),\n },\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 ValidationError(\n \"RELAY_FEE_EXCEEDS_AMOUNT\",\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 relayTokenFee: relayTokenFee.toString(),\n amount: request.amount.toString(),\n },\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 // `as unknown as number` cast — the V4 quoter's solidity\n // signature takes `uint256` (a bigint at the wire level), but\n // our pinned ABI types it as `number`. The runtime is fine\n // (bigint serializes correctly) — this cast tells TS to skip\n // the structural check until we re-generate the ABI with the\n // correct uint256 type. Tracked: SDK_CORE_TRADING_AUDIT.md H11.\n exactAmount: BigInt(exactAmount) as unknown as number,\n },\n ],\n })),\n allowFailure: true,\n });\n\n const allRoutes: QuoteResult[] = [];\n // v0.7.4 — collect first-failure reason so the thrown error carries\n // root cause when EVERY route fails. Previous behaviour swallowed\n // each route's revert via `allowFailure: true` then threw a generic\n // \"No valid routes found\", losing the cause (RPC down, pool removed,\n // hook revert, etc.). See SDK_CORE_TRADING_AUDIT.md H4.\n let firstFailure: string | undefined;\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 } else if (firstFailure === undefined) {\n const errMsg =\n r.error instanceof Error\n ? r.error.message\n : String((r as { error?: unknown }).error ?? \"unknown\");\n firstFailure = errMsg;\n }\n }\n\n if (allRoutes.length === 0) {\n throw new Error(\n `No valid routes found from ${exactCurrency} (${routes.length} candidates probed)` +\n (firstFailure ? `; first failure: ${firstFailure}` : \"\"),\n );\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 // v0.7.1 — assert deadline fits Permit2's uint48 expiration field\n // (max 2^48 - 1 ≈ year 8,921,478 in unix seconds). Catches the\n // common bug where a caller passes deadline in milliseconds; also\n // guards against any future caller passing > 2^53 (Number cast lossy).\n // See SDK_CORE_TRADING_AUDIT.md H10.\n const PERMIT2_EXPIRATION_MAX = 2n ** 48n - 1n;\n if (params.deadline <= 0n || params.deadline > PERMIT2_EXPIRATION_MAX) {\n throw new Error(\n `buildSwapUserOp: deadline (${params.deadline}) must be unix seconds in (0, 2^48-1]. ` +\n `Did you accidentally pass milliseconds?`,\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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pafi-dev/trading",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "Stateless on-chain trading handlers for PAFI — swap, quote, perp deposit",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"dist"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@pafi-dev/core": "0.7.
|
|
25
|
+
"@pafi-dev/core": "0.7.2"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
28
|
"viem": "^2.0.0"
|