@pafi-dev/trading 0.3.3 → 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 +241 -2
- 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 +258 -1
- 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
|
|
|
@@ -788,6 +790,241 @@ async function quoteOperatorFeeOutput(provider, chainId, outputTokenAddress) {
|
|
|
788
790
|
|
|
789
791
|
// src/pools.ts
|
|
790
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
|
+
}
|
|
791
1028
|
// Annotate the CommonJS export names for ESM import in node:
|
|
792
1029
|
0 && (module.exports = {
|
|
793
1030
|
PAFI_SUBGRAPH_URL,
|
|
@@ -803,9 +1040,11 @@ var import_core10 = require("@pafi-dev/core");
|
|
|
803
1040
|
combineRoutes,
|
|
804
1041
|
fetchPafiPools,
|
|
805
1042
|
findBestQuote,
|
|
1043
|
+
perpDepositDirect,
|
|
806
1044
|
quoteBestRoute,
|
|
807
1045
|
quoteExactInput,
|
|
808
1046
|
quoteExactInputSingle,
|
|
809
|
-
simulateSwap
|
|
1047
|
+
simulateSwap,
|
|
1048
|
+
swapDirect
|
|
810
1049
|
});
|
|
811
1050
|
//# sourceMappingURL=index.cjs.map
|