@pafi-dev/core 0.5.17 → 0.5.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1073,6 +1073,14 @@ interface SendWithPaymasterFallbackParams {
1073
1073
  fallbackClient: SmartAccountSender | null | undefined;
1074
1074
  /** Transaction params forwarded verbatim to `sendTransaction`. */
1075
1075
  txParams: Record<string, unknown>;
1076
+ /**
1077
+ * Optional alternate `txParams` used **only on the fallback attempt**.
1078
+ * Lets callers strip operator-fee transfers from the calldata when the
1079
+ * paymaster refuses — there's no point charging a "PAFI gas
1080
+ * reimbursement" fee in PT/USDC if PAFI didn't actually sponsor the
1081
+ * gas. When omitted, the same `txParams` is reused for both attempts.
1082
+ */
1083
+ txParamsFallback?: Record<string, unknown>;
1076
1084
  /**
1077
1085
  * Called just before the fallback attempt so the caller can log or
1078
1086
  * update UI. Receives the error message from the failed primary attempt.
@@ -1104,6 +1112,37 @@ interface SendWithPaymasterFallbackParams {
1104
1112
  */
1105
1113
  declare function sendWithPaymasterFallback(params: SendWithPaymasterFallbackParams): Promise<Hex>;
1106
1114
 
1115
+ interface QuoteOperatorFeePtConfig {
1116
+ provider: PublicClient;
1117
+ chainId: number;
1118
+ pointTokenAddress: Address;
1119
+ /**
1120
+ * ERC-4337 gas units the UserOp will consume on chain. Default
1121
+ * 500_000n covers the common scenarios (mint, swap, perp deposit
1122
+ * via Relay) with margin. Pass a tighter number when you have a
1123
+ * Pimlico estimate to feed in.
1124
+ */
1125
+ gasUnits?: bigint;
1126
+ /**
1127
+ * Premium applied on top of raw gas cost in basis points. Default
1128
+ * 12_000 (= 1.2×, 20% buffer to cover oracle drift + paymaster
1129
+ * overhead). Same default the issuer SDK's `FeeManager` uses.
1130
+ */
1131
+ premiumBps?: number;
1132
+ /** Chainlink ETH/USD feed override. Defaults to chain's address. */
1133
+ chainlinkFeedAddress?: Address;
1134
+ /** Subgraph URL override. Defaults to `PAFI_SUBGRAPH_URL`. */
1135
+ subgraphUrl?: string;
1136
+ /** USDT token decimals. Default 6 (USDC/USDT on Base/Ethereum). */
1137
+ usdtDecimals?: number;
1138
+ /** Fallback ETH price (USD) when Chainlink is unreachable. Default 3000. */
1139
+ fallbackEthPriceUsd?: number;
1140
+ /** Fallback PT price (USDT per 1 PT) when subgraph is unreachable. Default 0.1. */
1141
+ fallbackPtPriceUsdt?: number;
1142
+ fetchImpl?: typeof fetch;
1143
+ }
1144
+ declare function quoteOperatorFeePt(config: QuoteOperatorFeePtConfig): Promise<bigint>;
1145
+
1107
1146
  declare const BATCH_EXECUTOR_ADDRESS_BASE_MAINNET: `0x${string}`;
1108
1147
  declare const BATCH_EXECUTOR_ADDRESS_BASE_SEPOLIA: `0x${string}`;
1109
1148
 
@@ -1558,4 +1597,4 @@ declare class PafiSDK {
1558
1597
  signLoginMessage(message: string): Promise<Hex>;
1559
1598
  }
1560
1599
 
1561
- export { ApiError, BATCH_EXECUTOR_7702_IMPL, BATCH_EXECUTOR_ABI, BATCH_EXECUTOR_ADDRESS_BASE_MAINNET, BATCH_EXECUTOR_ADDRESS_BASE_SEPOLIA, BROKER_HASHES, BestQuote, type BuildDelegationUserOpParams, type BuildPartialUserOpParams, type BuildPerpDepositViaRelayParams, type BuildPerpDepositWithGasDeductionParams, COMMON_POOLS, COMMON_TOKENS, CONTRACT_ADDRESSES, ChainConfig, type CheckEthAndBranchParams, ConfigurationError, type ContractAddresses, DUMMY_SIGNATURE_V07, type DelegateImpl, EIP712Signature, ENTRY_POINT_V07, ENTRY_POINT_V08, LoginMessageParams, MintRequest, type ModalOpenOptions, ORDERLY_RELAY_ABI, ORDERLY_VAULT_ABI, ORDERLY_VAULT_ADDRESSES, ORDERLY_VAULT_BASE_MAINNET, Operation, type OrderlyRelayDepositRequest, PAFI_SUBGRAPH_URL, PERMIT2_ADDRESS, POINT_TOKEN_FACTORY_ADDRESSES, POINT_TOKEN_IMPL_ADDRESSES, POINT_TOKEN_POOLS, type PackedUserOperationMessage, type PafiProxyTransportParams, PafiSDK, PafiSDKConfig, PafiSDKError, type PafiWebModalAdapter, type PafiWebModalHandle, PartialUserOperation, type PaymasterConfig, PointTokenDomainConfig, PoolKey, QuoteResult, ReceiverConsent, SIMPLE_7702_IMPL_BASE_MAINNET, SUPPORTED_CHAINS, type SendWithPaymasterFallbackParams, type SignatureStruct, SignatureVerification, SigningError, SimulationError, type SmartAccountSender, type SponsorshipScenario, type SubmissionPath, SwapSimulationResult, TOKEN_HASHES, UNIVERSAL_ROUTER_ADDRESSES, type UserOpTypedData, UserOperation, V4_QUOTER_ADDRESSES, type VaultDepositFE, _resetPaymasterConfigForTests, assembleUserOperation, buildDelegationUserOp, buildPartialUserOperation, buildPerpDepositViaRelay, buildPerpDepositWithGasDeduction, buildUserOpTypedData, burnRequestTypes, checkDelegation, checkEthAndBranch, computeAccountId, computeAuthorizationHash, computeUserOpHash, createPafiProxyTransport, decodeBatchExecuteCalls, detectDelegateImpl, encodeBatchExecute, erc20ApproveOp, erc20BurnOp, erc20TransferOp, fetchPafiPools, getAaNonce, getContractAddresses, getDummySignatureFor7702, getPafiWebModalAdapter, getPaymasterConfig, isDelegatedTo, isDelegatedToTarget, isPaymasterConfigured, isPaymasterError, mintRequestTypes, openPafiWebModal, openWebPopup, parseEip7702DelegatedAddress, rawCallOp, receiverConsentTypes, sendWithPaymasterFallback, serializeUserOpToJsonRpc, setPafiWebModalAdapter, setPaymasterConfig, webPopupAdapter };
1600
+ export { ApiError, BATCH_EXECUTOR_7702_IMPL, BATCH_EXECUTOR_ABI, BATCH_EXECUTOR_ADDRESS_BASE_MAINNET, BATCH_EXECUTOR_ADDRESS_BASE_SEPOLIA, BROKER_HASHES, BestQuote, type BuildDelegationUserOpParams, type BuildPartialUserOpParams, type BuildPerpDepositViaRelayParams, type BuildPerpDepositWithGasDeductionParams, COMMON_POOLS, COMMON_TOKENS, CONTRACT_ADDRESSES, ChainConfig, type CheckEthAndBranchParams, ConfigurationError, type ContractAddresses, DUMMY_SIGNATURE_V07, type DelegateImpl, EIP712Signature, ENTRY_POINT_V07, ENTRY_POINT_V08, LoginMessageParams, MintRequest, type ModalOpenOptions, ORDERLY_RELAY_ABI, ORDERLY_VAULT_ABI, ORDERLY_VAULT_ADDRESSES, ORDERLY_VAULT_BASE_MAINNET, Operation, type OrderlyRelayDepositRequest, PAFI_SUBGRAPH_URL, PERMIT2_ADDRESS, POINT_TOKEN_FACTORY_ADDRESSES, POINT_TOKEN_IMPL_ADDRESSES, POINT_TOKEN_POOLS, type PackedUserOperationMessage, type PafiProxyTransportParams, PafiSDK, PafiSDKConfig, PafiSDKError, type PafiWebModalAdapter, type PafiWebModalHandle, PartialUserOperation, type PaymasterConfig, PointTokenDomainConfig, PoolKey, type QuoteOperatorFeePtConfig, QuoteResult, ReceiverConsent, SIMPLE_7702_IMPL_BASE_MAINNET, SUPPORTED_CHAINS, type SendWithPaymasterFallbackParams, type SignatureStruct, SignatureVerification, SigningError, SimulationError, type SmartAccountSender, type SponsorshipScenario, type SubmissionPath, SwapSimulationResult, TOKEN_HASHES, UNIVERSAL_ROUTER_ADDRESSES, type UserOpTypedData, UserOperation, V4_QUOTER_ADDRESSES, type VaultDepositFE, _resetPaymasterConfigForTests, assembleUserOperation, buildDelegationUserOp, buildPartialUserOperation, buildPerpDepositViaRelay, buildPerpDepositWithGasDeduction, buildUserOpTypedData, burnRequestTypes, checkDelegation, checkEthAndBranch, computeAccountId, computeAuthorizationHash, computeUserOpHash, createPafiProxyTransport, decodeBatchExecuteCalls, detectDelegateImpl, encodeBatchExecute, erc20ApproveOp, erc20BurnOp, erc20TransferOp, fetchPafiPools, getAaNonce, getContractAddresses, getDummySignatureFor7702, getPafiWebModalAdapter, getPaymasterConfig, isDelegatedTo, isDelegatedToTarget, isPaymasterConfigured, isPaymasterError, mintRequestTypes, openPafiWebModal, openWebPopup, parseEip7702DelegatedAddress, quoteOperatorFeePt, rawCallOp, receiverConsentTypes, sendWithPaymasterFallback, serializeUserOpToJsonRpc, setPafiWebModalAdapter, setPaymasterConfig, webPopupAdapter };
package/dist/index.js CHANGED
@@ -611,19 +611,89 @@ function isPaymasterError(err) {
611
611
  return msg.includes("paymaster") || msg.includes("sponsorship") || msg.includes("aa31") || msg.includes("aa32") || msg.includes("aa33") || msg.includes("aa34") || msg.includes("pm_") || msg.includes("rate limit") || msg.includes("unauthorized") || msg.includes("403") || msg.includes("429") || msg.includes("503");
612
612
  }
613
613
  async function sendWithPaymasterFallback(params) {
614
- const { primaryClient, fallbackClient, txParams, onFallback } = params;
614
+ const { primaryClient, fallbackClient, txParams, txParamsFallback, onFallback } = params;
615
615
  try {
616
616
  return await primaryClient.sendTransaction(txParams);
617
617
  } catch (err) {
618
618
  if (isPaymasterError(err) && fallbackClient) {
619
619
  const msg = err?.message ?? String(err);
620
620
  onFallback?.(msg);
621
- return await fallbackClient.sendTransaction(txParams);
621
+ return await fallbackClient.sendTransaction(txParamsFallback ?? txParams);
622
622
  }
623
623
  throw err;
624
624
  }
625
625
  }
626
626
 
627
+ // src/fee/operatorFeeQuoter.ts
628
+ import { parseAbi } from "viem";
629
+
630
+ // src/subgraph/pools.ts
631
+ import { isAddress } from "viem";
632
+ var PAFI_SUBGRAPH_URL = "https://graph-base-mainnet.pacificfinance.org/subgraphs/name/pafi-subgraph-v2";
633
+ var POOL_QUERY = `
634
+ query GetPoolForPointToken($id: ID!) {
635
+ pafiToken(id: $id) {
636
+ id
637
+ pool {
638
+ id
639
+ feeTier
640
+ tickSpacing
641
+ hooks
642
+ token0 { id }
643
+ token1 { id }
644
+ }
645
+ }
646
+ }
647
+ `;
648
+ function sortCurrencies(a, b) {
649
+ return a.toLowerCase() < b.toLowerCase() ? [a, b] : [b, a];
650
+ }
651
+ async function fetchPafiPools(_chainId, pointTokenAddress, subgraphUrl = PAFI_SUBGRAPH_URL) {
652
+ let response;
653
+ try {
654
+ response = await fetch(subgraphUrl, {
655
+ method: "POST",
656
+ headers: { "Content-Type": "application/json" },
657
+ body: JSON.stringify({
658
+ query: POOL_QUERY,
659
+ variables: { id: pointTokenAddress.toLowerCase() }
660
+ })
661
+ });
662
+ } catch (err) {
663
+ console.warn("[fetchPafiPools] subgraph unreachable:", err.message);
664
+ return [];
665
+ }
666
+ if (!response.ok) {
667
+ console.warn(`[fetchPafiPools] subgraph returned ${response.status}`);
668
+ return [];
669
+ }
670
+ const json = await response.json();
671
+ if (json.errors && json.errors.length > 0) {
672
+ console.warn(
673
+ "[fetchPafiPools] subgraph errors:",
674
+ json.errors.map((e) => e.message).join("; ")
675
+ );
676
+ return [];
677
+ }
678
+ const pool = json.data?.pafiToken?.pool;
679
+ if (!pool) return [];
680
+ if (!isAddress(pool.hooks) || !isAddress(pool.token0.id) || !isAddress(pool.token1.id) || !Number.isFinite(Number(pool.feeTier)) || !Number.isFinite(Number(pool.tickSpacing))) {
681
+ console.error("[fetchPafiPools] invalid pool data in subgraph response \u2014 skipping");
682
+ return [];
683
+ }
684
+ const [currency0, currency1] = sortCurrencies(
685
+ pool.token0.id,
686
+ pool.token1.id
687
+ );
688
+ return [{
689
+ currency0,
690
+ currency1,
691
+ fee: Number(pool.feeTier),
692
+ tickSpacing: Number(pool.tickSpacing),
693
+ hooks: pool.hooks
694
+ }];
695
+ }
696
+
627
697
  // src/contracts/real/addresses.ts
628
698
  var PLACEHOLDER_DEAD = (suffix) => `0x000000000000000000000000000000000000${suffix.toLowerCase().padStart(4, "0")}`;
629
699
  var CONTRACT_ADDRESSES = {
@@ -679,6 +749,120 @@ function getContractAddresses(chainId) {
679
749
  return addrs;
680
750
  }
681
751
 
752
+ // src/fee/operatorFeeQuoter.ts
753
+ var CHAINLINK_ABI = parseAbi([
754
+ "function latestRoundData() external view returns (uint80, int256, uint256, uint256, uint80)"
755
+ ]);
756
+ var CHAINLINK_MAX_AGE_S = 3600n;
757
+ var POOL_PRICE_QUERY = `
758
+ query GetPoolPrice($id: ID!) {
759
+ pafiToken(id: $id) {
760
+ pool {
761
+ token0 { id }
762
+ token1 { id }
763
+ token0Price
764
+ token1Price
765
+ }
766
+ }
767
+ }
768
+ `;
769
+ var DEFAULT_GAS_UNITS = 500000n;
770
+ var DEFAULT_PREMIUM_BPS = 12e3;
771
+ var DEFAULT_USDT_DECIMALS = 6;
772
+ async function quoteOperatorFeePt(config) {
773
+ const {
774
+ provider,
775
+ chainId,
776
+ pointTokenAddress,
777
+ gasUnits = DEFAULT_GAS_UNITS,
778
+ premiumBps = DEFAULT_PREMIUM_BPS,
779
+ subgraphUrl = PAFI_SUBGRAPH_URL,
780
+ usdtDecimals = DEFAULT_USDT_DECIMALS,
781
+ fallbackEthPriceUsd = 3e3,
782
+ fallbackPtPriceUsdt = 0.1,
783
+ fetchImpl = globalThis.fetch
784
+ } = config;
785
+ const chainlinkFeedAddress = config.chainlinkFeedAddress ?? getContractAddresses(chainId).chainlinkEthUsd;
786
+ const gasPrice = await provider.getGasPrice();
787
+ const nativeCost = gasPrice * gasUnits;
788
+ const withPremium = nativeCost * BigInt(premiumBps) / 10000n;
789
+ const [ethPrice8dec, ptPerUsdt18dec] = await Promise.all([
790
+ getEthPrice8dec(provider, chainlinkFeedAddress, fallbackEthPriceUsd),
791
+ getPtPerUsdt18dec(
792
+ fetchImpl,
793
+ subgraphUrl,
794
+ pointTokenAddress,
795
+ fallbackPtPriceUsdt
796
+ )
797
+ ]);
798
+ return withPremium * ethPrice8dec * ptPerUsdt18dec / 10n ** 26n;
799
+ }
800
+ async function getEthPrice8dec(provider, feed, fallback) {
801
+ try {
802
+ const result = await provider.readContract({
803
+ address: feed,
804
+ abi: CHAINLINK_ABI,
805
+ functionName: "latestRoundData"
806
+ });
807
+ const answer = result[1];
808
+ const updatedAt = result[3];
809
+ if (answer <= 0n) throw new Error("Chainlink: non-positive price");
810
+ const ageS = BigInt(Math.floor(Date.now() / 1e3)) - updatedAt;
811
+ if (ageS > CHAINLINK_MAX_AGE_S) {
812
+ throw new Error(`Chainlink: price stale by ${ageS}s`);
813
+ }
814
+ return answer;
815
+ } catch (err) {
816
+ console.warn(
817
+ "[quoteOperatorFeePt] Chainlink unavailable, using fallback:",
818
+ err.message
819
+ );
820
+ return BigInt(Math.round(fallback * 1e8));
821
+ }
822
+ }
823
+ async function getPtPerUsdt18dec(fetchImpl, subgraphUrl, pointTokenAddress, fallbackPtPriceUsdt) {
824
+ try {
825
+ const response = await fetchImpl(subgraphUrl, {
826
+ method: "POST",
827
+ headers: { "Content-Type": "application/json" },
828
+ body: JSON.stringify({
829
+ query: POOL_PRICE_QUERY,
830
+ variables: { id: pointTokenAddress.toLowerCase() }
831
+ })
832
+ });
833
+ if (!response.ok) throw new Error(`subgraph HTTP ${response.status}`);
834
+ const json = await response.json();
835
+ if (json.errors?.length) {
836
+ throw new Error(json.errors.map((e) => e.message).join("; "));
837
+ }
838
+ const pool = json.data?.pafiToken?.pool;
839
+ if (!pool) throw new Error("pafiToken or pool not found");
840
+ const isPtToken0 = pool.token0.id.toLowerCase() === pointTokenAddress.toLowerCase();
841
+ const priceStr = isPtToken0 ? pool.token0Price : pool.token1Price;
842
+ if (!priceStr || Number(priceStr) <= 0) {
843
+ throw new Error(`invalid pool price from subgraph: ${priceStr}`);
844
+ }
845
+ const raw = parseBigDecimalTo18(priceStr);
846
+ if (raw === 0n) {
847
+ throw new Error(`pool price parsed to zero: ${priceStr}`);
848
+ }
849
+ return 10n ** 24n / raw;
850
+ } catch (err) {
851
+ console.warn(
852
+ "[quoteOperatorFeePt] subgraph unavailable, using fallback:",
853
+ err.message
854
+ );
855
+ const ptPerUsdtHuman = 1 / fallbackPtPriceUsdt;
856
+ return parseBigDecimalTo18(ptPerUsdtHuman.toFixed(18));
857
+ }
858
+ }
859
+ function parseBigDecimalTo18(s) {
860
+ const SCALE = 18;
861
+ const [whole = "0", frac = ""] = s.split(".");
862
+ const padded = (frac + "0".repeat(SCALE)).slice(0, SCALE);
863
+ return BigInt(whole + padded);
864
+ }
865
+
682
866
  // src/contracts/real/batchExecutor.ts
683
867
  var BATCH_EXECUTOR_ADDRESS_BASE_MAINNET = getContractAddresses(8453).batchExecutor;
684
868
  var BATCH_EXECUTOR_ADDRESS_BASE_SEPOLIA = getContractAddresses(84532).batchExecutor;
@@ -804,73 +988,6 @@ async function openPafiWebModal(url, options = {}) {
804
988
  );
805
989
  }
806
990
 
807
- // src/subgraph/pools.ts
808
- import { isAddress } from "viem";
809
- var PAFI_SUBGRAPH_URL = "https://graph-base-mainnet.pacificfinance.org/subgraphs/name/pafi-subgraph-v2";
810
- var POOL_QUERY = `
811
- query GetPoolForPointToken($id: ID!) {
812
- pafiToken(id: $id) {
813
- id
814
- pool {
815
- id
816
- feeTier
817
- tickSpacing
818
- hooks
819
- token0 { id }
820
- token1 { id }
821
- }
822
- }
823
- }
824
- `;
825
- function sortCurrencies(a, b) {
826
- return a.toLowerCase() < b.toLowerCase() ? [a, b] : [b, a];
827
- }
828
- async function fetchPafiPools(_chainId, pointTokenAddress, subgraphUrl = PAFI_SUBGRAPH_URL) {
829
- let response;
830
- try {
831
- response = await fetch(subgraphUrl, {
832
- method: "POST",
833
- headers: { "Content-Type": "application/json" },
834
- body: JSON.stringify({
835
- query: POOL_QUERY,
836
- variables: { id: pointTokenAddress.toLowerCase() }
837
- })
838
- });
839
- } catch (err) {
840
- console.warn("[fetchPafiPools] subgraph unreachable:", err.message);
841
- return [];
842
- }
843
- if (!response.ok) {
844
- console.warn(`[fetchPafiPools] subgraph returned ${response.status}`);
845
- return [];
846
- }
847
- const json = await response.json();
848
- if (json.errors && json.errors.length > 0) {
849
- console.warn(
850
- "[fetchPafiPools] subgraph errors:",
851
- json.errors.map((e) => e.message).join("; ")
852
- );
853
- return [];
854
- }
855
- const pool = json.data?.pafiToken?.pool;
856
- if (!pool) return [];
857
- if (!isAddress(pool.hooks) || !isAddress(pool.token0.id) || !isAddress(pool.token1.id) || !Number.isFinite(Number(pool.feeTier)) || !Number.isFinite(Number(pool.tickSpacing))) {
858
- console.error("[fetchPafiPools] invalid pool data in subgraph response \u2014 skipping");
859
- return [];
860
- }
861
- const [currency0, currency1] = sortCurrencies(
862
- pool.token0.id,
863
- pool.token1.id
864
- );
865
- return [{
866
- currency0,
867
- currency1,
868
- fee: Number(pool.feeTier),
869
- tickSpacing: Number(pool.tickSpacing),
870
- hooks: pool.hooks
871
- }];
872
- }
873
-
874
991
  // src/index.ts
875
992
  var PafiSDK = class {
876
993
  _pointTokenAddress;
@@ -1203,6 +1320,7 @@ export {
1203
1320
  quoteBestRoute,
1204
1321
  quoteExactInput,
1205
1322
  quoteExactInputSingle,
1323
+ quoteOperatorFeePt,
1206
1324
  rawCallOp,
1207
1325
  receiverConsentTypes,
1208
1326
  sendWithPaymasterFallback,