@pafi-dev/trading 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +132 -378
- package/dist/index.cjs +63 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +75 -51
- package/dist/index.d.ts +75 -51
- package/dist/index.js +65 -36
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -305,8 +305,13 @@ function buildSwapUserOp(params) {
|
|
|
305
305
|
if (params.minAmountOut < 0n) {
|
|
306
306
|
throw new Error("buildSwapUserOp: minAmountOut must be non-negative");
|
|
307
307
|
}
|
|
308
|
-
if (params.
|
|
309
|
-
throw new Error("buildSwapUserOp:
|
|
308
|
+
if (params.gasFeeAmountOutput < 0n) {
|
|
309
|
+
throw new Error("buildSwapUserOp: gasFeeAmountOutput must be non-negative");
|
|
310
|
+
}
|
|
311
|
+
if (params.gasFeeAmountOutput > 0n && params.minAmountOut < params.gasFeeAmountOutput) {
|
|
312
|
+
throw new Error(
|
|
313
|
+
"buildSwapUserOp: minAmountOut must be >= gasFeeAmountOutput so the post-swap fee transfer cannot revert"
|
|
314
|
+
);
|
|
310
315
|
}
|
|
311
316
|
if (params.swapPath.length === 0) {
|
|
312
317
|
throw new Error(
|
|
@@ -325,7 +330,6 @@ function buildSwapUserOp(params) {
|
|
|
325
330
|
functionName: "execute",
|
|
326
331
|
args: [commands, inputs, params.deadline]
|
|
327
332
|
});
|
|
328
|
-
const totalInputApproval = params.amountIn + params.gasFeeAmount;
|
|
329
333
|
const permit2ApproveData = buildPermit2ApprovalCalldata(
|
|
330
334
|
params.inputTokenAddress,
|
|
331
335
|
params.universalRouterAddress,
|
|
@@ -333,16 +337,16 @@ function buildSwapUserOp(params) {
|
|
|
333
337
|
Number(params.deadline)
|
|
334
338
|
);
|
|
335
339
|
const operations = [
|
|
336
|
-
(0, import_core8.erc20ApproveOp)(params.inputTokenAddress, import_core8.PERMIT2_ADDRESS,
|
|
340
|
+
(0, import_core8.erc20ApproveOp)(params.inputTokenAddress, import_core8.PERMIT2_ADDRESS, params.amountIn),
|
|
337
341
|
(0, import_core8.rawCallOp)(import_core8.PERMIT2_ADDRESS, permit2ApproveData),
|
|
338
342
|
(0, import_core8.rawCallOp)(params.universalRouterAddress, swapCallData)
|
|
339
343
|
];
|
|
340
|
-
if (params.
|
|
344
|
+
if (params.gasFeeAmountOutput > 0n) {
|
|
341
345
|
operations.push(
|
|
342
346
|
(0, import_core8.erc20TransferOp)(
|
|
343
|
-
params.
|
|
347
|
+
params.outputTokenAddress,
|
|
344
348
|
params.feeRecipient,
|
|
345
|
-
params.
|
|
349
|
+
params.gasFeeAmountOutput
|
|
346
350
|
)
|
|
347
351
|
);
|
|
348
352
|
}
|
|
@@ -385,6 +389,8 @@ var TradingHandlers = class {
|
|
|
385
389
|
return {
|
|
386
390
|
inputAmount: 0n,
|
|
387
391
|
estimatedOutputAmount: 0n,
|
|
392
|
+
outputNet: 0n,
|
|
393
|
+
feeAmountOutput: 0n,
|
|
388
394
|
gasEstimate: 0n
|
|
389
395
|
};
|
|
390
396
|
}
|
|
@@ -400,15 +406,25 @@ var TradingHandlers = class {
|
|
|
400
406
|
request.amount,
|
|
401
407
|
pools
|
|
402
408
|
);
|
|
409
|
+
const feeAmountOutput = await quoteOperatorFeeOutput(
|
|
410
|
+
this.provider,
|
|
411
|
+
request.chainId,
|
|
412
|
+
outputTokenAddress
|
|
413
|
+
).catch(() => 0n);
|
|
414
|
+
const outputNet = best.bestRoute.amountOut > feeAmountOutput ? best.bestRoute.amountOut - feeAmountOutput : 0n;
|
|
403
415
|
return {
|
|
404
416
|
inputAmount: request.amount,
|
|
405
417
|
estimatedOutputAmount: best.bestRoute.amountOut,
|
|
418
|
+
outputNet,
|
|
419
|
+
feeAmountOutput,
|
|
406
420
|
gasEstimate: best.bestRoute.gasEstimate
|
|
407
421
|
};
|
|
408
422
|
} catch {
|
|
409
423
|
return {
|
|
410
424
|
inputAmount: request.amount,
|
|
411
425
|
estimatedOutputAmount: 0n,
|
|
426
|
+
outputNet: 0n,
|
|
427
|
+
feeAmountOutput: 0n,
|
|
412
428
|
gasEstimate: 0n,
|
|
413
429
|
quoteError: "QUOTE_UNAVAILABLE"
|
|
414
430
|
};
|
|
@@ -418,13 +434,16 @@ var TradingHandlers = class {
|
|
|
418
434
|
// POST /swap
|
|
419
435
|
// =========================================================================
|
|
420
436
|
/**
|
|
421
|
-
* Build a
|
|
437
|
+
* Build a swap UserOp (direction-agnostic).
|
|
422
438
|
*
|
|
423
439
|
* Quotes the best route, applies slippage, then encodes a 4-step
|
|
424
|
-
* batch:
|
|
425
|
-
*
|
|
426
|
-
*
|
|
427
|
-
*
|
|
440
|
+
* batch: input.approve → Permit2.approve → UniversalRouter.execute →
|
|
441
|
+
* output.transfer (fee in OUTPUT token, omitted when fee = 0).
|
|
442
|
+
*
|
|
443
|
+
* v0.3 — Fee model is OUTPUT-side (token-availability rule). User
|
|
444
|
+
* holds exactly `amountIn` of input; receives `outputGross - fee`
|
|
445
|
+
* net. Quote response surfaces both `estimatedOutputAmount` (gross)
|
|
446
|
+
* and `outputNet` so the FE can display reality.
|
|
428
447
|
*/
|
|
429
448
|
async handleSwap(request) {
|
|
430
449
|
if (request.chainId !== this.chainId) {
|
|
@@ -442,11 +461,6 @@ var TradingHandlers = class {
|
|
|
442
461
|
const outputTokenAddress = (0, import_viem4.getAddress)(request.outputTokenAddress);
|
|
443
462
|
const userAddress = (0, import_viem4.getAddress)(request.userAddress);
|
|
444
463
|
const pools = request.pools ?? [];
|
|
445
|
-
const gasFeeAmount = request.gasFeeAmount !== void 0 ? request.gasFeeAmount : await (0, import_core9.quoteOperatorFeePt)({
|
|
446
|
-
provider: this.provider,
|
|
447
|
-
chainId: request.chainId,
|
|
448
|
-
pointTokenAddress: inputTokenAddress
|
|
449
|
-
}).catch(() => 0n);
|
|
450
464
|
let quoteResult;
|
|
451
465
|
try {
|
|
452
466
|
quoteResult = await findBestQuote(
|
|
@@ -462,10 +476,20 @@ var TradingHandlers = class {
|
|
|
462
476
|
`handleSwap: no swap path found from ${inputTokenAddress} to ${outputTokenAddress}`
|
|
463
477
|
);
|
|
464
478
|
}
|
|
479
|
+
const gasFeeAmountOutput = request.gasFeeAmountOutput !== void 0 ? request.gasFeeAmountOutput : await quoteOperatorFeeOutput(
|
|
480
|
+
this.provider,
|
|
481
|
+
request.chainId,
|
|
482
|
+
outputTokenAddress
|
|
483
|
+
).catch(() => 0n);
|
|
465
484
|
const hops = quoteResult.bestRoute.path.length;
|
|
466
485
|
const slippageBps = request.slippageBps ?? (hops > 1 ? 100 : 50);
|
|
467
486
|
const estimatedOutputAmount = quoteResult.bestRoute.amountOut;
|
|
468
487
|
const minAmountOut = estimatedOutputAmount * BigInt(1e4 - slippageBps) / 10000n;
|
|
488
|
+
if (gasFeeAmountOutput > 0n && minAmountOut < gasFeeAmountOutput) {
|
|
489
|
+
throw new Error(
|
|
490
|
+
`handleSwap: minAmountOut (${minAmountOut}) below operator fee (${gasFeeAmountOutput}) \u2014 increase swap amount or accept tighter slippage`
|
|
491
|
+
);
|
|
492
|
+
}
|
|
469
493
|
const deadline = BigInt(Math.floor(Date.now() / 1e3) + 5 * 60);
|
|
470
494
|
const userOp = buildSwapUserOp({
|
|
471
495
|
userAddress,
|
|
@@ -477,10 +501,10 @@ var TradingHandlers = class {
|
|
|
477
501
|
minAmountOut,
|
|
478
502
|
swapPath: quoteResult.bestRoute.path,
|
|
479
503
|
deadline,
|
|
480
|
-
|
|
504
|
+
gasFeeAmountOutput,
|
|
481
505
|
feeRecipient: pafiFeeRecipient
|
|
482
506
|
});
|
|
483
|
-
const userOpFallback =
|
|
507
|
+
const userOpFallback = gasFeeAmountOutput > 0n ? buildSwapUserOp({
|
|
484
508
|
userAddress,
|
|
485
509
|
aaNonce: request.aaNonce,
|
|
486
510
|
inputTokenAddress,
|
|
@@ -490,7 +514,7 @@ var TradingHandlers = class {
|
|
|
490
514
|
minAmountOut,
|
|
491
515
|
swapPath: quoteResult.bestRoute.path,
|
|
492
516
|
deadline,
|
|
493
|
-
|
|
517
|
+
gasFeeAmountOutput: 0n,
|
|
494
518
|
feeRecipient: pafiFeeRecipient
|
|
495
519
|
}) : void 0;
|
|
496
520
|
return {
|
|
@@ -500,7 +524,7 @@ var TradingHandlers = class {
|
|
|
500
524
|
minAmountOut,
|
|
501
525
|
hops,
|
|
502
526
|
deadline,
|
|
503
|
-
feeAmountUsed:
|
|
527
|
+
feeAmountUsed: gasFeeAmountOutput,
|
|
504
528
|
feeRecipient: pafiFeeRecipient
|
|
505
529
|
};
|
|
506
530
|
}
|
|
@@ -577,11 +601,10 @@ var TradingHandlers = class {
|
|
|
577
601
|
const useRelay = request.viaRelay !== false;
|
|
578
602
|
const { orderlyRelay: relayAddress, pafiFeeRecipient } = (0, import_core9.getContractAddresses)(request.chainId);
|
|
579
603
|
const relayDeployed = !isPlaceholderAddress(relayAddress);
|
|
580
|
-
const
|
|
604
|
+
const gasFeeUsdc = request.gasFeeUsdc !== void 0 ? request.gasFeeUsdc : useRelay && relayDeployed ? await (0, import_core9.quoteOperatorFeeUsdt)({
|
|
581
605
|
provider: this.provider,
|
|
582
|
-
chainId: request.chainId
|
|
583
|
-
|
|
584
|
-
}) : 0n;
|
|
606
|
+
chainId: request.chainId
|
|
607
|
+
}).catch(() => 0n) : 0n;
|
|
585
608
|
if (useRelay && relayDeployed) {
|
|
586
609
|
const RELAY_FEE_FLOOR_USDC = 2000000n;
|
|
587
610
|
const percentCap = request.amount * 500n / 10000n;
|
|
@@ -616,16 +639,10 @@ var TradingHandlers = class {
|
|
|
616
639
|
aaNonce: request.aaNonce,
|
|
617
640
|
relayAddress,
|
|
618
641
|
request: relayRequest,
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
// transfer from. `gasFeePt` resolves to 0 in that case via the
|
|
622
|
-
// ?? chain above, so the call below collapses to a no-op
|
|
623
|
-
// batch (USDC.approve + relay.deposit only).
|
|
624
|
-
pointTokenAddress: request.pointTokenAddress,
|
|
625
|
-
gasFeePt: gasFeePt > 0n ? gasFeePt : void 0,
|
|
626
|
-
gasFeePtRecipient: gasFeePt > 0n ? pafiFeeRecipient : void 0
|
|
642
|
+
gasFeeUsdc: gasFeeUsdc > 0n ? gasFeeUsdc : void 0,
|
|
643
|
+
gasFeeUsdcRecipient: gasFeeUsdc > 0n ? pafiFeeRecipient : void 0
|
|
627
644
|
});
|
|
628
|
-
const userOpFallback =
|
|
645
|
+
const userOpFallback = gasFeeUsdc > 0n ? (0, import_core9.buildPerpDepositViaRelay)({
|
|
629
646
|
userAddress,
|
|
630
647
|
aaNonce: request.aaNonce,
|
|
631
648
|
relayAddress,
|
|
@@ -641,7 +658,7 @@ var TradingHandlers = class {
|
|
|
641
658
|
brokerHash,
|
|
642
659
|
usdcAddress,
|
|
643
660
|
relayAddress,
|
|
644
|
-
feeAmountUsed:
|
|
661
|
+
feeAmountUsed: gasFeeUsdc,
|
|
645
662
|
feeRecipient: pafiFeeRecipient
|
|
646
663
|
};
|
|
647
664
|
}
|
|
@@ -675,6 +692,17 @@ var TradingHandlers = class {
|
|
|
675
692
|
function isPlaceholderAddress(addr) {
|
|
676
693
|
return /^0x0{36}[0-9a-fA-F]{4}$/i.test(addr);
|
|
677
694
|
}
|
|
695
|
+
async function quoteOperatorFeeOutput(provider, chainId, outputTokenAddress) {
|
|
696
|
+
const { usdt } = (0, import_core9.getContractAddresses)(chainId);
|
|
697
|
+
if (usdt && (0, import_viem4.getAddress)(outputTokenAddress) === (0, import_viem4.getAddress)(usdt)) {
|
|
698
|
+
return (0, import_core9.quoteOperatorFeeUsdt)({ provider, chainId });
|
|
699
|
+
}
|
|
700
|
+
return (0, import_core9.quoteOperatorFeePt)({
|
|
701
|
+
provider,
|
|
702
|
+
chainId,
|
|
703
|
+
pointTokenAddress: outputTokenAddress
|
|
704
|
+
});
|
|
705
|
+
}
|
|
678
706
|
|
|
679
707
|
// src/pools.ts
|
|
680
708
|
var import_core10 = require("@pafi-dev/core");
|
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} 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 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 return {\n inputAmount: request.amount,\n estimatedOutputAmount: best.bestRoute.amountOut,\n gasEstimate: best.bestRoute.gasEstimate,\n };\n } catch {\n return {\n inputAmount: request.amount,\n estimatedOutputAmount: 0n,\n gasEstimate: 0n,\n quoteError: \"QUOTE_UNAVAILABLE\",\n };\n }\n }\n\n // =========================================================================\n // POST /swap\n // =========================================================================\n\n /**\n * Build a PT → USDT swap UserOp.\n *\n * Quotes the best route, applies slippage, then encodes a 4-step\n * batch: PT.approve → Permit2.approve → UniversalRouter.execute →\n * PT.transfer (fee, omitted when gasFeePt = 0). Returns an unsigned\n * `PartialUserOperation`; caller attaches paymaster data + user\n * signature and submits to the Bundler.\n */\n async handleSwap(request: ApiSwapRequest): Promise<ApiSwapResponse> {\n if (request.chainId !== this.chainId) {\n throw new Error(`handleSwap: unsupported chainId ${request.chainId}`);\n }\n if (request.amount <= 0n) {\n throw new Error(\"handleSwap: amount must be positive\");\n }\n\n const { pafiFeeRecipient } = getContractAddresses(request.chainId);\n const universalRouter = UNIVERSAL_ROUTER_ADDRESSES[request.chainId];\n if (!universalRouter) {\n throw new Error(`handleSwap: no UniversalRouter for chainId ${request.chainId}`);\n }\n\n const inputTokenAddress = getAddress(request.inputTokenAddress);\n const outputTokenAddress = getAddress(request.outputTokenAddress);\n const userAddress = getAddress(request.userAddress);\n const pools = request.pools ?? [];\n\n // Resolve the operator fee in INPUT token units. `quoteOperatorFeePt`\n // is named for legacy reasons but it converts USDT-denominated gas\n // into the requested PT — for non-PT inputs (USDT) caller passes an\n // explicit `gasFeeAmount`. For PT inputs auto-quote works.\n //\n // Generic strategy: only auto-quote when the input *is* a PT\n // (matching the legacy behavior). For USDT/non-PT inputs, caller\n // must pass `gasFeeAmount` explicitly.\n const gasFeeAmount =\n request.gasFeeAmount !== undefined\n ? request.gasFeeAmount\n : await quoteOperatorFeePt({\n provider: this.provider,\n chainId: request.chainId,\n pointTokenAddress: inputTokenAddress,\n }).catch(() => 0n);\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 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 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 gasFeeAmount,\n feeRecipient: pafiFeeRecipient,\n });\n\n // Fee-stripped fallback for paymaster-refused path.\n const userOpFallback =\n gasFeeAmount > 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 gasFeeAmount: 0n,\n feeRecipient: pafiFeeRecipient,\n })\n : undefined;\n\n return {\n userOp,\n userOpFallback,\n estimatedOutputAmount,\n minAmountOut,\n hops,\n deadline,\n feeAmountUsed: gasFeeAmount,\n feeRecipient: pafiFeeRecipient,\n };\n }\n\n // =========================================================================\n // POST /perp-deposit\n // =========================================================================\n\n /**\n * Build an Orderly perp deposit UserOp.\n *\n * Default path is the **PAFI Orderly Relay** (`viaRelay: true`):\n * USDC.approve(relay) + relay.deposit(req). The Relay holds an ETH\n * reserve and pays Orderly's LayerZero `msg.value` out of it; the\n * user pays a USDC fee (quoted via `Relay.quoteTokenFee`) instead.\n * No native ETH on the user wallet is required, so paymaster\n * sponsorship of the ERC-4337 gas is sufficient end-to-end.\n *\n * Fallback path (`viaRelay: false`): direct `Vault.deposit{value}`.\n * Reserved for chains where no Relay is deployed — the user wallet\n * **must** hold `layerZeroFee` as native ETH.\n *\n * The Relay path automatically falls back to Vault when\n * `getContractAddresses(chainId).orderlyRelay` is the placeholder\n * sentinel (Relay not deployed for that chain).\n */\n async handlePerpDeposit(request: ApiPerpDepositRequest): Promise<ApiPerpDepositResponse> {\n if (request.chainId !== this.chainId) {\n throw new Error(`handlePerpDeposit: unsupported chainId ${request.chainId}`);\n }\n if (request.amount <= 0n) {\n throw new Error(\"handlePerpDeposit: amount must be positive\");\n }\n\n const vault = ORDERLY_VAULT_ADDRESSES[request.chainId];\n if (!vault) {\n throw new Error(`handlePerpDeposit: no Orderly Vault for chainId ${request.chainId}`);\n }\n\n const brokerHash = BROKER_HASHES[request.brokerId as keyof typeof BROKER_HASHES];\n if (!brokerHash) {\n throw new Error(`handlePerpDeposit: unknown brokerId \"${request.brokerId}\"`);\n }\n const tokenHash = TOKEN_HASHES.USDC;\n const userAddress = getAddress(request.userAddress);\n\n const [usdcAddress, brokerAllowed] = await Promise.all([\n this.provider.readContract({\n address: vault,\n abi: ORDERLY_VAULT_ABI,\n functionName: \"getAllowedToken\",\n args: [tokenHash],\n }) as Promise<Address>,\n this.provider.readContract({\n address: vault,\n abi: ORDERLY_VAULT_ABI,\n functionName: \"getAllowedBroker\",\n args: [brokerHash],\n }) as Promise<boolean>,\n ]);\n\n if (!brokerAllowed) {\n throw new Error(\n `handlePerpDeposit: broker \"${request.brokerId}\" is not whitelisted on Orderly Vault`,\n );\n }\n\n const accountId = computeAccountId(userAddress, brokerHash);\n const depositData = {\n accountId,\n brokerHash,\n tokenHash,\n tokenAmount: request.amount,\n };\n\n // Always read layerZeroFee for response — even on the Relay path\n // it's useful informational output (lets the FE show \"Relay\n // covers ~X ETH for you\").\n const layerZeroFee = (await this.provider.readContract({\n address: vault,\n abi: ORDERLY_VAULT_ABI,\n functionName: \"getDepositFee\",\n args: [userAddress, depositData],\n })) as bigint;\n\n const useRelay = request.viaRelay !== false;\n const { orderlyRelay: relayAddress, pafiFeeRecipient } =\n getContractAddresses(request.chainId);\n const relayDeployed = !isPlaceholderAddress(relayAddress);\n\n // Resolve operator fee — same auto/explicit/zero semantics as\n // handleSwap. Only applied to the Relay path; the legacy direct\n // Vault path skips operator fee entirely (it's already gas-heavy\n // due to the user-paid LayerZero msg.value, and PAFI doesn't\n // sponsor that path on Base anyway).\n const gasFeePt =\n request.gasFeePt !== undefined\n ? request.gasFeePt\n : useRelay && relayDeployed && request.pointTokenAddress\n ? await quoteOperatorFeePt({\n provider: this.provider,\n chainId: request.chainId,\n pointTokenAddress: getAddress(request.pointTokenAddress),\n })\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 const userOp = buildPerpDepositViaRelay({\n userAddress,\n aaNonce: request.aaNonce,\n relayAddress,\n request: relayRequest,\n // Only attach the PT-fee transfer when the caller actually\n // supplies a PointToken; otherwise the SDK has no token to\n // transfer from. `gasFeePt` resolves to 0 in that case via the\n // ?? chain above, so the call below collapses to a no-op\n // batch (USDC.approve + relay.deposit only).\n pointTokenAddress: request.pointTokenAddress,\n gasFeePt: gasFeePt > 0n ? gasFeePt : undefined,\n gasFeePtRecipient: gasFeePt > 0n ? pafiFeeRecipient : undefined,\n });\n\n // Same shape, no PT fee transfer — for the paymaster-refused\n // fallback path. The Relay still charges its USDC token-fee\n // (that compensates LayerZero ETH spend, NOT PAFI's gas).\n const userOpFallback =\n gasFeePt > 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: gasFeePt,\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","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 * v1.6 — 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 + gasFeeAmount)` — single\n * approve covers both swap input + operator gas reimbursement.\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. `inputToken.transfer(feeRecipient, gasFeeAmount)` — operator\n * gas reimbursement, paid in INPUT token (omitted when 0).\n *\n * ## Fee model — input-token strategy\n *\n * Operator gas fee is charged in the **input token** (option (a) chosen\n * 2026-04-27). Generic across directions; user only needs to hold a\n * single token in sufficient quantity.\n *\n * User must hold `amountIn + gasFeeAmount` of `inputTokenAddress`.\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 *\n * ## Order of operations\n *\n * Fee transfer is LAST so a reverting swap also refunds the fee\n * (atomic batch revert semantics).\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's balance must be ≥ `amountIn + gasFeeAmount`. */\n amountIn: bigint;\n /**\n * Minimum output to accept. Caller applies slippage (typically\n * 50-100 bps; multi-hop routes should bias higher) against a fresh\n * quote. Sub-minimum swap 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 INPUT token. Omitted from\n * the batch when 0n. Caller is responsible for quoting this in input\n * token units (typically USDT-denominated then converted via\n * `quoteOperatorFee`).\n */\n gasFeeAmount: 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, `gasFeeAmount` is negative,\n * or `swapPath` is empty.\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.gasFeeAmount < 0n) {\n throw new Error(\"buildSwapUserOp: gasFeeAmount must be non-negative\");\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 enough for swap + fee in a single call.\n const totalInputApproval = params.amountIn + params.gasFeeAmount;\n\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, totalInputApproval),\n rawCallOp(PERMIT2_ADDRESS, permit2ApproveData),\n rawCallOp(params.universalRouterAddress, swapCallData),\n ];\n\n if (params.gasFeeAmount > 0n) {\n operations.push(\n erc20TransferOp(\n params.inputTokenAddress,\n params.feeRecipient,\n params.gasFeeAmount,\n ),\n );\n }\n\n return buildPartialUserOperation({\n sender: params.userAddress,\n nonce: params.aaNonce,\n operations,\n gasLimits: {\n callGasLimit: params.gasLimits?.callGasLimit ?? 700_000n,\n verificationGasLimit: params.gasLimits?.verificationGasLimit ?? 150_000n,\n preVerificationGas: params.gasLimits?.preVerificationGas ?? 50_000n,\n },\n });\n}\n","// Re-export from @pafi-dev/core — fetchPafiPools lives in core so all\n// SDK packages share one implementation.\nexport { fetchPafiPools, PAFI_SUBGRAPH_URL } from \"@pafi-dev/core\";\n"],"mappings":"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","names":["import_viem","import_core","import_core","import_core","import_core","import_viem","import_core","import_viem","import_core","userOp","import_core"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/api/handlers.ts","../src/quoting/routes.ts","../src/quoting/quote.ts","../src/swap/approval.ts","../src/swap/universalRouter.ts","../src/swap/simulate.ts","../src/swap/buildSwap.ts","../src/pools.ts"],"sourcesContent":["export { TradingHandlers } from \"./api/handlers\";\nexport type { TradingHandlersConfig } from \"./api/handlers\";\n\nexport type {\n ApiQuoteRequest,\n ApiQuoteResponse,\n ApiQuoteError,\n ApiSwapRequest,\n ApiSwapResponse,\n ApiPerpDepositRequest,\n ApiPerpDepositResponse,\n} from \"./api/types\";\n\nexport { fetchPafiPools, PAFI_SUBGRAPH_URL } from \"./pools\";\n\n// v1.6 — Generalized swap UserOp builder + V4 quoting (moved from\n// @pafi-dev/core 2026-04-27). Exposed as building blocks for callers\n// that compose swap flows beyond what `TradingHandlers` provides.\nexport {\n buildSwapUserOp,\n type BuildSwapUserOpParams,\n buildUniversalRouterExecuteArgs,\n buildV4SwapInput,\n buildSwapFromQuote,\n buildPermit2ApprovalCalldata,\n buildErc20ApprovalCalldata,\n checkAllowance,\n simulateSwap,\n type SwapSimulationResult,\n} from \"./swap\";\n\nexport {\n findBestQuote,\n quoteBestRoute,\n quoteExactInput,\n quoteExactInputSingle,\n buildAllPaths,\n combineRoutes,\n} from \"./quoting\";\n","import { getAddress } from \"viem\";\nimport type { Address, PublicClient } from \"viem\";\nimport {\n buildPerpDepositWithGasDeduction,\n buildPerpDepositViaRelay,\n ORDERLY_RELAY_ABI,\n getContractAddresses,\n UNIVERSAL_ROUTER_ADDRESSES,\n ORDERLY_VAULT_ABI,\n ORDERLY_VAULT_ADDRESSES,\n BROKER_HASHES,\n TOKEN_HASHES,\n computeAccountId,\n quoteOperatorFeePt,\n quoteOperatorFeeUsdt,\n} from \"@pafi-dev/core\";\nimport { findBestQuote } from \"../quoting\";\nimport { buildSwapUserOp } from \"../swap\";\nimport type {\n ApiQuoteRequest,\n ApiQuoteResponse,\n ApiSwapRequest,\n ApiSwapResponse,\n ApiPerpDepositRequest,\n ApiPerpDepositResponse,\n} from \"./types\";\n\nexport interface TradingHandlersConfig {\n provider: PublicClient;\n chainId: number;\n}\n\n/**\n * Framework-agnostic handlers for on-chain trading actions.\n *\n * All handlers are stateless — they need only a PublicClient for RPC\n * calls. No ledger, no signer, no DB. Issuers wrap these in their own\n * HTTP controllers (Express / NestJS / Hono / etc.) the same way they\n * wrap `IssuerApiHandlers` from `@pafi-dev/issuer`.\n *\n * Example (NestJS):\n *\n * const trading = new TradingHandlers({ provider, chainId });\n *\n * // GET /quote\n * const quote = await trading.handleQuote({ chainId, pointTokenAddress, amount, pools });\n *\n * // POST /swap\n * const swap = await trading.handleSwap({ chainId, userAddress, pointTokenAddress, amount, aaNonce });\n *\n * // POST /perp-deposit\n * const deposit = await trading.handlePerpDeposit({ chainId, userAddress, amount, aaNonce, brokerId });\n */\nexport class TradingHandlers {\n private readonly provider: PublicClient;\n private readonly chainId: number;\n\n constructor(config: TradingHandlersConfig) {\n this.provider = config.provider;\n this.chainId = config.chainId;\n }\n\n // =========================================================================\n // GET /quote\n // =========================================================================\n\n /**\n * Quote exact-input PT → USDT via Uniswap V4 on-chain Quoter.\n *\n * Uses multicall to batch all candidate routes into a single RPC call.\n * Returns `quoteError: \"QUOTE_UNAVAILABLE\"` when no pool/path exists\n * rather than throwing, so callers can show a soft \"unavailable\" UI\n * state without 500-ing.\n */\n async handleQuote(request: ApiQuoteRequest): Promise<ApiQuoteResponse> {\n if (request.chainId !== this.chainId) {\n throw new Error(`handleQuote: unsupported chainId ${request.chainId}`);\n }\n if (request.amount === 0n) {\n return {\n inputAmount: 0n,\n estimatedOutputAmount: 0n,\n outputNet: 0n,\n feeAmountOutput: 0n,\n gasEstimate: 0n,\n };\n }\n\n const inputTokenAddress = getAddress(request.inputTokenAddress);\n const outputTokenAddress = getAddress(request.outputTokenAddress);\n const pools = request.pools ?? [];\n\n try {\n const best = await findBestQuote(\n this.provider,\n request.chainId,\n inputTokenAddress,\n outputTokenAddress,\n request.amount,\n pools,\n );\n const feeAmountOutput = await quoteOperatorFeeOutput(\n this.provider,\n request.chainId,\n outputTokenAddress,\n ).catch(() => 0n);\n const outputNet =\n best.bestRoute.amountOut > feeAmountOutput\n ? best.bestRoute.amountOut - feeAmountOutput\n : 0n;\n return {\n inputAmount: request.amount,\n estimatedOutputAmount: best.bestRoute.amountOut,\n outputNet,\n feeAmountOutput,\n gasEstimate: best.bestRoute.gasEstimate,\n };\n } catch {\n return {\n inputAmount: request.amount,\n estimatedOutputAmount: 0n,\n outputNet: 0n,\n feeAmountOutput: 0n,\n gasEstimate: 0n,\n quoteError: \"QUOTE_UNAVAILABLE\",\n };\n }\n }\n\n // =========================================================================\n // POST /swap\n // =========================================================================\n\n /**\n * Build a swap UserOp (direction-agnostic).\n *\n * Quotes the best route, applies slippage, then encodes a 4-step\n * batch: input.approve → Permit2.approve → UniversalRouter.execute →\n * output.transfer (fee in OUTPUT token, omitted when fee = 0).\n *\n * v0.3 — Fee model is OUTPUT-side (token-availability rule). User\n * holds exactly `amountIn` of input; receives `outputGross - fee`\n * net. Quote response surfaces both `estimatedOutputAmount` (gross)\n * and `outputNet` so the FE can display reality.\n */\n async handleSwap(request: ApiSwapRequest): Promise<ApiSwapResponse> {\n if (request.chainId !== this.chainId) {\n throw new Error(`handleSwap: unsupported chainId ${request.chainId}`);\n }\n if (request.amount <= 0n) {\n throw new Error(\"handleSwap: amount must be positive\");\n }\n\n const { pafiFeeRecipient } = getContractAddresses(request.chainId);\n const universalRouter = UNIVERSAL_ROUTER_ADDRESSES[request.chainId];\n if (!universalRouter) {\n throw new Error(`handleSwap: no UniversalRouter for chainId ${request.chainId}`);\n }\n\n const inputTokenAddress = getAddress(request.inputTokenAddress);\n const outputTokenAddress = getAddress(request.outputTokenAddress);\n const userAddress = getAddress(request.userAddress);\n const pools = request.pools ?? [];\n\n let quoteResult: Awaited<ReturnType<typeof findBestQuote>>;\n try {\n quoteResult = await findBestQuote(\n this.provider,\n request.chainId,\n inputTokenAddress,\n outputTokenAddress,\n request.amount,\n pools,\n );\n } catch {\n throw new Error(\n `handleSwap: no swap path found from ${inputTokenAddress} to ${outputTokenAddress}`,\n );\n }\n\n // Resolve the operator fee in OUTPUT token units. Auto-quote uses\n // the right underlying quoter based on output token (USDT vs PT).\n const gasFeeAmountOutput =\n request.gasFeeAmountOutput !== undefined\n ? request.gasFeeAmountOutput\n : await quoteOperatorFeeOutput(\n this.provider,\n request.chainId,\n outputTokenAddress,\n ).catch(() => 0n);\n\n const hops = quoteResult.bestRoute.path.length;\n // Multi-hop routes compound slippage across legs — bias higher when\n // caller didn't override. 50 bps single-hop, 100 bps multi-hop.\n const slippageBps =\n request.slippageBps ?? (hops > 1 ? 100 : 50);\n\n const estimatedOutputAmount = quoteResult.bestRoute.amountOut;\n const minAmountOut = (estimatedOutputAmount * BigInt(10000 - slippageBps)) / 10000n;\n\n if (gasFeeAmountOutput > 0n && minAmountOut < gasFeeAmountOutput) {\n throw new Error(\n `handleSwap: minAmountOut (${minAmountOut}) below operator fee (${gasFeeAmountOutput}) — increase swap amount or accept tighter slippage`,\n );\n }\n\n const deadline = BigInt(Math.floor(Date.now() / 1000) + 5 * 60);\n\n const userOp = buildSwapUserOp({\n userAddress,\n aaNonce: request.aaNonce,\n inputTokenAddress,\n outputTokenAddress,\n universalRouterAddress: universalRouter,\n amountIn: request.amount,\n minAmountOut,\n swapPath: quoteResult.bestRoute.path,\n deadline,\n gasFeeAmountOutput,\n feeRecipient: pafiFeeRecipient,\n });\n\n // Fee-stripped fallback for paymaster-refused path.\n const userOpFallback =\n gasFeeAmountOutput > 0n\n ? buildSwapUserOp({\n userAddress,\n aaNonce: request.aaNonce,\n inputTokenAddress,\n outputTokenAddress,\n universalRouterAddress: universalRouter,\n amountIn: request.amount,\n minAmountOut,\n swapPath: quoteResult.bestRoute.path,\n deadline,\n gasFeeAmountOutput: 0n,\n feeRecipient: pafiFeeRecipient,\n })\n : undefined;\n\n return {\n userOp,\n userOpFallback,\n estimatedOutputAmount,\n minAmountOut,\n hops,\n deadline,\n feeAmountUsed: gasFeeAmountOutput,\n feeRecipient: pafiFeeRecipient,\n };\n }\n\n // =========================================================================\n // POST /perp-deposit\n // =========================================================================\n\n /**\n * Build an Orderly perp deposit UserOp.\n *\n * Default path is the **PAFI Orderly Relay** (`viaRelay: true`):\n * USDC.approve(relay) + relay.deposit(req). The Relay holds an ETH\n * reserve and pays Orderly's LayerZero `msg.value` out of it; the\n * user pays a USDC fee (quoted via `Relay.quoteTokenFee`) instead.\n * No native ETH on the user wallet is required, so paymaster\n * sponsorship of the ERC-4337 gas is sufficient end-to-end.\n *\n * Fallback path (`viaRelay: false`): direct `Vault.deposit{value}`.\n * Reserved for chains where no Relay is deployed — the user wallet\n * **must** hold `layerZeroFee` as native ETH.\n *\n * The Relay path automatically falls back to Vault when\n * `getContractAddresses(chainId).orderlyRelay` is the placeholder\n * sentinel (Relay not deployed for that chain).\n */\n async handlePerpDeposit(request: ApiPerpDepositRequest): Promise<ApiPerpDepositResponse> {\n if (request.chainId !== this.chainId) {\n throw new Error(`handlePerpDeposit: unsupported chainId ${request.chainId}`);\n }\n if (request.amount <= 0n) {\n throw new Error(\"handlePerpDeposit: amount must be positive\");\n }\n\n const vault = ORDERLY_VAULT_ADDRESSES[request.chainId];\n if (!vault) {\n throw new Error(`handlePerpDeposit: no Orderly Vault for chainId ${request.chainId}`);\n }\n\n const brokerHash = BROKER_HASHES[request.brokerId as keyof typeof BROKER_HASHES];\n if (!brokerHash) {\n throw new Error(`handlePerpDeposit: unknown brokerId \"${request.brokerId}\"`);\n }\n const tokenHash = TOKEN_HASHES.USDC;\n const userAddress = getAddress(request.userAddress);\n\n const [usdcAddress, brokerAllowed] = await Promise.all([\n this.provider.readContract({\n address: vault,\n abi: ORDERLY_VAULT_ABI,\n functionName: \"getAllowedToken\",\n args: [tokenHash],\n }) as Promise<Address>,\n this.provider.readContract({\n address: vault,\n abi: ORDERLY_VAULT_ABI,\n functionName: \"getAllowedBroker\",\n args: [brokerHash],\n }) as Promise<boolean>,\n ]);\n\n if (!brokerAllowed) {\n throw new Error(\n `handlePerpDeposit: broker \"${request.brokerId}\" is not whitelisted on Orderly Vault`,\n );\n }\n\n const accountId = computeAccountId(userAddress, brokerHash);\n const depositData = {\n accountId,\n brokerHash,\n tokenHash,\n tokenAmount: request.amount,\n };\n\n // Always read layerZeroFee for response — even on the Relay path\n // it's useful informational output (lets the FE show \"Relay\n // covers ~X ETH for you\").\n const layerZeroFee = (await this.provider.readContract({\n address: vault,\n abi: ORDERLY_VAULT_ABI,\n functionName: \"getDepositFee\",\n args: [userAddress, depositData],\n })) as bigint;\n\n const useRelay = request.viaRelay !== false;\n const { orderlyRelay: relayAddress, pafiFeeRecipient } =\n getContractAddresses(request.chainId);\n const relayDeployed = !isPlaceholderAddress(relayAddress);\n\n // Resolve operator fee in USDC (input-token). Only applied to the\n // Relay path; the legacy direct-Vault path skips operator fee\n // entirely (it's already gas-heavy due to the user-paid\n // LayerZero msg.value, and PAFI doesn't sponsor that path on Base\n // anyway).\n const gasFeeUsdc =\n request.gasFeeUsdc !== undefined\n ? request.gasFeeUsdc\n : useRelay && relayDeployed\n ? await quoteOperatorFeeUsdt({\n provider: this.provider,\n chainId: request.chainId,\n }).catch(() => 0n)\n : 0n;\n\n if (useRelay && relayDeployed) {\n // Cap = max(amount * 5%, 2 USDC). The Relay fee is a flat USDC\n // amount derived from the LayerZero ETH cost at oracle price —\n // it does NOT scale with deposit size. A pure percentage cap\n // breaks for small deposits (e.g. 0.01 USDC test deposit, 5%\n // cap = 500 wei < 14k wei real fee). The 2 USDC floor covers\n // normal LayerZero pricing on Base; the 5% slope still guards\n // against oracle spikes on large deposits.\n const RELAY_FEE_FLOOR_USDC = 2_000_000n; // 2 USDC (6 decimals)\n const percentCap = (request.amount * 500n) / 10_000n;\n const maxRelayFee =\n request.maxRelayFee ??\n (percentCap > RELAY_FEE_FLOOR_USDC ? percentCap : RELAY_FEE_FLOOR_USDC);\n\n const relayRequest = {\n token: usdcAddress,\n receiver: userAddress,\n brokerHash,\n totalAmount: request.amount,\n maxFee: maxRelayFee,\n };\n\n const relayTokenFee = (await this.provider.readContract({\n address: relayAddress,\n abi: ORDERLY_RELAY_ABI,\n functionName: \"quoteTokenFee\",\n args: [relayRequest],\n })) as bigint;\n\n if (relayTokenFee > maxRelayFee) {\n throw new Error(\n `handlePerpDeposit: Relay tokenFee ${relayTokenFee} (≈ ${\n Number(relayTokenFee) / 1e6\n } USDC) exceeds maxRelayFee ${maxRelayFee} — pass a larger ` +\n `\\`maxRelayFee\\` or increase the deposit \\`amount\\` so the fee ` +\n `becomes a smaller share of the total.`,\n );\n }\n\n // Sanity-check: Relay forwards `(totalAmount − tokenFee)` to\n // Orderly Vault. When `tokenFee >= totalAmount` the forwarded\n // amount is zero / negative and the Relay reverts on-chain with\n // `FeeExceedsAmount(fee, totalAmount)` (selector 0x536766bf),\n // which propagates as an opaque `BatchExecutor.CallFailed(1, …)`\n // revert at simulation time. Catch this early on the client so\n // the UX shows an actionable message instead of an AA21/AA34\n // bundler error wrapped around the raw selector.\n if (relayTokenFee >= request.amount) {\n const feeUsdc = Number(relayTokenFee) / 1e6;\n const amountUsdc = Number(request.amount) / 1e6;\n throw new Error(\n `handlePerpDeposit: deposit amount ${amountUsdc} USDC is below the ` +\n `Relay fee ${feeUsdc} USDC — increase \\`amount\\` to at least ` +\n `${(feeUsdc * 2).toFixed(6)} USDC so a meaningful balance reaches ` +\n `your Orderly account after the Relay charge.`,\n );\n }\n\n // USDC fee transferred FIRST in the batch (input-token rule —\n // user holds USDC at start). Recipient = pafiFeeRecipient.\n const userOp = buildPerpDepositViaRelay({\n userAddress,\n aaNonce: request.aaNonce,\n relayAddress,\n request: relayRequest,\n gasFeeUsdc: gasFeeUsdc > 0n ? gasFeeUsdc : undefined,\n gasFeeUsdcRecipient: gasFeeUsdc > 0n ? pafiFeeRecipient : undefined,\n });\n\n // Same shape, no USDC fee transfer — for the paymaster-refused\n // fallback path. The Relay still charges its own USDC token-fee\n // (compensates LayerZero ETH spend, NOT PAFI's gas).\n const userOpFallback =\n gasFeeUsdc > 0n\n ? buildPerpDepositViaRelay({\n userAddress,\n aaNonce: request.aaNonce,\n relayAddress,\n request: relayRequest,\n })\n : undefined;\n\n return {\n userOp,\n userOpFallback,\n path: \"relay\",\n layerZeroFee,\n relayTokenFee,\n accountId,\n brokerHash,\n usdcAddress,\n relayAddress,\n feeAmountUsed: gasFeeUsdc,\n feeRecipient: pafiFeeRecipient,\n };\n }\n\n // Fallback: direct Vault.deposit{value} — user wallet MUST hold\n // `layerZeroFee` as native ETH (paymaster does not sponsor msg.value).\n const userOp = buildPerpDepositWithGasDeduction({\n userAddress,\n aaNonce: request.aaNonce,\n chainId: request.chainId,\n usdcAddress,\n amount: request.amount,\n depositData,\n layerZeroFee,\n });\n\n return {\n userOp,\n path: \"vault\",\n layerZeroFee,\n relayTokenFee: 0n,\n accountId,\n brokerHash,\n usdcAddress,\n relayAddress: vault,\n // Vault path doesn't include the PT operator fee transfer (it's\n // an unsponsored path on chains without a Relay deployment, and\n // the user is paying msg.value in ETH already). Echo 0n + the\n // canonical recipient so the response shape stays consistent.\n feeAmountUsed: 0n,\n feeRecipient: pafiFeeRecipient,\n };\n }\n}\n\n/**\n * `addresses.ts` uses `0x000…<suffix>` sentinels for chains where a\n * given contract is not yet deployed. Detect them by upper-160-bits =\n * 0 so we route to the Vault fallback automatically.\n */\nfunction isPlaceholderAddress(addr: Address): boolean {\n return /^0x0{36}[0-9a-fA-F]{4}$/i.test(addr);\n}\n\n/**\n * Quote the operator gas fee in OUTPUT token raw units. Picks the right\n * underlying quoter:\n *\n * - Output is USDT (canonical address from `getContractAddresses`):\n * `quoteOperatorFeeUsdt` — pure Chainlink ETH/USD, no V4 hop.\n * - Output is anything else (PT0, PT1, ...): `quoteOperatorFeePt`\n * against the output token's V4 pool.\n *\n * Used by `handleQuote` (display the user-facing net output) and\n * `handleSwap` (auto-quote when caller didn't override\n * `gasFeeAmountOutput`).\n */\nasync function quoteOperatorFeeOutput(\n provider: PublicClient,\n chainId: number,\n outputTokenAddress: Address,\n): Promise<bigint> {\n const { usdt } = getContractAddresses(chainId);\n if (\n usdt &&\n getAddress(outputTokenAddress) === getAddress(usdt as Address)\n ) {\n return quoteOperatorFeeUsdt({ provider, chainId });\n }\n return quoteOperatorFeePt({\n provider,\n chainId,\n pointTokenAddress: outputTokenAddress,\n });\n}\n","import type { Address } from \"viem\";\nimport type { PathKey, PoolKey } from \"@pafi-dev/core\";\nimport { COMMON_POOLS, POINT_TOKEN_POOLS } from \"@pafi-dev/core\";\n\nconst ZERO_ADDRESS = \"0x0000000000000000000000000000000000000000\" as Address;\n\n/**\n * Combine point-token-specific pools and common pools for a given chain.\n * Point token pools are listed first so callers can prioritise them.\n */\nexport function combineRoutes(\n chainId: number,\n pointTokenAddress: Address,\n): PoolKey[] {\n const commonPools = COMMON_POOLS[chainId] ?? [];\n const pointPools = POINT_TOKEN_POOLS[chainId]?.[pointTokenAddress] ?? [];\n return [...pointPools, ...commonPools];\n}\n\n/**\n * Build all possible swap paths from `tokenIn` to `tokenOut` using the given\n * pools. Returns an array of PathKey[] routes (each up to `maxHops` hops).\n *\n * Supports both direct single-hop routes and multi-hop routes through\n * intermediate tokens. Each pool is used at most once per path.\n *\n * @param pools - Available pools to route through\n * @param tokenIn - Input token address\n * @param tokenOut - Desired output token address\n * @param maxHops - Maximum number of hops (default 3)\n */\nexport function buildAllPaths(\n pools: PoolKey[],\n tokenIn: Address,\n tokenOut: Address,\n maxHops = 3,\n): PathKey[][] {\n const results: PathKey[][] = [];\n\n function dfs(\n currentToken: Address,\n currentPath: PathKey[],\n usedPoolIndices: Set<number>,\n ) {\n if (currentPath.length > maxHops) return;\n\n // Check if we've reached the destination\n if (\n currentPath.length > 0 &&\n currentToken.toLowerCase() === tokenOut.toLowerCase()\n ) {\n results.push([...currentPath]);\n return;\n }\n\n for (let i = 0; i < pools.length; i++) {\n if (usedPoolIndices.has(i)) continue;\n\n const pool = pools[i]!;\n const c0 = pool.currency0.toLowerCase();\n const c1 = pool.currency1.toLowerCase();\n const curr = currentToken.toLowerCase();\n\n let nextToken: Address | null = null;\n if (curr === c0) {\n nextToken = pool.currency1 as Address;\n } else if (curr === c1) {\n nextToken = pool.currency0 as Address;\n }\n\n if (!nextToken) continue;\n\n const hop: PathKey = {\n intermediateCurrency: nextToken,\n fee: pool.fee,\n tickSpacing: pool.tickSpacing,\n hooks: pool.hooks ?? ZERO_ADDRESS,\n hookData: \"0x\",\n };\n\n usedPoolIndices.add(i);\n currentPath.push(hop);\n dfs(nextToken, currentPath, usedPoolIndices);\n currentPath.pop();\n usedPoolIndices.delete(i);\n }\n }\n\n dfs(tokenIn, [], new Set());\n return results;\n}\n","import type { Address, PublicClient } from \"viem\";\nimport type { BestQuote, PathKey, PoolKey, QuoteResult } from \"@pafi-dev/core\";\nimport { v4QuoterAbi } from \"@pafi-dev/core\";\nimport { buildAllPaths } from \"./routes\";\nimport { COMMON_POOLS, V4_QUOTER_ADDRESSES } from \"@pafi-dev/core\";\n\n/**\n * Quote exact-input for a multi-hop path.\n */\nexport async function quoteExactInput(\n client: PublicClient,\n quoterAddress: Address,\n exactCurrency: Address,\n path: PathKey[],\n exactAmount: bigint,\n): Promise<QuoteResult> {\n const [amountOut, gasEstimate] = await client.readContract({\n address: quoterAddress,\n abi: v4QuoterAbi,\n functionName: \"quoteExactInput\",\n args: [{ exactCurrency, path, exactAmount: BigInt(exactAmount) as unknown as number }],\n }) as [bigint, bigint];\n\n return { amountOut, gasEstimate, path };\n}\n\n/**\n * Quote exact-input for a single-hop swap, given an explicit PoolKey and direction.\n */\nexport async function quoteExactInputSingle(\n client: PublicClient,\n quoterAddress: Address,\n poolKey: PoolKey,\n zeroForOne: boolean,\n exactAmount: bigint,\n hookData: `0x${string}`,\n): Promise<{ amountOut: bigint; gasEstimate: bigint }> {\n const [amountOut, gasEstimate] = await client.readContract({\n address: quoterAddress,\n abi: v4QuoterAbi,\n functionName: \"quoteExactInputSingle\",\n args: [\n {\n poolKey,\n zeroForOne,\n exactAmount: BigInt(exactAmount) as unknown as number,\n hookData,\n },\n ],\n }) as [bigint, bigint];\n\n return { amountOut, gasEstimate };\n}\n\n/**\n * Try multiple PathKey[] routes and return the best quote plus all results.\n * Routes that fail (e.g. pool does not exist) are silently skipped.\n *\n * Uses viem multicall to batch all quotes into a single RPC call for speed.\n */\nexport async function quoteBestRoute(\n client: PublicClient,\n quoterAddress: Address,\n exactCurrency: Address,\n routes: PathKey[][],\n exactAmount: bigint,\n): Promise<BestQuote> {\n const results = await client.multicall({\n contracts: routes.map((path) => ({\n address: quoterAddress,\n abi: v4QuoterAbi,\n functionName: \"quoteExactInput\" as const,\n args: [\n {\n exactCurrency,\n path,\n exactAmount: BigInt(exactAmount) as unknown as number,\n },\n ],\n })),\n allowFailure: true,\n });\n\n const allRoutes: QuoteResult[] = [];\n for (let i = 0; i < results.length; i++) {\n const r = results[i]!;\n if (r.status === \"success\") {\n const [amountOut, gasEstimate] = r.result as unknown as [bigint, bigint];\n allRoutes.push({ amountOut, gasEstimate, path: routes[i]! });\n }\n }\n\n if (allRoutes.length === 0) {\n throw new Error(\"No valid routes found\");\n }\n\n const bestRoute = allRoutes.reduce((best, current) =>\n current.amountOut > best.amountOut ? current : best,\n );\n\n return { bestRoute, allRoutes };\n}\n\n/**\n * Find and quote the best swap route from `tokenIn` to `tokenOut`.\n *\n * Combines the caller's `pools` with `COMMON_POOLS[chainId]`, builds all\n * possible paths (up to `maxHops`), then quotes them all via a single\n * multicall and returns the best result.\n *\n * @param client - viem PublicClient\n * @param chainId - Chain ID (used to look up COMMON_POOLS and V4_QUOTER_ADDRESSES)\n * @param tokenIn - Input token address\n * @param tokenOut - Desired output token address\n * @param exactAmount - Exact input amount\n * @param pools - Additional pools to consider (e.g. point-token-specific)\n * @param quoterAddress - Override the default V4 Quoter address for this chain\n * @param maxHops - Maximum number of hops per path (default 3)\n */\nexport async function findBestQuote(\n client: PublicClient,\n chainId: number,\n tokenIn: Address,\n tokenOut: Address,\n exactAmount: bigint,\n pools: PoolKey[] = [],\n quoterAddress?: Address,\n maxHops = 3,\n): Promise<BestQuote> {\n const quoter = quoterAddress ?? V4_QUOTER_ADDRESSES[chainId];\n if (!quoter) {\n throw new Error(`No V4 Quoter address configured for chain ${chainId}`);\n }\n\n const commonPools = COMMON_POOLS[chainId] ?? [];\n const allPools = [...pools, ...commonPools];\n const paths = buildAllPaths(allPools, tokenIn, tokenOut, maxHops);\n\n if (paths.length === 0) {\n throw new Error(`No paths found from ${tokenIn} to ${tokenOut}`);\n }\n\n return quoteBestRoute(client, quoter, tokenIn, paths, exactAmount);\n}\n","import { encodeFunctionData } from \"viem\";\nimport type { Address, Hex, PublicClient } from \"viem\";\nimport { erc20Abi } from \"@pafi-dev/core\";\nimport { permit2Abi } from \"@pafi-dev/core\";\n\nexport async function checkAllowance(\n client: PublicClient,\n token: Address,\n owner: Address,\n spender: Address,\n): Promise<bigint> {\n return client.readContract({\n address: token,\n abi: erc20Abi,\n functionName: \"allowance\",\n args: [owner, spender],\n });\n}\n\n/**\n * Encode an ERC-20 approve(spender, amount) call.\n */\nexport function buildErc20ApprovalCalldata(\n spender: Address,\n amount: bigint,\n): Hex {\n return encodeFunctionData({\n abi: erc20Abi,\n functionName: \"approve\",\n args: [spender, amount],\n });\n}\n\n/**\n * Encode a Permit2 approve(token, spender, amount, expiration) call.\n */\nexport function buildPermit2ApprovalCalldata(\n token: Address,\n spender: Address,\n amount: bigint,\n expiration: number,\n): Hex {\n return encodeFunctionData({\n abi: permit2Abi,\n functionName: \"approve\",\n args: [token, spender, amount, expiration],\n });\n}\n","import { encodeAbiParameters, encodePacked } from \"viem\";\nimport type { Address, Hex } from \"viem\";\nimport type { PathKey, QuoteResult } from \"@pafi-dev/core\";\n\n// -------------------------------------------------------------------------\n// V4 UniversalRouter command / action constants\n// Reference: https://github.com/Uniswap/v4-periphery/blob/main/src/libraries/Actions.sol\n// -------------------------------------------------------------------------\n\n/** UniversalRouter command byte for V4 swap */\nexport const V4_SWAP = 0x10 as const;\n\n/** V4 actions */\nexport const SWAP_EXACT_IN_SINGLE = 0x06 as const;\nexport const SWAP_EXACT_IN = 0x07 as const;\nexport const SETTLE_ALL = 0x0c as const;\nexport const TAKE_ALL = 0x0f as const;\n\n// -------------------------------------------------------------------------\n// ABI type strings matching Uniswap's V4Planner encoding\n// Reference: https://github.com/Uniswap/sdks/blob/main/sdks/v4-sdk/src/utils/v4Planner.ts\n//\n// IMPORTANT: PathKey.fee is uint256 in the V4 Router ABI (not uint24).\n// -------------------------------------------------------------------------\n\n// PathKey components for V4 Router ABI encoding.\n// IMPORTANT: fee is uint256 in the V4 Router (not uint24 as in PoolKey).\n// Reference: https://github.com/Uniswap/sdks/blob/main/sdks/v4-sdk/src/utils/v4Planner.ts\nconst PATH_KEY_ABI_COMPONENTS = [\n { name: \"intermediateCurrency\", type: \"address\" },\n { name: \"fee\", type: \"uint256\" },\n { name: \"tickSpacing\", type: \"int24\" },\n { name: \"hooks\", type: \"address\" },\n { name: \"hookData\", type: \"bytes\" },\n] as const;\n\nconst EXACT_INPUT_PARAMS_ABI = [\n { name: \"currencyIn\", type: \"address\" },\n {\n name: \"path\",\n type: \"tuple[]\",\n components: PATH_KEY_ABI_COMPONENTS,\n },\n { name: \"amountIn\", type: \"uint128\" },\n { name: \"amountOutMinimum\", type: \"uint128\" },\n] as const;\n\n/**\n * Build the calldata inputs[0] (the V4_SWAP command payload) for\n * UniversalRouter.execute.\n *\n * Actions encoded: SWAP_EXACT_IN → SETTLE_ALL → TAKE_ALL\n *\n * Encoding matches the Uniswap V4 SDK's V4Planner — each action's params\n * are individually ABI-encoded, then wrapped together with the action bytes.\n */\nexport function buildV4SwapInput(\n currencyIn: Address,\n path: PathKey[],\n amountIn: bigint,\n minAmountOut: bigint,\n outputCurrency: Address,\n): Hex {\n const actions = encodePacked(\n [\"uint8\", \"uint8\", \"uint8\"],\n [SWAP_EXACT_IN, SETTLE_ALL, TAKE_ALL],\n );\n\n // Param 0: ExactInputParams — encoded as a single tuple so the CalldataDecoder\n // can locate it via a single offset pointer. fee is uint256 per V4 Router spec.\n const swapParam = encodeAbiParameters(\n [{ name: \"swap\", type: \"tuple\", components: EXACT_INPUT_PARAMS_ABI }],\n [\n {\n currencyIn,\n path: path.map((p) => ({\n intermediateCurrency: p.intermediateCurrency,\n fee: BigInt(p.fee),\n tickSpacing: p.tickSpacing,\n hooks: p.hooks,\n hookData: p.hookData,\n })),\n amountIn,\n amountOutMinimum: minAmountOut,\n },\n ],\n );\n\n // Param 1: SETTLE_ALL { currency, maxAmount }\n const settleParam = encodeAbiParameters(\n [\n { name: \"currency\", type: \"address\" },\n { name: \"maxAmount\", type: \"uint256\" },\n ],\n [currencyIn, amountIn],\n );\n\n // Param 2: TAKE_ALL { currency, minAmount }\n const takeParam = encodeAbiParameters(\n [\n { name: \"currency\", type: \"address\" },\n { name: \"minAmount\", type: \"uint256\" },\n ],\n [outputCurrency, minAmountOut],\n );\n\n return encodeAbiParameters(\n [\n { name: \"actions\", type: \"bytes\" },\n { name: \"params\", type: \"bytes[]\" },\n ],\n [actions, [swapParam, settleParam, takeParam]],\n );\n}\n\n/**\n * Build the full commands + inputs args for UniversalRouter.execute.\n */\nexport function buildUniversalRouterExecuteArgs(\n currencyIn: Address,\n path: PathKey[],\n amountIn: bigint,\n minAmountOut: bigint,\n outputCurrency: Address,\n): { commands: Hex; inputs: Hex[] } {\n const commands = encodePacked([\"uint8\"], [V4_SWAP]);\n const inputs: Hex[] = [\n buildV4SwapInput(currencyIn, path, amountIn, minAmountOut, outputCurrency),\n ];\n return { commands, inputs };\n}\n\n/**\n * Build UniversalRouter execute args from a quote result.\n *\n * Takes the output of `findBestQuote` / `quoteBestRoute` and produces\n * ready-to-use `{ commands, inputs }` for `UniversalRouter.execute`.\n *\n * @param quote - Quote result containing the path\n * @param currencyIn - Input token address\n * @param currencyOut - Output token address\n * @param amountIn - Exact input amount (same value passed to the quoter)\n * @param minAmountOut - Minimum acceptable output (caller applies slippage)\n *\n * @deprecated Since v1.4 — the Issuer App no longer handles swaps.\n * For the new PT→USDT batch call on PAFI Web (Scenario 4 with\n * EIP-7702 gas deduction), use `buildSwapWithGasDeduction()` (v1.5).\n * This helper is kept for legacy v0.2.x consumers; will be removed in v2.0.\n */\nexport function buildSwapFromQuote(params: {\n quote: QuoteResult;\n currencyIn: Address;\n currencyOut: Address;\n amountIn: bigint;\n minAmountOut: bigint;\n}): { commands: Hex; inputs: Hex[] } {\n return buildUniversalRouterExecuteArgs(\n params.currencyIn,\n params.quote.path,\n params.amountIn,\n params.minAmountOut,\n params.currencyOut,\n );\n}\n","import type { Address, Hex, PublicClient } from \"viem\";\nimport { universalRouterAbi } from \"@pafi-dev/core\";\nimport { SimulationError } from \"@pafi-dev/core\";\n\nexport interface SwapSimulationResult {\n success: boolean;\n gasEstimate: bigint;\n}\n\n/**\n * Simulate a UniversalRouter.execute swap call via eth_call (no gas spent).\n *\n * Runs the full V4 swap flow — token transfer via Permit2, swap via\n * PoolManager, output settlement — without submitting a transaction.\n * If the simulation reverts, throws a `SimulationError` with the reason.\n *\n * @param client - viem PublicClient\n * @param routerAddress - UniversalRouter contract address\n * @param commands - Packed command bytes (from buildSwapFromQuote)\n * @param inputs - Encoded inputs per command (from buildSwapFromQuote)\n * @param deadline - Unix timestamp after which the tx expires\n * @param from - Address that will execute the swap\n */\nexport async function simulateSwap(\n client: PublicClient,\n routerAddress: Address,\n commands: Hex,\n inputs: Hex[],\n deadline: bigint,\n from: Address,\n): Promise<SwapSimulationResult> {\n try {\n const gasEstimate = await client.estimateContractGas({\n address: routerAddress,\n abi: universalRouterAbi,\n functionName: \"execute\",\n args: [commands, inputs, deadline],\n account: from,\n });\n\n return { success: true, gasEstimate };\n } catch (error: unknown) {\n const message =\n error instanceof Error ? error.message : \"Unknown simulation error\";\n throw new SimulationError(\"swap\", message);\n }\n}\n","import { encodeFunctionData } from \"viem\";\nimport type { Address, Hex } from \"viem\";\nimport {\n PERMIT2_ADDRESS,\n buildPartialUserOperation,\n erc20ApproveOp,\n erc20TransferOp,\n rawCallOp,\n universalRouterAbi,\n type Operation,\n type PartialUserOperation,\n type PathKey,\n} from \"@pafi-dev/core\";\nimport { buildUniversalRouterExecuteArgs } from \"./universalRouter\";\nimport { buildPermit2ApprovalCalldata } from \"./approval\";\n\n/**\n * v0.3 — Generalized swap UserOp builder. Direction-agnostic: works\n * for **any** ERC-20 → ERC-20 pair routable through PAFI's V4 pools:\n *\n * - PT → USDT (cashout)\n * - USDT → PT (buy PT with USDT)\n * - PT0 → PT1 (same-issuer multi-token swap; routes via USDT)\n *\n * UserOp shape (atomic batch via EIP-7702 BatchExecutor):\n *\n * 1. `inputToken.approve(Permit2, amountIn)` — exactly the swap amount.\n * 2. `Permit2.approve(inputToken, router, amountIn, deadline)` — Permit2\n * authorization to UniversalRouter.\n * 3. `UniversalRouter.execute(commands, inputs, deadline)` — V4 swap\n * `inputToken → outputToken`; user receives ≥ `minAmountOut`.\n * 4. `outputToken.transfer(feeRecipient, gasFeeAmountOutput)` —\n * operator gas reimbursement, paid in OUTPUT token (omitted when 0).\n *\n * ## Fee model — output-token strategy (token-availability rule)\n *\n * Fee is charged in the **output token** AFTER the swap, because the\n * user holds output token at that point in the batch. Three benefits:\n *\n * - User holds exactly `amountIn` of input token (FE quote = chain\n * reality). No \"you need 1010 to swap 1000\" UX surprise.\n * - Quote API returns `outputNet = outputGross - feeOutput` directly,\n * matching what user actually receives.\n * - PT → USDT: PAFI receives USDT (universally liquid, clean accounting).\n *\n * Caller MUST ensure `minAmountOut >= gasFeeAmountOutput` (fee transfer\n * reverts otherwise → whole batch reverts).\n *\n * ## PAFI Hook fee\n *\n * The V4 PAFIHook charges 10% on PT → USDT direction (one-way). The\n * 10% is taken at pool level inside `UniversalRouter.execute`, so this\n * builder doesn't model it explicitly — it shows up as a smaller\n * `amountOut` from `findBestQuote`.\n *\n * - PT → USDT: pool charges 10% (output reduced)\n * - USDT → PT: no hook fee\n * - PT0 → PT1: 10% on PT0 → USDT leg, 0% on USDT → PT1 leg\n */\nexport interface BuildSwapUserOpParams {\n /** User's EOA (with EIP-7702 delegation to BatchExecutor). */\n userAddress: Address;\n /** ERC-4337 account nonce — fetched from EntryPoint by the caller. */\n aaNonce: bigint;\n\n /** Token being spent — approved to Permit2 + UniversalRouter. */\n inputTokenAddress: Address;\n /** Token user receives. */\n outputTokenAddress: Address;\n /** UniversalRouter contract address (chain-specific). */\n universalRouterAddress: Address;\n\n /** Input token units to swap. User must hold exactly this much input token. */\n amountIn: bigint;\n /**\n * Minimum output (gross) to accept from the swap. Caller applies\n * slippage against a fresh quote. MUST be ≥ `gasFeeAmountOutput` or\n * the post-swap fee transfer reverts.\n */\n minAmountOut: bigint;\n\n /**\n * V4 pool path for the swap. Single-hop = 1 PathKey, multi-hop = N.\n * Get this from `findBestQuote().bestRoute.path`.\n */\n swapPath: PathKey[];\n /** Unix seconds. After this, the router rejects the swap. */\n deadline: bigint;\n\n /**\n * Operator gas-reimbursement fee — paid in OUTPUT token. Omitted\n * from the batch when 0n. Caller quotes this via `quoteOperatorFee*`\n * helpers (USDT/PT depending on output token).\n *\n * v0.3 — switched from input-side fee (v0.2 `gasFeeAmount`) to\n * output-side fee. See \"Fee model\" comment above.\n */\n gasFeeAmountOutput: bigint;\n /**\n * Where the gas fee lands — typically the canonical PAFI fee\n * recipient from `getContractAddresses(chainId).pafiFeeRecipient`.\n */\n feeRecipient: Address;\n\n /** Override ERC-4337 gas estimates. Defaults are conservative. */\n gasLimits?: {\n callGasLimit?: bigint;\n verificationGasLimit?: bigint;\n preVerificationGas?: bigint;\n };\n}\n\n/**\n * Build an unsigned UserOp for the generalized swap flow.\n *\n * @throws when `amountIn` is non-positive, `gasFeeAmountOutput` is\n * negative, `swapPath` is empty, or `minAmountOut < gasFeeAmountOutput`.\n */\nexport function buildSwapUserOp(\n params: BuildSwapUserOpParams,\n): PartialUserOperation {\n if (params.amountIn <= 0n) {\n throw new Error(\"buildSwapUserOp: amountIn must be positive\");\n }\n if (params.minAmountOut < 0n) {\n throw new Error(\"buildSwapUserOp: minAmountOut must be non-negative\");\n }\n if (params.gasFeeAmountOutput < 0n) {\n throw new Error(\"buildSwapUserOp: gasFeeAmountOutput must be non-negative\");\n }\n if (\n params.gasFeeAmountOutput > 0n &&\n params.minAmountOut < params.gasFeeAmountOutput\n ) {\n throw new Error(\n \"buildSwapUserOp: minAmountOut must be >= gasFeeAmountOutput so the post-swap fee transfer cannot revert\",\n );\n }\n if (params.swapPath.length === 0) {\n throw new Error(\n \"buildSwapUserOp: swapPath must contain at least one PathKey\",\n );\n }\n\n const { commands, inputs } = buildUniversalRouterExecuteArgs(\n params.inputTokenAddress,\n params.swapPath,\n params.amountIn,\n params.minAmountOut,\n params.outputTokenAddress,\n );\n\n const swapCallData: Hex = encodeFunctionData({\n abi: universalRouterAbi,\n functionName: \"execute\",\n args: [commands, inputs, params.deadline],\n });\n\n // Approve only the swap amount — fee comes out of swap proceeds.\n const permit2ApproveData: Hex = buildPermit2ApprovalCalldata(\n params.inputTokenAddress,\n params.universalRouterAddress,\n params.amountIn,\n Number(params.deadline),\n );\n\n const operations: Operation[] = [\n erc20ApproveOp(params.inputTokenAddress, PERMIT2_ADDRESS, params.amountIn),\n rawCallOp(PERMIT2_ADDRESS, permit2ApproveData),\n rawCallOp(params.universalRouterAddress, swapCallData),\n ];\n\n // Fee transfer in OUTPUT token, AFTER swap, when user holds output.\n if (params.gasFeeAmountOutput > 0n) {\n operations.push(\n erc20TransferOp(\n params.outputTokenAddress,\n params.feeRecipient,\n params.gasFeeAmountOutput,\n ),\n );\n }\n\n return buildPartialUserOperation({\n sender: params.userAddress,\n nonce: params.aaNonce,\n operations,\n gasLimits: {\n callGasLimit: params.gasLimits?.callGasLimit ?? 700_000n,\n verificationGasLimit: params.gasLimits?.verificationGasLimit ?? 150_000n,\n preVerificationGas: params.gasLimits?.preVerificationGas ?? 50_000n,\n },\n });\n}\n","// Re-export from @pafi-dev/core — fetchPafiPools lives in core so all\n// SDK packages share one implementation.\nexport { fetchPafiPools, PAFI_SUBGRAPH_URL } from \"@pafi-dev/core\";\n"],"mappings":"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","names":["import_viem","import_core","import_core","import_core","import_core","import_viem","import_core","import_viem","import_core","userOp","import_core"]}
|