@pafi-dev/trading 0.3.2 → 0.4.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 +95 -0
- package/dist/index.cjs +330 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +178 -3
- package/dist/index.d.ts +178 -3
- package/dist/index.js +348 -23
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -187,6 +187,99 @@ simulations.
|
|
|
187
187
|
|
|
188
188
|
---
|
|
189
189
|
|
|
190
|
+
## Direct path — no AA, no bundler, no sponsor-relayer
|
|
191
|
+
|
|
192
|
+
`v0.4.0+` ships `swapDirect()` + `perpDepositDirect()` for the FE-only
|
|
193
|
+
flow where the user pays gas in ETH and broadcasts a single type-2 tx
|
|
194
|
+
calling its own EIP-7702 delegated bytecode. No Pimlico API key, no
|
|
195
|
+
sponsor-relayer, no PAFI infra.
|
|
196
|
+
|
|
197
|
+
**Precondition:** user EOA must already be EIP-7702 delegated (run
|
|
198
|
+
`delegateDirect()` from `@pafi-dev/core` first; both helpers throw a
|
|
199
|
+
clear error otherwise).
|
|
200
|
+
|
|
201
|
+
### `swapDirect`
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
import { swapDirect, fetchPafiPools } from "@pafi-dev/trading";
|
|
205
|
+
import { createPublicClient, createWalletClient, custom, http, parseUnits } from "viem";
|
|
206
|
+
import { base } from "viem/chains";
|
|
207
|
+
|
|
208
|
+
const publicClient = createPublicClient({ chain: base, transport: http() });
|
|
209
|
+
// `walletClient` from Privy embedded wallet, MetaMask, etc.
|
|
210
|
+
const walletClient = createWalletClient({
|
|
211
|
+
account: wallet.address,
|
|
212
|
+
chain: base,
|
|
213
|
+
transport: custom(await wallet.getEthereumProvider()),
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
const pools = await fetchPafiPools(8453, POINT_TOKEN);
|
|
217
|
+
|
|
218
|
+
const result = await swapDirect({
|
|
219
|
+
userAddress: wallet.address,
|
|
220
|
+
chainId: 8453,
|
|
221
|
+
inputTokenAddress: POINT_TOKEN,
|
|
222
|
+
outputTokenAddress: USDT,
|
|
223
|
+
amount: parseUnits("100", 18),
|
|
224
|
+
pools,
|
|
225
|
+
publicClient,
|
|
226
|
+
walletClient,
|
|
227
|
+
// optional: slippageBps, deadline, gasFeeAmountOutput, waitForReceipt
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
console.log("Swap tx:", result.txHash);
|
|
231
|
+
// {
|
|
232
|
+
// txHash, receipt?, estimatedOutputAmount, minAmountOut, hops,
|
|
233
|
+
// deadline, feeAmountUsed
|
|
234
|
+
// }
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**Operator fee:** default `0n` (skip — PAFI not sponsoring this swap).
|
|
238
|
+
Pass an explicit `gasFeeAmountOutput: bigint` only if you want to mirror
|
|
239
|
+
the sponsored flow's fee transfer.
|
|
240
|
+
|
|
241
|
+
### `perpDepositDirect`
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
import { perpDepositDirect } from "@pafi-dev/trading";
|
|
245
|
+
|
|
246
|
+
const result = await perpDepositDirect({
|
|
247
|
+
userAddress: wallet.address,
|
|
248
|
+
chainId: 8453,
|
|
249
|
+
amount: parseUnits("10", 6), // 10 USDC
|
|
250
|
+
brokerId: "orderly", // | "woofi_pro" | "logx"
|
|
251
|
+
publicClient,
|
|
252
|
+
walletClient,
|
|
253
|
+
// optional: maxRelayFee, gasFeeUsdc, waitForReceipt
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// {
|
|
257
|
+
// txHash, receipt?, relayTokenFee, maxFee, netDeposit,
|
|
258
|
+
// feeAmountUsed, accountId, brokerHash, usdcAddress, relayAddress
|
|
259
|
+
// }
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
The Relay still charges its own USDC fee (`Relay.quoteTokenFee`) to
|
|
263
|
+
cover LayerZero `msg.value`; that's separate from PAFI's operator fee
|
|
264
|
+
(which is skipped by default on the direct path).
|
|
265
|
+
|
|
266
|
+
### When to use direct vs. AA path
|
|
267
|
+
|
|
268
|
+
| Aspect | `swapDirect` / `perpDepositDirect` | `TradingHandlers.handleSwap` / `handlePerpDeposit` |
|
|
269
|
+
| --- | --- | --- |
|
|
270
|
+
| User pays gas | ETH (native) | Free (sponsor) or ETH (fallback) |
|
|
271
|
+
| Bundler required | No | Yes (Pimlico) |
|
|
272
|
+
| Sponsor-relayer required | No | Yes (sponsored path) |
|
|
273
|
+
| Operator fee charged | Default skip | Default included |
|
|
274
|
+
| Round trips | 1 (broadcast tx) | 2+ (paymaster + bundler) |
|
|
275
|
+
| Precondition | EIP-7702 delegated | Same |
|
|
276
|
+
|
|
277
|
+
Use direct for FE dev/test, integrations without a Pimlico key, or
|
|
278
|
+
production paths where users explicitly opt to pay gas. Use AA path for
|
|
279
|
+
the production gas-free UX via sponsor-relayer.
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
190
283
|
## API reference
|
|
191
284
|
|
|
192
285
|
### `TradingHandlers`
|
|
@@ -213,6 +306,8 @@ Methods:
|
|
|
213
306
|
- `buildPermit2ApprovalCalldata`, `buildErc20ApprovalCalldata`, `checkAllowance`
|
|
214
307
|
- `simulateSwap` — `eth_call` dry-run
|
|
215
308
|
- `fetchPafiPools(chainId, pointTokenAddress, subgraphUrl?)`
|
|
309
|
+
- `swapDirect(params)` — FE-direct swap (no AA, user pays gas) — see "Direct path" section
|
|
310
|
+
- `perpDepositDirect(params)` — FE-direct perp deposit (no AA, user pays gas)
|
|
216
311
|
|
|
217
312
|
---
|
|
218
313
|
|
package/dist/index.cjs
CHANGED
|
@@ -33,10 +33,12 @@ __export(index_exports, {
|
|
|
33
33
|
combineRoutes: () => combineRoutes,
|
|
34
34
|
fetchPafiPools: () => import_core10.fetchPafiPools,
|
|
35
35
|
findBestQuote: () => findBestQuote,
|
|
36
|
+
perpDepositDirect: () => perpDepositDirect,
|
|
36
37
|
quoteBestRoute: () => quoteBestRoute,
|
|
37
38
|
quoteExactInput: () => quoteExactInput,
|
|
38
39
|
quoteExactInputSingle: () => quoteExactInputSingle,
|
|
39
|
-
simulateSwap: () => simulateSwap
|
|
40
|
+
simulateSwap: () => simulateSwap,
|
|
41
|
+
swapDirect: () => swapDirect
|
|
40
42
|
});
|
|
41
43
|
module.exports = __toCommonJS(index_exports);
|
|
42
44
|
|
|
@@ -129,6 +131,12 @@ async function quoteBestRoute(client, quoterAddress, exactCurrency, routes, exac
|
|
|
129
131
|
{
|
|
130
132
|
exactCurrency,
|
|
131
133
|
path,
|
|
134
|
+
// `as unknown as number` cast — the V4 quoter's solidity
|
|
135
|
+
// signature takes `uint256` (a bigint at the wire level), but
|
|
136
|
+
// our pinned ABI types it as `number`. The runtime is fine
|
|
137
|
+
// (bigint serializes correctly) — this cast tells TS to skip
|
|
138
|
+
// the structural check until we re-generate the ABI with the
|
|
139
|
+
// correct uint256 type. Tracked: SDK_CORE_TRADING_AUDIT.md H11.
|
|
132
140
|
exactAmount: BigInt(exactAmount)
|
|
133
141
|
}
|
|
134
142
|
]
|
|
@@ -136,15 +144,21 @@ async function quoteBestRoute(client, quoterAddress, exactCurrency, routes, exac
|
|
|
136
144
|
allowFailure: true
|
|
137
145
|
});
|
|
138
146
|
const allRoutes = [];
|
|
147
|
+
let firstFailure;
|
|
139
148
|
for (let i = 0; i < results.length; i++) {
|
|
140
149
|
const r = results[i];
|
|
141
150
|
if (r.status === "success") {
|
|
142
151
|
const [amountOut, gasEstimate] = r.result;
|
|
143
152
|
allRoutes.push({ amountOut, gasEstimate, path: routes[i] });
|
|
153
|
+
} else if (firstFailure === void 0) {
|
|
154
|
+
const errMsg = r.error instanceof Error ? r.error.message : String(r.error ?? "unknown");
|
|
155
|
+
firstFailure = errMsg;
|
|
144
156
|
}
|
|
145
157
|
}
|
|
146
158
|
if (allRoutes.length === 0) {
|
|
147
|
-
throw new Error(
|
|
159
|
+
throw new Error(
|
|
160
|
+
`No valid routes found from ${exactCurrency} (${routes.length} candidates probed)` + (firstFailure ? `; first failure: ${firstFailure}` : "")
|
|
161
|
+
);
|
|
148
162
|
}
|
|
149
163
|
const bestRoute = allRoutes.reduce(
|
|
150
164
|
(best, current) => current.amountOut > best.amountOut ? current : best
|
|
@@ -318,6 +332,12 @@ function buildSwapUserOp(params) {
|
|
|
318
332
|
"buildSwapUserOp: swapPath must contain at least one PathKey"
|
|
319
333
|
);
|
|
320
334
|
}
|
|
335
|
+
const PERMIT2_EXPIRATION_MAX = 2n ** 48n - 1n;
|
|
336
|
+
if (params.deadline <= 0n || params.deadline > PERMIT2_EXPIRATION_MAX) {
|
|
337
|
+
throw new Error(
|
|
338
|
+
`buildSwapUserOp: deadline (${params.deadline}) must be unix seconds in (0, 2^48-1]. Did you accidentally pass milliseconds?`
|
|
339
|
+
);
|
|
340
|
+
}
|
|
321
341
|
const { commands, inputs } = buildUniversalRouterExecuteArgs(
|
|
322
342
|
params.inputTokenAddress,
|
|
323
343
|
params.swapPath,
|
|
@@ -383,7 +403,11 @@ var TradingHandlers = class {
|
|
|
383
403
|
*/
|
|
384
404
|
async handleQuote(request) {
|
|
385
405
|
if (request.chainId !== this.chainId) {
|
|
386
|
-
throw new
|
|
406
|
+
throw new import_core9.ValidationError(
|
|
407
|
+
"UNSUPPORTED_CHAIN_ID",
|
|
408
|
+
`handleQuote: unsupported chainId ${request.chainId}`,
|
|
409
|
+
{ requested: request.chainId, supported: this.chainId }
|
|
410
|
+
);
|
|
387
411
|
}
|
|
388
412
|
if (request.amount === 0n) {
|
|
389
413
|
return {
|
|
@@ -452,20 +476,31 @@ var TradingHandlers = class {
|
|
|
452
476
|
*/
|
|
453
477
|
async handleSwap(authenticatedAddress, request) {
|
|
454
478
|
if ((0, import_viem4.getAddress)(authenticatedAddress) !== (0, import_viem4.getAddress)(request.userAddress)) {
|
|
455
|
-
throw new
|
|
479
|
+
throw new import_core9.ValidationError(
|
|
480
|
+
"USER_ADDRESS_MISMATCH",
|
|
456
481
|
`handleSwap: authenticatedAddress (${authenticatedAddress}) does not match request.userAddress (${request.userAddress})`
|
|
457
482
|
);
|
|
458
483
|
}
|
|
459
484
|
if (request.chainId !== this.chainId) {
|
|
460
|
-
throw new
|
|
485
|
+
throw new import_core9.ValidationError(
|
|
486
|
+
"UNSUPPORTED_CHAIN_ID",
|
|
487
|
+
`handleSwap: unsupported chainId ${request.chainId}`,
|
|
488
|
+
{ requested: request.chainId, supported: this.chainId }
|
|
489
|
+
);
|
|
461
490
|
}
|
|
462
491
|
if (request.amount <= 0n) {
|
|
463
|
-
throw new
|
|
492
|
+
throw new import_core9.ValidationError(
|
|
493
|
+
"INVALID_AMOUNT",
|
|
494
|
+
"handleSwap: amount must be positive"
|
|
495
|
+
);
|
|
464
496
|
}
|
|
465
497
|
const { pafiFeeRecipient } = (0, import_core9.getContractAddresses)(request.chainId);
|
|
466
498
|
const universalRouter = import_core9.UNIVERSAL_ROUTER_ADDRESSES[request.chainId];
|
|
467
499
|
if (!universalRouter) {
|
|
468
|
-
throw new
|
|
500
|
+
throw new import_core9.ValidationError(
|
|
501
|
+
"ROUTER_NOT_DEPLOYED",
|
|
502
|
+
`handleSwap: no UniversalRouter for chainId ${request.chainId}`
|
|
503
|
+
);
|
|
469
504
|
}
|
|
470
505
|
const inputTokenAddress = (0, import_viem4.getAddress)(request.inputTokenAddress);
|
|
471
506
|
const outputTokenAddress = (0, import_viem4.getAddress)(request.outputTokenAddress);
|
|
@@ -481,9 +516,11 @@ var TradingHandlers = class {
|
|
|
481
516
|
request.amount,
|
|
482
517
|
pools
|
|
483
518
|
);
|
|
484
|
-
} catch {
|
|
485
|
-
|
|
486
|
-
|
|
519
|
+
} catch (err) {
|
|
520
|
+
const cause = err instanceof Error ? err.message : String(err);
|
|
521
|
+
throw new import_core9.ValidationError(
|
|
522
|
+
"NO_SWAP_PATH",
|
|
523
|
+
`handleSwap: no swap path found from ${inputTokenAddress} to ${outputTokenAddress} (cause: ${cause})`
|
|
487
524
|
);
|
|
488
525
|
}
|
|
489
526
|
const gasFeeAmountOutput = request.gasFeeAmountOutput !== void 0 ? request.gasFeeAmountOutput : await quoteOperatorFeeOutput(
|
|
@@ -496,8 +533,13 @@ var TradingHandlers = class {
|
|
|
496
533
|
const estimatedOutputAmount = quoteResult.bestRoute.amountOut;
|
|
497
534
|
const minAmountOut = estimatedOutputAmount * BigInt(1e4 - slippageBps) / 10000n;
|
|
498
535
|
if (gasFeeAmountOutput > 0n && minAmountOut < gasFeeAmountOutput) {
|
|
499
|
-
throw new
|
|
500
|
-
|
|
536
|
+
throw new import_core9.ValidationError(
|
|
537
|
+
"AMOUNT_TOO_SMALL_FOR_FEE",
|
|
538
|
+
`handleSwap: minAmountOut (${minAmountOut}) below operator fee (${gasFeeAmountOutput}) \u2014 increase swap amount or accept tighter slippage`,
|
|
539
|
+
{
|
|
540
|
+
minAmountOut: minAmountOut.toString(),
|
|
541
|
+
gasFeeAmountOutput: gasFeeAmountOutput.toString()
|
|
542
|
+
}
|
|
501
543
|
);
|
|
502
544
|
}
|
|
503
545
|
const deadline = BigInt(Math.floor(Date.now() / 1e3) + 5 * 60);
|
|
@@ -561,23 +603,38 @@ var TradingHandlers = class {
|
|
|
561
603
|
*/
|
|
562
604
|
async handlePerpDeposit(authenticatedAddress, request) {
|
|
563
605
|
if ((0, import_viem4.getAddress)(authenticatedAddress) !== (0, import_viem4.getAddress)(request.userAddress)) {
|
|
564
|
-
throw new
|
|
606
|
+
throw new import_core9.ValidationError(
|
|
607
|
+
"USER_ADDRESS_MISMATCH",
|
|
565
608
|
`handlePerpDeposit: authenticatedAddress (${authenticatedAddress}) does not match request.userAddress (${request.userAddress})`
|
|
566
609
|
);
|
|
567
610
|
}
|
|
568
611
|
if (request.chainId !== this.chainId) {
|
|
569
|
-
throw new
|
|
612
|
+
throw new import_core9.ValidationError(
|
|
613
|
+
"UNSUPPORTED_CHAIN_ID",
|
|
614
|
+
`handlePerpDeposit: unsupported chainId ${request.chainId}`,
|
|
615
|
+
{ requested: request.chainId, supported: this.chainId }
|
|
616
|
+
);
|
|
570
617
|
}
|
|
571
618
|
if (request.amount <= 0n) {
|
|
572
|
-
throw new
|
|
619
|
+
throw new import_core9.ValidationError(
|
|
620
|
+
"INVALID_AMOUNT",
|
|
621
|
+
"handlePerpDeposit: amount must be positive"
|
|
622
|
+
);
|
|
573
623
|
}
|
|
574
624
|
const vault = import_core9.ORDERLY_VAULT_ADDRESSES[request.chainId];
|
|
575
625
|
if (!vault) {
|
|
576
|
-
throw new
|
|
626
|
+
throw new import_core9.ValidationError(
|
|
627
|
+
"VAULT_NOT_DEPLOYED",
|
|
628
|
+
`handlePerpDeposit: no Orderly Vault for chainId ${request.chainId}`
|
|
629
|
+
);
|
|
577
630
|
}
|
|
578
631
|
const brokerHash = import_core9.BROKER_HASHES[request.brokerId];
|
|
579
632
|
if (!brokerHash) {
|
|
580
|
-
throw new
|
|
633
|
+
throw new import_core9.ValidationError(
|
|
634
|
+
"UNKNOWN_BROKER_ID",
|
|
635
|
+
`handlePerpDeposit: unknown brokerId "${request.brokerId}"`,
|
|
636
|
+
{ brokerId: request.brokerId }
|
|
637
|
+
);
|
|
581
638
|
}
|
|
582
639
|
const tokenHash = import_core9.TOKEN_HASHES.USDC;
|
|
583
640
|
const userAddress = (0, import_viem4.getAddress)(request.userAddress);
|
|
@@ -596,8 +653,10 @@ var TradingHandlers = class {
|
|
|
596
653
|
})
|
|
597
654
|
]);
|
|
598
655
|
if (!brokerAllowed) {
|
|
599
|
-
throw new
|
|
600
|
-
|
|
656
|
+
throw new import_core9.ValidationError(
|
|
657
|
+
"BROKER_NOT_WHITELISTED",
|
|
658
|
+
`handlePerpDeposit: broker "${request.brokerId}" is not whitelisted on Orderly Vault`,
|
|
659
|
+
{ brokerId: request.brokerId, brokerHash }
|
|
601
660
|
);
|
|
602
661
|
}
|
|
603
662
|
const accountId = (0, import_core9.computeAccountId)(userAddress, brokerHash);
|
|
@@ -638,15 +697,25 @@ var TradingHandlers = class {
|
|
|
638
697
|
args: [relayRequest]
|
|
639
698
|
});
|
|
640
699
|
if (relayTokenFee > maxRelayFee) {
|
|
641
|
-
throw new
|
|
642
|
-
|
|
700
|
+
throw new import_core9.ValidationError(
|
|
701
|
+
"RELAY_FEE_EXCEEDS_MAX",
|
|
702
|
+
`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.`,
|
|
703
|
+
{
|
|
704
|
+
relayTokenFee: relayTokenFee.toString(),
|
|
705
|
+
maxRelayFee: maxRelayFee.toString()
|
|
706
|
+
}
|
|
643
707
|
);
|
|
644
708
|
}
|
|
645
709
|
if (relayTokenFee >= request.amount) {
|
|
646
710
|
const feeUsdc = Number(relayTokenFee) / 1e6;
|
|
647
711
|
const amountUsdc = Number(request.amount) / 1e6;
|
|
648
|
-
throw new
|
|
649
|
-
|
|
712
|
+
throw new import_core9.ValidationError(
|
|
713
|
+
"RELAY_FEE_EXCEEDS_AMOUNT",
|
|
714
|
+
`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.`,
|
|
715
|
+
{
|
|
716
|
+
relayTokenFee: relayTokenFee.toString(),
|
|
717
|
+
amount: request.amount.toString()
|
|
718
|
+
}
|
|
650
719
|
);
|
|
651
720
|
}
|
|
652
721
|
const userOp2 = (0, import_core9.buildPerpDepositViaRelay)({
|
|
@@ -721,6 +790,241 @@ async function quoteOperatorFeeOutput(provider, chainId, outputTokenAddress) {
|
|
|
721
790
|
|
|
722
791
|
// src/pools.ts
|
|
723
792
|
var import_core10 = require("@pafi-dev/core");
|
|
793
|
+
|
|
794
|
+
// src/direct/swapDirect.ts
|
|
795
|
+
var import_core11 = require("@pafi-dev/core");
|
|
796
|
+
async function swapDirect(params) {
|
|
797
|
+
const code = await params.publicClient.getCode({
|
|
798
|
+
address: params.userAddress
|
|
799
|
+
});
|
|
800
|
+
const delegate = (0, import_core11.parseEip7702DelegatedAddress)(code);
|
|
801
|
+
if (!delegate) {
|
|
802
|
+
throw new Error(
|
|
803
|
+
`swapDirect: user ${params.userAddress} is not EIP-7702 delegated. Run \`delegateDirect()\` first (user pays a one-time delegation tx) or use the AA path via \`TradingHandlers.handleSwap()\` + sponsor-relayer.`
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
const impl = (0, import_core11.detectDelegateImpl)(delegate);
|
|
807
|
+
if (impl === "unknown") {
|
|
808
|
+
params.onWarning?.(
|
|
809
|
+
`swapDirect: user delegated to ${delegate} which is not a PAFI-recognised impl (expected ${import_core11.SIMPLE_7702_IMPL_BASE_MAINNET} or ${import_core11.BATCH_EXECUTOR_7702_IMPL}). Continuing \u2014 execute will revert if the impl doesn't expose executeBatch.`
|
|
810
|
+
);
|
|
811
|
+
}
|
|
812
|
+
const universalRouter = import_core11.UNIVERSAL_ROUTER_ADDRESSES[params.chainId];
|
|
813
|
+
if (!universalRouter) {
|
|
814
|
+
throw new Error(`swapDirect: no UniversalRouter for chainId ${params.chainId}`);
|
|
815
|
+
}
|
|
816
|
+
if (params.amount <= 0n) {
|
|
817
|
+
throw new Error("swapDirect: amount must be positive");
|
|
818
|
+
}
|
|
819
|
+
let quoteResult;
|
|
820
|
+
try {
|
|
821
|
+
quoteResult = await findBestQuote(
|
|
822
|
+
params.publicClient,
|
|
823
|
+
params.chainId,
|
|
824
|
+
params.inputTokenAddress,
|
|
825
|
+
params.outputTokenAddress,
|
|
826
|
+
params.amount,
|
|
827
|
+
params.pools ?? []
|
|
828
|
+
);
|
|
829
|
+
} catch (err) {
|
|
830
|
+
const cause = err instanceof Error ? err.message : String(err);
|
|
831
|
+
throw new Error(
|
|
832
|
+
`swapDirect: no swap path found from ${params.inputTokenAddress} to ${params.outputTokenAddress} (cause: ${cause})`
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
const hops = quoteResult.bestRoute.path.length;
|
|
836
|
+
const slippageBps = params.slippageBps ?? (hops > 1 ? 100 : 50);
|
|
837
|
+
const estimatedOutputAmount = quoteResult.bestRoute.amountOut;
|
|
838
|
+
const minAmountOut = estimatedOutputAmount * BigInt(1e4 - slippageBps) / 10000n;
|
|
839
|
+
const gasFeeAmountOutput = params.gasFeeAmountOutput ?? 0n;
|
|
840
|
+
if (gasFeeAmountOutput > 0n && minAmountOut < gasFeeAmountOutput) {
|
|
841
|
+
throw new Error(
|
|
842
|
+
`swapDirect: minAmountOut (${minAmountOut}) below operator fee (${gasFeeAmountOutput})`
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
const deadline = params.deadline ?? BigInt(Math.floor(Date.now() / 1e3) + 5 * 60);
|
|
846
|
+
const { pafiFeeRecipient } = (0, import_core11.getContractAddresses)(params.chainId);
|
|
847
|
+
const userOp = buildSwapUserOp({
|
|
848
|
+
userAddress: params.userAddress,
|
|
849
|
+
aaNonce: 0n,
|
|
850
|
+
// ignored on the native-tx path; nonce comes from EOA tx count
|
|
851
|
+
inputTokenAddress: params.inputTokenAddress,
|
|
852
|
+
outputTokenAddress: params.outputTokenAddress,
|
|
853
|
+
universalRouterAddress: universalRouter,
|
|
854
|
+
amountIn: params.amount,
|
|
855
|
+
minAmountOut,
|
|
856
|
+
swapPath: quoteResult.bestRoute.path,
|
|
857
|
+
deadline,
|
|
858
|
+
gasFeeAmountOutput,
|
|
859
|
+
feeRecipient: pafiFeeRecipient
|
|
860
|
+
});
|
|
861
|
+
const account = params.walletClient.account;
|
|
862
|
+
if (!account) {
|
|
863
|
+
throw new Error(
|
|
864
|
+
"swapDirect: walletClient has no account attached \u2014 cannot send tx"
|
|
865
|
+
);
|
|
866
|
+
}
|
|
867
|
+
const txHash = await params.walletClient.sendTransaction({
|
|
868
|
+
account,
|
|
869
|
+
chain: params.walletClient.chain,
|
|
870
|
+
to: params.userAddress,
|
|
871
|
+
value: 0n,
|
|
872
|
+
data: userOp.callData
|
|
873
|
+
});
|
|
874
|
+
let receipt;
|
|
875
|
+
if (params.waitForReceipt !== false) {
|
|
876
|
+
try {
|
|
877
|
+
receipt = await params.publicClient.waitForTransactionReceipt({
|
|
878
|
+
hash: txHash
|
|
879
|
+
});
|
|
880
|
+
} catch (err) {
|
|
881
|
+
params.onWarning?.(
|
|
882
|
+
`swapDirect: tx ${txHash} sent but receipt fetch failed: ${err instanceof Error ? err.message : String(err)}`
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
return {
|
|
887
|
+
txHash,
|
|
888
|
+
receipt,
|
|
889
|
+
estimatedOutputAmount,
|
|
890
|
+
minAmountOut,
|
|
891
|
+
hops,
|
|
892
|
+
deadline,
|
|
893
|
+
feeAmountUsed: gasFeeAmountOutput
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// src/direct/perpDepositDirect.ts
|
|
898
|
+
var import_core12 = require("@pafi-dev/core");
|
|
899
|
+
async function perpDepositDirect(params) {
|
|
900
|
+
if (params.amount <= 0n) {
|
|
901
|
+
throw new Error("perpDepositDirect: amount must be positive");
|
|
902
|
+
}
|
|
903
|
+
const code = await params.publicClient.getCode({
|
|
904
|
+
address: params.userAddress
|
|
905
|
+
});
|
|
906
|
+
const delegate = (0, import_core12.parseEip7702DelegatedAddress)(code);
|
|
907
|
+
if (!delegate) {
|
|
908
|
+
throw new Error(
|
|
909
|
+
`perpDepositDirect: user ${params.userAddress} is not EIP-7702 delegated. Run \`delegateDirect()\` first or use the AA path via \`TradingHandlers.handlePerpDeposit()\` + sponsor-relayer.`
|
|
910
|
+
);
|
|
911
|
+
}
|
|
912
|
+
const impl = (0, import_core12.detectDelegateImpl)(delegate);
|
|
913
|
+
if (impl === "unknown") {
|
|
914
|
+
params.onWarning?.(
|
|
915
|
+
`perpDepositDirect: user delegated to ${delegate} (not a PAFI-recognised impl ${import_core12.SIMPLE_7702_IMPL_BASE_MAINNET} / ${import_core12.BATCH_EXECUTOR_7702_IMPL}). Continuing \u2014 execute will revert if the impl doesn't expose executeBatch.`
|
|
916
|
+
);
|
|
917
|
+
}
|
|
918
|
+
const vault = import_core12.ORDERLY_VAULT_ADDRESSES[params.chainId];
|
|
919
|
+
if (!vault) {
|
|
920
|
+
throw new Error(
|
|
921
|
+
`perpDepositDirect: no Orderly Vault for chainId ${params.chainId}`
|
|
922
|
+
);
|
|
923
|
+
}
|
|
924
|
+
const brokerHash = import_core12.BROKER_HASHES[params.brokerId];
|
|
925
|
+
if (!brokerHash) {
|
|
926
|
+
throw new Error(
|
|
927
|
+
`perpDepositDirect: unknown brokerId "${params.brokerId}"`
|
|
928
|
+
);
|
|
929
|
+
}
|
|
930
|
+
const tokenHash = import_core12.TOKEN_HASHES.USDC;
|
|
931
|
+
const [usdcAddress, brokerAllowed] = await Promise.all([
|
|
932
|
+
params.publicClient.readContract({
|
|
933
|
+
address: vault,
|
|
934
|
+
abi: import_core12.ORDERLY_VAULT_ABI,
|
|
935
|
+
functionName: "getAllowedToken",
|
|
936
|
+
args: [tokenHash]
|
|
937
|
+
}),
|
|
938
|
+
params.publicClient.readContract({
|
|
939
|
+
address: vault,
|
|
940
|
+
abi: import_core12.ORDERLY_VAULT_ABI,
|
|
941
|
+
functionName: "getAllowedBroker",
|
|
942
|
+
args: [brokerHash]
|
|
943
|
+
})
|
|
944
|
+
]);
|
|
945
|
+
if (!brokerAllowed) {
|
|
946
|
+
throw new Error(
|
|
947
|
+
`perpDepositDirect: broker "${params.brokerId}" is not whitelisted on Orderly Vault`
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
const { orderlyRelay: relayAddress, pafiFeeRecipient } = (0, import_core12.getContractAddresses)(
|
|
951
|
+
params.chainId
|
|
952
|
+
);
|
|
953
|
+
const RELAY_FEE_FLOOR_USDC = 2000000n;
|
|
954
|
+
const percentCap = params.amount * 500n / 10000n;
|
|
955
|
+
const maxFee = params.maxRelayFee ?? (percentCap > RELAY_FEE_FLOOR_USDC ? percentCap : RELAY_FEE_FLOOR_USDC);
|
|
956
|
+
const relayRequest = {
|
|
957
|
+
token: usdcAddress,
|
|
958
|
+
receiver: params.userAddress,
|
|
959
|
+
brokerHash,
|
|
960
|
+
totalAmount: params.amount,
|
|
961
|
+
maxFee
|
|
962
|
+
};
|
|
963
|
+
const relayTokenFee = await params.publicClient.readContract({
|
|
964
|
+
address: relayAddress,
|
|
965
|
+
abi: import_core12.ORDERLY_RELAY_ABI,
|
|
966
|
+
functionName: "quoteTokenFee",
|
|
967
|
+
args: [relayRequest]
|
|
968
|
+
});
|
|
969
|
+
if (relayTokenFee > maxFee) {
|
|
970
|
+
throw new Error(
|
|
971
|
+
`perpDepositDirect: Relay tokenFee ${relayTokenFee} exceeds maxFee ${maxFee} \u2014 pass a larger \`maxRelayFee\` or increase \`amount\`.`
|
|
972
|
+
);
|
|
973
|
+
}
|
|
974
|
+
if (relayTokenFee >= params.amount) {
|
|
975
|
+
throw new Error(
|
|
976
|
+
`perpDepositDirect: deposit amount ${params.amount} below Relay fee ${relayTokenFee} \u2014 increase \`amount\`.`
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
const gasFeeUsdc = params.gasFeeUsdc ?? 0n;
|
|
980
|
+
const partial = (0, import_core12.buildPerpDepositViaRelay)({
|
|
981
|
+
userAddress: params.userAddress,
|
|
982
|
+
aaNonce: 0n,
|
|
983
|
+
// ignored on the native-tx path
|
|
984
|
+
relayAddress,
|
|
985
|
+
request: relayRequest,
|
|
986
|
+
gasFeeUsdc: gasFeeUsdc > 0n ? gasFeeUsdc : void 0,
|
|
987
|
+
gasFeeUsdcRecipient: gasFeeUsdc > 0n ? pafiFeeRecipient : void 0
|
|
988
|
+
});
|
|
989
|
+
const account = params.walletClient.account;
|
|
990
|
+
if (!account) {
|
|
991
|
+
throw new Error(
|
|
992
|
+
"perpDepositDirect: walletClient has no account attached \u2014 cannot send tx"
|
|
993
|
+
);
|
|
994
|
+
}
|
|
995
|
+
const txHash = await params.walletClient.sendTransaction({
|
|
996
|
+
account,
|
|
997
|
+
chain: params.walletClient.chain,
|
|
998
|
+
to: params.userAddress,
|
|
999
|
+
value: 0n,
|
|
1000
|
+
data: partial.callData
|
|
1001
|
+
});
|
|
1002
|
+
let receipt;
|
|
1003
|
+
if (params.waitForReceipt !== false) {
|
|
1004
|
+
try {
|
|
1005
|
+
receipt = await params.publicClient.waitForTransactionReceipt({
|
|
1006
|
+
hash: txHash
|
|
1007
|
+
});
|
|
1008
|
+
} catch (err) {
|
|
1009
|
+
params.onWarning?.(
|
|
1010
|
+
`perpDepositDirect: tx ${txHash} sent but receipt fetch failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1011
|
+
);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
const accountId = (0, import_core12.computeAccountId)(params.userAddress, brokerHash);
|
|
1015
|
+
return {
|
|
1016
|
+
txHash,
|
|
1017
|
+
receipt,
|
|
1018
|
+
relayTokenFee,
|
|
1019
|
+
maxFee,
|
|
1020
|
+
netDeposit: params.amount - relayTokenFee,
|
|
1021
|
+
feeAmountUsed: gasFeeUsdc,
|
|
1022
|
+
accountId,
|
|
1023
|
+
brokerHash,
|
|
1024
|
+
usdcAddress,
|
|
1025
|
+
relayAddress
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
724
1028
|
// Annotate the CommonJS export names for ESM import in node:
|
|
725
1029
|
0 && (module.exports = {
|
|
726
1030
|
PAFI_SUBGRAPH_URL,
|
|
@@ -736,9 +1040,11 @@ var import_core10 = require("@pafi-dev/core");
|
|
|
736
1040
|
combineRoutes,
|
|
737
1041
|
fetchPafiPools,
|
|
738
1042
|
findBestQuote,
|
|
1043
|
+
perpDepositDirect,
|
|
739
1044
|
quoteBestRoute,
|
|
740
1045
|
quoteExactInput,
|
|
741
1046
|
quoteExactInputSingle,
|
|
742
|
-
simulateSwap
|
|
1047
|
+
simulateSwap,
|
|
1048
|
+
swapDirect
|
|
743
1049
|
});
|
|
744
1050
|
//# sourceMappingURL=index.cjs.map
|