@pafi-dev/core 0.19.0 → 0.22.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 +100 -8
- package/dist/abi/index.cjs +14 -4
- package/dist/abi/index.cjs.map +1 -1
- package/dist/abi/index.d.cts +3846 -482
- package/dist/abi/index.d.ts +3846 -482
- package/dist/abi/index.js +15 -5
- package/dist/{chunk-KRHGFUDI.cjs → chunk-245YA3CQ.cjs} +87 -20
- package/dist/chunk-245YA3CQ.cjs.map +1 -0
- package/dist/{chunk-C7VB6WTL.cjs → chunk-2CU7ZH2A.cjs} +490 -497
- package/dist/chunk-2CU7ZH2A.cjs.map +1 -0
- package/dist/chunk-2DVM77Y2.cjs +5018 -0
- package/dist/chunk-2DVM77Y2.cjs.map +1 -0
- package/dist/{chunk-UZUDJXKE.cjs → chunk-3ZT7KTN4.cjs} +5 -3
- package/dist/chunk-3ZT7KTN4.cjs.map +1 -0
- package/dist/{chunk-H3X3FYUU.js → chunk-4VPIPVV5.js} +62 -35
- package/dist/chunk-4VPIPVV5.js.map +1 -0
- package/dist/{chunk-UCO5DXD6.js → chunk-5Y7MGN56.js} +87 -20
- package/dist/chunk-5Y7MGN56.js.map +1 -0
- package/dist/chunk-DQKCPH6B.cjs +199 -0
- package/dist/chunk-DQKCPH6B.cjs.map +1 -0
- package/dist/{chunk-4TNHRZ4X.js → chunk-K4GBOB5V.js} +4 -2
- package/dist/chunk-K4GBOB5V.js.map +1 -0
- package/dist/{chunk-LF5GIN5P.js → chunk-W2DHHR2N.js} +490 -497
- package/dist/chunk-W2DHHR2N.js.map +1 -0
- package/dist/chunk-ZQVYWMOG.js +5018 -0
- package/dist/chunk-ZQVYWMOG.js.map +1 -0
- package/dist/contract/index.cjs +8 -4
- package/dist/contract/index.cjs.map +1 -1
- package/dist/contract/index.d.cts +53 -27
- package/dist/contract/index.d.ts +53 -27
- package/dist/contract/index.js +13 -9
- package/dist/eip712/index.cjs +3 -3
- package/dist/eip712/index.d.cts +7 -1
- package/dist/eip712/index.d.ts +7 -1
- package/dist/eip712/index.js +2 -2
- package/dist/index.cjs +174 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +604 -10
- package/dist/index.d.ts +604 -10
- package/dist/index.js +174 -24
- package/dist/index.js.map +1 -1
- package/dist/{types-C17pznGz.d.ts → types-Hn1zUPTt.d.cts} +46 -13
- package/dist/{types-C17pznGz.d.cts → types-Hn1zUPTt.d.ts} +46 -13
- package/package.json +12 -12
- package/dist/chunk-4TNHRZ4X.js.map +0 -1
- package/dist/chunk-C7VB6WTL.cjs.map +0 -1
- package/dist/chunk-H3X3FYUU.js.map +0 -1
- package/dist/chunk-KRHGFUDI.cjs.map +0 -1
- package/dist/chunk-LF5GIN5P.js.map +0 -1
- package/dist/chunk-TRYGIC2I.cjs +0 -172
- package/dist/chunk-TRYGIC2I.cjs.map +0 -1
- package/dist/chunk-UCO5DXD6.js.map +0 -1
- package/dist/chunk-UZUDJXKE.cjs.map +0 -1
- package/dist/chunk-XXLIIWIF.cjs +0 -711
- package/dist/chunk-XXLIIWIF.cjs.map +0 -1
- package/dist/chunk-ZJXXCG5P.js +0 -711
- package/dist/chunk-ZJXXCG5P.js.map +0 -1
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/phitran/Pacific-Finance/pafi-backend/pafi-sdk/packages/core/dist/index.cjs","../src/index.ts","../src/errors.ts","../src/perp/buildPerpDepositWithGasDeduction.ts","../src/contracts/real/orderlyVault.ts","../src/userop/operations.ts","../src/userop/batchExecute.ts","../src/userop/buildUserOperation.ts","../src/perp/buildPerpDepositViaRelay.ts","../src/transfer/buildErc20Transfer.ts","../src/userop/types.ts","../src/userop/serializeUserOp.ts","../src/userop/computeUserOpHash.ts","../src/userop/eip7702Helpers.ts","../src/utils/checkEthAndBranch.ts","../src/utils/v3Path.ts","../src/delegation/checkDelegation.ts","../src/delegation/buildDelegationUserOp.ts","../src/delegation/computeAuthorizationHash.ts","../src/delegation/eip7702Authorization.ts","../src/contracts/real/addresses.ts","../src/delegation/delegateDirect.ts","../src/transport/proxyTransport.ts","../src/transport/paymasterFallback.ts","../src/fee/operatorFeeQuoter.ts","../src/subgraph/pools.ts","../src/contracts/real/batchExecutor.ts","../src/contracts/real/pafi-services.ts","../src/web-handoff/webPopup.ts","../src/web-handoff/index.ts"],"names":["encodeFunctionData","erc20Abi","keccak256"],"mappings":"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,IAAA;AACgB,IAAA;AACN,IAAA;AAChD,EAAA;AAAA;AAAA;AAAA;AAMsD,EAAA;AAChB,IAAA;AACY,IAAA;AAClD,EAAA;AAEsE,EAAA;AAChC,IAAA;AACS,IAAA;AAC/C,EAAA;AAKE,EAAA;AAGoC,IAAA;AAC7B,IAAA;AACL,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAM8D,EAAA;AACrD,IAAA;AACgB,MAAA;AACE,MAAA;AACvB,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAQmB,EAAA;AACiB,IAAA;AACE,IAAA;AACb,IAAA;AACT,IAAA;AACiB,MAAA;AAC/B,IAAA;AACgD,IAAA;AAClD,EAAA;AAAA;AAGsD,EAAA;AAClB,IAAA;AACX,IAAA;AACT,IAAA;AACiB,MAAA;AAC/B,IAAA;AAC8C,IAAA;AAChD,EAAA;AACF;ADk3CwD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/Users/phitran/Pacific-Finance/pafi-backend/pafi-sdk/packages/core/dist/index.cjs","sourcesContent":[null,"import { createPublicClient, http } from \"viem\";\nimport type { Address, Hex, PublicClient, WalletClient } from \"viem\";\n\n// -------------------------------------------------------------------------\n// Re-export all sub-modules\n// -------------------------------------------------------------------------\nexport * from \"./types\";\nexport * from \"./constants\";\nexport * from \"./errors\";\nexport * from \"./abi/index\";\nexport * from \"./eip712/index\";\nexport * from \"./contract/index\";\n// Quoting + swap orchestration live in @pafi-dev/trading. Core retains\n// the underlying ABI primitives (universalRouterAbi, permit2Abi,\n// v3QuoterV2Abi) + types (PoolKey, V3Path, QuoteResult) — used by\n// trading — but no longer ships swap UserOp builders / quote\n// orchestration.\nexport * from \"./perp/index\";\nexport * from \"./transfer/index\";\nexport * from \"./auth/index\";\n\n// v1.4 — Account Abstraction primitives (EIP-7702 + ERC-4337 v0.7)\nexport * from \"./userop/index\";\nexport * from \"./paymaster/index\";\nexport * from \"./utils/index\";\nexport * from \"./delegation/index\";\nexport * from \"./transport/index\";\n// Operator-fee quoter — pure on-chain + subgraph reads, lets the SDK\n// Direct path compute the PT operator fee without calling the issuer\n// backend. Sponsor-relayer's `FeeValidatorService` runs the same\n// math server-side (with a 5% tolerance window).\nexport * from \"./fee/index\";\n\n// Contract ABIs + addresses. ABI matches deployed PointToken on Base\n// mainnet; the Base Sepolia row stays placeholder. EIP-712 helpers live\n// under `./eip712/` and are already exported above — not re-exported here.\n// Consumers: `import { POINT_TOKEN_ABI, CONTRACT_ADDRESSES, ... } from '@pafi-dev/core'`\nexport * from \"./contracts/index\";\n\n// PAFI Web handoff (mobile + desktop modal helper).\n// `openPafiWebModal()` + adapter registry for platform-specific UX.\nexport * from \"./web-handoff/index\";\n\n// Subgraph helpers — pool discovery via PAFI-hosted subgraph.\n// Browser and Node compatible (plain fetch).\nexport * from \"./subgraph/index\";\n\n// -------------------------------------------------------------------------\n// Internal imports for PafiSDK class\n// -------------------------------------------------------------------------\nimport { buildMintRequestTypedData, signMintRequest, verifyMintRequest } from \"./eip712/mintRequest\";\nimport {\n getMintRequestNonce,\n getTokenName,\n} from \"./contract/pointToken\";\nimport { createLoginMessage } from \"./auth/loginMessage\";\nimport type { LoginMessageParams } from \"./auth/types\";\nimport { ConfigurationError } from \"./errors\";\nimport type {\n EIP712Signature,\n MintRequest,\n PafiSDKConfig,\n PointTokenDomainConfig,\n SignatureVerification,\n SignatureVerifyOptions,\n} from \"./types\";\n\n// -------------------------------------------------------------------------\n// PafiSDK — convenience class wrapping all contract + crypto primitives.\n//\n// This class is HTTP-client-free on purpose. It covers signing, verifying,\n// contract reads, and calldata encoding — the things that need a signer\n// or a provider but no HTTP layer. The issuer backend defines its own HTTP\n// contract via `@pafi-dev/issuer`; frontends build `fetch()` calls against\n// those types directly.\n// -------------------------------------------------------------------------\n\nexport class PafiSDK {\n private _pointTokenAddress?: Address;\n private _signer?: WalletClient;\n private _provider?: PublicClient;\n private _chainId?: number;\n\n // -------------------------------------------------------------------------\n // Domain namespaces — grouped by concern for better IDE autocomplete.\n // Each property is a plain object of bound methods; the underlying logic\n // lives in the flat methods below so both access styles work.\n // -------------------------------------------------------------------------\n\n readonly mint: {\n buildTypedData: PafiSDK[\"buildMintRequestTypedData\"];\n sign: PafiSDK[\"signMintRequest\"];\n verify: PafiSDK[\"verifyMintRequest\"];\n getNonce: PafiSDK[\"getMintRequestNonce\"];\n };\n\n readonly auth: {\n createLoginMessage: PafiSDK[\"createLoginMessage\"];\n signMessage: PafiSDK[\"signLoginMessage\"];\n };\n\n constructor(config: PafiSDKConfig) {\n this._pointTokenAddress = config.pointTokenAddress;\n this._signer = config.signer;\n this._chainId = config.chainId;\n\n if (config.provider) {\n this._provider = config.provider;\n } else if (config.rpcUrl) {\n this._provider = createPublicClient({\n transport: http(config.rpcUrl),\n });\n }\n\n this.mint = {\n buildTypedData: this.buildMintRequestTypedData.bind(this),\n sign: this.signMintRequest.bind(this),\n verify: this.verifyMintRequest.bind(this),\n getNonce: this.getMintRequestNonce.bind(this),\n };\n\n this.auth = {\n createLoginMessage: this.createLoginMessage.bind(this),\n signMessage: this.signLoginMessage.bind(this),\n };\n }\n\n // -------------------------------------------------------------------------\n // Setters\n // -------------------------------------------------------------------------\n\n setPointTokenAddress(address: Address): void {\n this._pointTokenAddress = address;\n }\n\n setSigner(signer: WalletClient): void {\n this._signer = signer;\n }\n\n setProvider(provider: PublicClient): void {\n this._provider = provider;\n }\n\n // -------------------------------------------------------------------------\n // Private guards\n // -------------------------------------------------------------------------\n\n private requirePointToken(): Address {\n if (!this._pointTokenAddress) {\n throw new ConfigurationError(\"pointTokenAddress not set\");\n }\n return this._pointTokenAddress;\n }\n\n private requireProvider(): PublicClient {\n if (!this._provider) {\n throw new ConfigurationError(\"provider not set\");\n }\n return this._provider;\n }\n\n private requireSigner(): WalletClient {\n if (!this._signer) {\n throw new ConfigurationError(\"signer not set\");\n }\n return this._signer;\n }\n\n private requireChainId(): number {\n if (this._chainId === undefined) {\n throw new ConfigurationError(\"chainId not set\");\n }\n return this._chainId;\n }\n\n // -------------------------------------------------------------------------\n // Domain\n // -------------------------------------------------------------------------\n\n async getDomain(): Promise<PointTokenDomainConfig> {\n const provider = this.requireProvider();\n const pointToken = this.requirePointToken();\n const chainId = this.requireChainId();\n const name = await getTokenName(provider, pointToken);\n return { name, verifyingContract: pointToken, chainId };\n }\n\n // -------------------------------------------------------------------------\n // EIP-712 — delegates to pure functions\n // -------------------------------------------------------------------------\n\n async buildMintRequestTypedData(message: MintRequest) {\n const domain = await this.getDomain();\n return buildMintRequestTypedData(domain, message);\n }\n\n async signMintRequest(message: MintRequest): Promise<EIP712Signature> {\n const domain = await this.getDomain();\n return signMintRequest(this.requireSigner(), domain, message);\n }\n\n async verifyMintRequest(\n message: MintRequest,\n signature: Hex,\n expectedMinter: Address,\n options?: SignatureVerifyOptions,\n ): Promise<SignatureVerification> {\n const domain = await this.getDomain();\n return verifyMintRequest(\n domain,\n message,\n signature,\n expectedMinter,\n options,\n );\n }\n\n // -------------------------------------------------------------------------\n // Contract reads\n // -------------------------------------------------------------------------\n\n async getMintRequestNonce(receiver: Address): Promise<bigint> {\n return getMintRequestNonce(\n this.requireProvider(),\n this.requirePointToken(),\n receiver,\n );\n }\n\n // -------------------------------------------------------------------------\n // Auth — EIP-4361 login helpers (offline, stateless)\n // -------------------------------------------------------------------------\n\n async createLoginMessage(\n params: Omit<LoginMessageParams, \"address\" | \"chainId\">,\n ): Promise<string> {\n const signer = this.requireSigner();\n const chainId = this.requireChainId();\n const account = signer.account;\n if (!account) {\n throw new ConfigurationError(\"signer has no account attached\");\n }\n return createLoginMessage({ ...params, address: account.address, chainId });\n }\n\n /** Sign a login message string with the current signer (personal_sign) */\n async signLoginMessage(message: string): Promise<Hex> {\n const signer = this.requireSigner();\n const account = signer.account;\n if (!account) {\n throw new ConfigurationError(\"signer has no account attached\");\n }\n return signer.signMessage({ account, message });\n }\n}\n\n// `buildUniversalRouterExecuteArgs` and other swap helpers moved to\n// `@pafi-dev/trading` along with `findBestQuote`.\n","/**\n * Unified error base for the entire SDK (core + issuer + trading).\n * Subclasses declare `code` (machine-readable) and `httpStatus`\n * (recommended HTTP status for issuer's controller).\n *\n * Issuer's `createSdkErrorMapper` routes any `PafiSdkError` instance\n * through framework-specific exception factories; non-PafiSdkError\n * still becomes 500.\n */\nexport type SdkErrorHttpStatus =\n | \"not_found\"\n | \"forbidden\"\n | \"unprocessable\"\n | \"service_unavailable\";\n\n/**\n * Stripe-style error taxonomy. The SDK emits one of these on every\n * error so consumers can branch UI behavior on `type` (toast vs modal\n * vs retry banner) without whitelisting individual `code` values.\n */\nexport type PafiErrorType =\n | \"validation_error\"\n | \"authentication_error\"\n | \"authorization_error\"\n | \"not_found_error\"\n | \"business_logic_error\"\n | \"rate_limit_error\"\n | \"server_error\"\n | \"service_unavailable_error\";\n\n/** Numeric HTTP status implied by an `SdkErrorHttpStatus` slot. */\nexport const SDK_ERROR_HTTP_STATUS_CODE: Record<SdkErrorHttpStatus, number> = {\n not_found: 404,\n forbidden: 403,\n unprocessable: 422,\n service_unavailable: 503,\n};\n\n/** Default `type` slot for a numeric HTTP status. */\nexport function defaultErrorTypeForStatus(status: number): PafiErrorType {\n if (status === 400) return \"validation_error\";\n if (status === 401) return \"authentication_error\";\n if (status === 403) return \"authorization_error\";\n if (status === 404) return \"not_found_error\";\n if (status === 422) return \"business_logic_error\";\n if (status === 429) return \"rate_limit_error\";\n if (status === 503) return \"service_unavailable_error\";\n return \"server_error\";\n}\n\nexport abstract class PafiSdkError extends Error {\n abstract readonly code: string;\n /**\n * `true` when the FE should consider a retry safe — typically because\n * the failure is transient.\n */\n readonly safeToRetry: boolean = false;\n readonly details?: unknown;\n abstract readonly httpStatus: SdkErrorHttpStatus;\n /**\n * Optional Stripe-style taxonomy override. Defaults to the type\n * implied by `httpStatus` (forbidden→authorization_error,\n * unprocessable→business_logic_error, etc).\n */\n readonly type?: PafiErrorType;\n /**\n * Optional name of the request field that triggered the error (e.g.\n * `\"amount\"`, `\"chainId\"`). Surfaced on validation failures so the\n * client can highlight the offending field.\n */\n readonly param?: string;\n /**\n * Optional structured context (e.g. `{ available, requested }` for a\n * cap denial). Distinct from `details` — `metadata` is meant for\n * UI consumption; `details` carries raw debug info.\n */\n readonly metadata?: Record<string, unknown>;\n\n constructor(message: string) {\n super(message);\n this.name = new.target.name;\n }\n}\n\n/**\n * SDK-level misconfiguration — required input missing on the\n * `PafiSDK` class or a helper that needs a provider/signer/chainId.\n * The SDK can't service the request until the deployment is fixed,\n * so this routes to **503** in `createSdkErrorMapper`.\n *\n * Note: `@pafi-dev/issuer` has a separate `ConfigurationError` with\n * the same name but a different shape (carries a caller-supplied\n * `code`). Both extend `PafiSdkError`; pick based on which package\n * raised the error.\n */\nexport class ConfigurationError extends PafiSdkError {\n readonly httpStatus = \"service_unavailable\" as const;\n readonly code = \"CONFIGURATION_ERROR\" as const;\n readonly type = \"server_error\" as const;\n constructor(message: string) {\n super(message);\n }\n}\n\n/**\n * EIP-712 / EIP-7702 signing failed inside the SDK — typically the\n * signer wallet rejected, the KMS was unreachable, or a signature\n * post-condition (recover-to-expected-address) didn't hold.\n *\n * Routes to **503** because the most common cause is transient signer\n * infrastructure (KMS hiccup); retry is often safe. Set\n * `safeToRetry = true` from the call site if you can prove it.\n */\nexport class SigningError extends PafiSdkError {\n readonly httpStatus = \"service_unavailable\" as const;\n readonly code = \"SIGNING_FAILED\" as const;\n readonly type = \"server_error\" as const;\n constructor(message: string) {\n super(message);\n }\n}\n\n/**\n * `eth_call` dry-run reverted. The on-chain tx would also revert —\n * caller's params are bad (slippage, insufficient balance, expired\n * deadline). Routes to **422** as a business-logic failure.\n *\n * `operation` is a short tag (`\"swap\"`, `\"perp-deposit\"`, etc.) for\n * log-grouping; `reason` carries the raw revert string surfaced by\n * the simulator.\n */\nexport class SimulationError extends PafiSdkError {\n readonly httpStatus = \"unprocessable\" as const;\n readonly code = \"SIMULATION_FAILED\" as const;\n readonly type = \"business_logic_error\" as const;\n readonly operation: string;\n readonly reason: string;\n constructor(operation: string, reason: string) {\n super(`Simulation failed for ${operation}: ${reason}`);\n this.operation = operation;\n this.reason = reason;\n }\n}\n\n/**\n * External HTTP call (Pimlico bundler, PAFI sponsor-relayer, PAFI\n * issuer-api) failed. Routes to **503**. `upstreamStatus` carries the\n * remote HTTP status when known (e.g. 502 from Pimlico for a bundler\n * outage) — useful for log-grouping but distinct from `httpStatus`\n * which is the status PAFI returns to its OWN caller.\n */\nexport class ApiError extends PafiSdkError {\n readonly httpStatus = \"service_unavailable\" as const;\n readonly code = \"API_ERROR\" as const;\n readonly type = \"service_unavailable_error\" as const;\n readonly upstreamStatus?: number;\n constructor(message: string, upstreamStatus?: number) {\n super(message);\n this.upstreamStatus = upstreamStatus;\n }\n}\n\n/**\n * Thrown by `quoteOperatorFee*` when an upstream price source\n * (Chainlink ETH/USD, PAFI subgraph) is unavailable or stale and the\n * caller did not opt in to the hardcoded fallback prices via\n * `allowStaleFallback: true`.\n *\n * Extends the unified `PafiSdkError` so issuer's `createSdkErrorMapper`\n * routes it to 503 instead of leaking as a 500.\n */\nexport class OracleStaleError extends PafiSdkError {\n readonly httpStatus = \"service_unavailable\" as const;\n readonly code = \"ORACLE_STALE\" as const;\n readonly source: \"chainlink\" | \"subgraph\";\n readonly reason: string;\n constructor(source: \"chainlink\" | \"subgraph\", reason: string) {\n super(`Oracle ${source} unavailable: ${reason}`);\n this.source = source;\n this.reason = reason;\n }\n}\n\n/**\n * Generic 4xx-class validation failure for input checks at any SDK\n * boundary (core helpers, trading handlers, etc.). Issuer's\n * `createSdkErrorMapper` routes to 422.\n *\n * Uses an instance `details` field so subclasses can override the\n * declaration; matches the issuer-side shape that pre-existed here.\n */\nexport class ValidationError extends PafiSdkError {\n readonly httpStatus = \"unprocessable\" as const;\n readonly type = \"validation_error\" as const;\n readonly code: string;\n declare readonly details?: Record<string, unknown>;\n declare readonly param?: string;\n declare readonly metadata?: Record<string, unknown>;\n\n constructor(\n code: string,\n message: string,\n details?: Record<string, unknown>,\n options?: { param?: string; metadata?: Record<string, unknown> },\n ) {\n super(message);\n this.code = code;\n (this as { details?: Record<string, unknown> }).details = details;\n if (options?.param) {\n (this as { param?: string }).param = options.param;\n }\n if (options?.metadata) {\n (this as { metadata?: Record<string, unknown> }).metadata = options.metadata;\n }\n }\n}\n","import { encodeFunctionData } from \"viem\";\nimport type { Address, Hex } from \"viem\";\nimport {\n ORDERLY_VAULT_ABI,\n ORDERLY_VAULT_ADDRESSES,\n type VaultDepositFE,\n} from \"../contracts/real/orderlyVault\";\nimport { erc20ApproveOp, rawCallOp } from \"../userop/operations\";\nimport { buildPartialUserOperation } from \"../userop/buildUserOperation\";\nimport type { Operation, PartialUserOperation } from \"../userop/types\";\n\n/**\n * Deposit USDC from the user's wallet into the Orderly perp Vault on\n * Base mainnet.\n *\n * Builds a `PartialUserOperation` packaging the 2 inner calls:\n *\n * 1. `USDC.approve(orderlyVault, amount)` — let the Vault pull USDC\n * 2. `OrderlyVault.deposit{value: layerZeroFee}(VaultDepositFE)` —\n * transfer USDC into the Vault + emit a LayerZero message that\n * credits the user's perp account on the Orderly chain\n *\n * ## ⚠️ Native ETH constraint\n *\n * PAFI sponsor-relayer sponsors GAS, **not `msg.value`**. The LayerZero\n * cross-chain fee MUST come from the user's own native ETH balance on\n * Base — even when the rest of the UserOp is sponsored.\n *\n * Quote the fee BEFORE calling this builder via:\n *\n * const fee = await client.readContract({\n * address: orderlyVault,\n * abi: ORDERLY_VAULT_ABI,\n * functionName: 'getDepositFee',\n * args: [user, depositData],\n * });\n *\n * If `user.eth < fee`, surface this error to the FE so the user can\n * top up before retrying — the Vault `deposit{value: 0}` call will\n * revert otherwise.\n *\n * ## Why no PT/USDC fee deduction?\n *\n * Unlike Scenario 1/2/4 which deduct an operator fee in PT/USDC inside\n * the batch, perp deposit doesn't append a fee transfer because:\n *\n * - User is paying Orderly's LayerZero fee directly via `msg.value`\n * - There's no PAFI-specific operator cost on top — Orderly handles\n * the off-chain accounting once the LayerZero message lands\n *\n * If we want to charge a PAFI service fee on top later, append a\n * `USDC.transfer(feeRecipient, fee)` op — same pattern as the swap\n * builder.\n */\nexport interface BuildPerpDepositWithGasDeductionParams {\n /** User EOA (will be `msg.sender` via EIP-7702 delegation). */\n userAddress: Address;\n /** ERC-4337 account nonce — fetched from EntryPoint by the caller. */\n aaNonce: bigint;\n\n /** Chain ID for Orderly Vault address resolution. */\n chainId: number;\n /**\n * Override Orderly Vault address (e.g. for fork tests). Defaults to\n * `ORDERLY_VAULT_ADDRESSES[chainId]`.\n */\n vaultAddress?: Address;\n\n /**\n * USDC ERC-20 address — the actual token Orderly accepts. Caller\n * resolves this via `vault.getAllowedToken(TOKEN_HASHES.USDC)` so we\n * don't hardcode the wrong USDC variant (native vs bridged).\n */\n usdcAddress: Address;\n\n /** USDC amount to deposit (uint128, 6 decimals). */\n amount: bigint;\n\n /**\n * Pre-built `VaultDepositFE` struct — caller computes accountId via\n * `computeAccountId(user, brokerHash)` and supplies tokenHash.\n */\n depositData: VaultDepositFE;\n\n /**\n * LayerZero fee in wei — from `vault.getDepositFee(user, data)`.\n * Becomes the `msg.value` on the `deposit()` call. User MUST hold\n * ≥ this much native ETH or the call reverts.\n */\n layerZeroFee: bigint;\n\n gasLimits?: {\n callGasLimit?: bigint;\n verificationGasLimit?: bigint;\n preVerificationGas?: bigint;\n };\n}\n\n/**\n * Build an unsigned UserOp for Scenario 3. Returns a\n * `PartialUserOperation` — caller attaches paymaster sponsorship for\n * gas + the user's UserOp-hash signature, then submits to the Bundler.\n */\nexport function buildPerpDepositWithGasDeduction(\n params: BuildPerpDepositWithGasDeductionParams,\n): PartialUserOperation {\n if (params.amount <= 0n) {\n throw new Error(\"buildPerpDepositWithGasDeduction: amount must be positive\");\n }\n if (params.layerZeroFee < 0n) {\n throw new Error(\n \"buildPerpDepositWithGasDeduction: layerZeroFee cannot be negative\",\n );\n }\n if (params.depositData.tokenAmount !== params.amount) {\n throw new Error(\n `buildPerpDepositWithGasDeduction: depositData.tokenAmount (${params.depositData.tokenAmount}) ` +\n `must equal amount (${params.amount})`,\n );\n }\n\n const vault =\n params.vaultAddress ?? ORDERLY_VAULT_ADDRESSES[params.chainId];\n if (!vault) {\n throw new Error(\n `buildPerpDepositWithGasDeduction: no Orderly Vault address for chainId ${params.chainId}`,\n );\n }\n\n const depositCallData: Hex = encodeFunctionData({\n abi: ORDERLY_VAULT_ABI,\n functionName: \"deposit\",\n args: [params.depositData],\n });\n\n const operations: Operation[] = [\n erc20ApproveOp(params.usdcAddress, vault, params.amount),\n {\n ...rawCallOp(vault, depositCallData),\n // BatchExecutor passes `value` from the inner call along; this\n // becomes the LayerZero fee. The aggregated `msg.value` for the\n // top-level UserOp must equal the sum of inner `value`s.\n value: params.layerZeroFee,\n },\n ];\n\n return buildPartialUserOperation({\n sender: params.userAddress,\n nonce: params.aaNonce,\n operations,\n gasLimits: {\n callGasLimit: params.gasLimits?.callGasLimit ?? 800_000n,\n verificationGasLimit:\n params.gasLimits?.verificationGasLimit ?? 150_000n,\n preVerificationGas: params.gasLimits?.preVerificationGas ?? 50_000n,\n },\n });\n}\n","import { keccak256, encodePacked, encodeAbiParameters } from \"viem\";\nimport type { Address, Hex } from \"viem\";\n\n/**\n * Orderly Network Vault — entrypoint for **perp deposit** flow on\n * Base mainnet (Scenario 3).\n *\n * Source of truth for addresses + integration:\n * https://orderly.network/docs/build-on-omnichain/addresses#base\n *\n * ## Architecture\n *\n * User wallet (Base)\n * │ 1. USDC.approve(vault, amount)\n * │ 2. vault.deposit{value: layerZeroFee}({\n * │ accountId, brokerHash, tokenHash, tokenAmount\n * │ })\n * ▼\n * OrderlyVault (Base)\n * │ — locks USDC, emits LayerZero message\n * ▼\n * Orderly chain (off-chain matching engine)\n * │ — credits the user's perp account\n * ▼\n * User can now place perp orders via Orderly REST/WS API\n *\n * **Important:** `value` is required and is the LayerZero cross-chain\n * fee (paid in native ETH on Base). Quote it via\n * `vault.getDepositFee(user, data)` BEFORE calling deposit. Fees vary\n * with destination chain congestion.\n *\n * ## Sponsored vs direct\n *\n * ERC-4337 paymasters sponsor GAS, not `msg.value`. Even on the\n * sponsored path the user MUST hold enough native ETH on Base to cover\n * `getDepositFee()` (typically ~0.0002 ETH at quiet times).\n */\n\n/** Orderly Vault on Base mainnet (EIP-55 checksum). */\nexport const ORDERLY_VAULT_BASE_MAINNET: Address =\n \"0x816f722424B49Cf1275cc86DA9840Fbd5a6167e9\";\n\n/**\n * Per-chain registry of the Orderly Vault. Add chains here as Orderly\n * deploys them.\n */\nexport const ORDERLY_VAULT_ADDRESSES: Record<number, Address> = {\n 8453: ORDERLY_VAULT_BASE_MAINNET,\n};\n\n/**\n * Pre-computed broker hashes — `keccak256(abi.encodePacked(brokerId))`.\n * Add new brokers as they launch.\n *\n * Reference: https://orderly.network/docs/build-on-omnichain/user-flows/accountId\n */\nexport const BROKER_HASHES = {\n /** Default partner broker on Base — most commonly whitelisted. */\n woofi_pro: keccak256(encodePacked([\"string\"], [\"woofi_pro\"])),\n /** Orderly's own white-label broker. */\n orderly: keccak256(encodePacked([\"string\"], [\"orderly\"])),\n /** LogX. */\n logx: keccak256(encodePacked([\"string\"], [\"logx\"])),\n /** Orderly's demo broker — whitelisted on Base mainnet for dev/test\n * integrations. Same Vault, same path; an Orderly demo account is\n * independent from production accounts. */\n demo: keccak256(encodePacked([\"string\"], [\"demo\"])),\n} as const satisfies Record<string, Hex>;\n\n/**\n * Pre-computed token hashes — `keccak256(abi.encodePacked(tokenSymbol))`.\n * The hash maps to the actual ERC-20 address on each chain via\n * `vault.getAllowedToken(tokenHash)`.\n *\n * Note: Orderly canonicalises by symbol (USDC, not \"USD Coin\").\n */\nexport const TOKEN_HASHES = {\n USDC: keccak256(encodePacked([\"string\"], [\"USDC\"])),\n} as const satisfies Record<string, Hex>;\n\n/**\n * Compute Orderly's `accountId` — uniquely identifies a user+broker\n * tuple. Matches the off-chain formula used by the Orderly SDK so\n * deposits + future REST API calls reference the same account.\n *\n * accountId = keccak256(abi.encode(user, brokerHash))\n *\n * Note `abi.encode` (not `encodePacked`) — uses 32-byte left-padding.\n */\nexport function computeAccountId(\n user: Address,\n brokerHash: Hex,\n): Hex {\n return keccak256(\n encodeAbiParameters(\n [\n { type: \"address\" },\n { type: \"bytes32\" },\n ],\n [user, brokerHash],\n ),\n );\n}\n\n/**\n * Minimal Orderly Vault ABI — just the functions PAFI needs for\n * Scenario 3 (perp deposit). Full ABI is on Orderly's docs site.\n */\nexport const ORDERLY_VAULT_ABI = [\n // Deposit USDC into Orderly perp account. Emits LayerZero message\n // to the Orderly chain. `msg.value` MUST equal `getDepositFee(...)`.\n {\n type: \"function\",\n name: \"deposit\",\n stateMutability: \"payable\",\n inputs: [\n {\n name: \"data\",\n type: \"tuple\",\n components: [\n { name: \"accountId\", type: \"bytes32\" },\n { name: \"brokerHash\", type: \"bytes32\" },\n { name: \"tokenHash\", type: \"bytes32\" },\n { name: \"tokenAmount\", type: \"uint128\" },\n ],\n },\n ],\n outputs: [],\n },\n // Quote the LayerZero fee for a deposit message. Pass the same\n // VaultDepositFE struct you intend to send.\n {\n type: \"function\",\n name: \"getDepositFee\",\n stateMutability: \"view\",\n inputs: [\n { name: \"user\", type: \"address\" },\n {\n name: \"data\",\n type: \"tuple\",\n components: [\n { name: \"accountId\", type: \"bytes32\" },\n { name: \"brokerHash\", type: \"bytes32\" },\n { name: \"tokenHash\", type: \"bytes32\" },\n { name: \"tokenAmount\", type: \"uint128\" },\n ],\n },\n ],\n outputs: [{ name: \"\", type: \"uint256\" }],\n },\n // Pre-flight check — is this brokerId whitelisted on this Vault?\n {\n type: \"function\",\n name: \"getAllowedBroker\",\n stateMutability: \"view\",\n inputs: [{ name: \"brokerHash\", type: \"bytes32\" }],\n outputs: [{ name: \"\", type: \"bool\" }],\n },\n // Pre-flight check — what ERC-20 address does this token symbol\n // resolve to on this Vault?\n {\n type: \"function\",\n name: \"getAllowedToken\",\n stateMutability: \"view\",\n inputs: [{ name: \"tokenHash\", type: \"bytes32\" }],\n outputs: [{ name: \"\", type: \"address\" }],\n },\n] as const;\n\n/** Struct mirror of `IOrderlyVault.VaultDepositFE` for type-safe builders. */\nexport interface VaultDepositFE {\n accountId: Hex;\n brokerHash: Hex;\n tokenHash: Hex;\n /** uint128 — USDC has 6 decimals. */\n tokenAmount: bigint;\n}\n","import { encodeFunctionData, erc20Abi, parseAbi } from \"viem\";\nimport type { Address } from \"viem\";\nimport type { Operation } from \"./types\";\n\n/**\n * ERC20Burnable extension — `burn(uint256 amount)` burns from msg.sender.\n * The EOA (via EIP-7702 delegation) is the `msg.sender` when a batch\n * runs — so this burns the user's balance without any role check.\n */\nconst ERC20_BURNABLE_ABI = parseAbi([\"function burn(uint256 amount)\"]);\n\n/**\n * Build an ERC-20 `transfer(to, amount)` operation. Used inside a batch\n * to move fee tokens from the user to the fee recipient atomically with\n * the main action.\n */\nexport function erc20TransferOp(\n token: Address,\n to: Address,\n amount: bigint,\n): Operation {\n return {\n target: token,\n value: 0n,\n data: encodeFunctionData({\n abi: erc20Abi,\n functionName: \"transfer\",\n args: [to, amount],\n }),\n };\n}\n\n/**\n * Build an ERC-20 `approve(spender, amount)` operation. Used inside a\n * batch before a swap / deposit call so the AMM / protocol can pull\n * tokens from the user.\n */\nexport function erc20ApproveOp(\n token: Address,\n spender: Address,\n amount: bigint,\n): Operation {\n return {\n target: token,\n value: 0n,\n data: encodeFunctionData({\n abi: erc20Abi,\n functionName: \"approve\",\n args: [spender, amount],\n }),\n };\n}\n\n/**\n * Build an ERC-20 `burn(amount)` operation (OpenZeppelin ERC20Burnable\n * extension). Burns from `msg.sender`, which — via EIP-7702 — is the\n * user's EOA.\n *\n * Requires the PointToken contract to expose a public `burn(uint256)`\n * without a role check. See\n * [SDK_V1.4_TASKS.md §11 — PointToken.burn callable via batch].\n */\nexport function erc20BurnOp(token: Address, amount: bigint): Operation {\n return {\n target: token,\n value: 0n,\n data: encodeFunctionData({\n abi: ERC20_BURNABLE_ABI,\n functionName: \"burn\",\n args: [amount],\n }),\n };\n}\n\n/**\n * Build a raw call operation with caller-supplied calldata. Useful for\n * non-ERC-20 contracts (PoolManager.swap, PerpDEX.deposit, Relayer.mint)\n * where the encoding is specific to that protocol.\n */\nexport function rawCallOp(\n target: Address,\n data: `0x${string}`,\n value: bigint = 0n,\n): Operation {\n return { target, value, data };\n}\n","import { decodeFunctionData, encodeFunctionData, parseAbi } from \"viem\";\nimport type { Address, Hex } from \"viem\";\nimport type { Operation } from \"./types\";\n\n/**\n * Standard BatchExecutor ABI — a contract that takes an array of calls\n * and invokes each one in sequence, reverting all if any fail.\n *\n * Function name is `executeBatch` (selector `0x34fcd5be`) to match\n * Pimlico's `Simple7702Account` — the EIP-7702 delegate impl PAFI\n * pins on Base mainnet. The shorter `execute(Call[])` (selector\n * `0x3f707e6b`) only exists on standalone batch executor contracts;\n * calling it on a 7702 delegated EOA falls through to the fallback\n * function and reverts with \"account: not from EntryPoint\" (AA23).\n */\nexport const BATCH_EXECUTOR_ABI = parseAbi([\n \"function executeBatch((address target, uint256 value, bytes data)[] calls)\",\n]);\n\n/**\n * Encode a batch of operations into calldata for\n * `BatchExecutor.executeBatch((address,uint256,bytes)[])`. The\n * resulting calldata goes into `UserOperation.callData`.\n *\n * When the EOA has an EIP-7702 delegation to a BatchExecutor, calling\n * this calldata against the EOA runs all operations with\n * `msg.sender = EOA` for each inner call.\n *\n * @param operations batch of calls, in execution order\n * @returns calldata bytes for `executeBatch((address,uint256,bytes)[])`\n */\nexport function encodeBatchExecute(operations: Operation[]): Hex {\n if (operations.length === 0) {\n throw new Error(\"encodeBatchExecute: operations array must not be empty\");\n }\n return encodeFunctionData({\n abi: BATCH_EXECUTOR_ABI,\n functionName: \"executeBatch\",\n args: [\n operations.map((op) => ({\n target: op.target,\n value: op.value,\n data: op.data,\n })),\n ],\n });\n}\n\n/**\n * Decode `BatchExecutor.executeBatch(calls[])` callData back into\n * individual `{ to, data, value }` objects.\n *\n * Used by issuer backends to convert a `PartialUserOperation.callData`\n * into the `calls[]` array returned to web/FE clients (which submit via\n * `permissionless.sendTransaction({ calls })`).\n */\nexport function decodeBatchExecuteCalls(\n callData: Hex,\n): Array<{ to: string; data: string; value: string }> {\n const { args } = decodeFunctionData({\n abi: BATCH_EXECUTOR_ABI,\n data: callData,\n });\n return (\n args[0] as ReadonlyArray<{ target: Address; value: bigint; data: Hex }>\n ).map((c) => ({\n to: c.target,\n data: c.data,\n value: c.value.toString(),\n }));\n}\n","import type { Address } from \"viem\";\nimport type { Operation, PartialUserOperation, UserOperation } from \"./types\";\nimport { encodeBatchExecute } from \"./batchExecute\";\n\n/**\n * Default gas limits — rough upper bounds for a 2-op batch on Base.\n * Bundler re-estimates before submission, so these are only used when\n * the caller doesn't supply their own.\n */\nconst DEFAULT_CALL_GAS_LIMIT = 500_000n;\nconst DEFAULT_VERIFICATION_GAS_LIMIT = 150_000n;\nconst DEFAULT_PRE_VERIFICATION_GAS = 50_000n;\n\nexport interface BuildPartialUserOpParams {\n /** User's EOA (with EIP-7702 delegation). */\n sender: Address;\n /** Batch of operations — encoded into callData via `encodeBatchExecute`. */\n operations: Operation[];\n /** EntryPoint nonce for this sender. Caller fetches from the EntryPoint contract. */\n nonce: bigint;\n /** Optional gas overrides; bundler re-estimates before submission. */\n gasLimits?: {\n callGasLimit?: bigint;\n verificationGasLimit?: bigint;\n preVerificationGas?: bigint;\n };\n /** Optional fee overrides; bundler usually fills these. */\n feeOverrides?: {\n maxFeePerGas?: bigint;\n maxPriorityFeePerGas?: bigint;\n };\n}\n\n/**\n * Build a partial ERC-4337 UserOperation from a batch of operations.\n * Paymaster fields and signature are populated later:\n * 1. Call `PafiBackendClient.requestSponsorship()` → get paymaster fields\n * 2. Compute userOpHash and have the user sign it (via Privy)\n * 3. Attach `signature` → submit to bundler\n *\n * This function is a pure struct builder — no network calls.\n */\nexport function buildPartialUserOperation(\n params: BuildPartialUserOpParams,\n): PartialUserOperation {\n return {\n sender: params.sender,\n nonce: params.nonce,\n callData: encodeBatchExecute(params.operations),\n callGasLimit: params.gasLimits?.callGasLimit ?? DEFAULT_CALL_GAS_LIMIT,\n verificationGasLimit:\n params.gasLimits?.verificationGasLimit ??\n DEFAULT_VERIFICATION_GAS_LIMIT,\n preVerificationGas:\n params.gasLimits?.preVerificationGas ?? DEFAULT_PRE_VERIFICATION_GAS,\n maxFeePerGas: params.feeOverrides?.maxFeePerGas ?? 0n,\n maxPriorityFeePerGas: params.feeOverrides?.maxPriorityFeePerGas ?? 0n,\n };\n}\n\n/**\n * Assemble a full UserOperation once paymaster fields + signature are\n * known. Used after `PafiBackendClient.requestSponsorship()` and user\n * signing complete.\n */\nexport function assembleUserOperation(\n partial: PartialUserOperation,\n paymaster: {\n paymaster: Address;\n paymasterData: `0x${string}`;\n paymasterVerificationGasLimit: bigint;\n paymasterPostOpGasLimit: bigint;\n },\n signature: `0x${string}`,\n): UserOperation {\n return {\n ...partial,\n paymaster: paymaster.paymaster,\n paymasterData: paymaster.paymasterData,\n paymasterVerificationGasLimit: paymaster.paymasterVerificationGasLimit,\n paymasterPostOpGasLimit: paymaster.paymasterPostOpGasLimit,\n signature,\n };\n}\n","import { encodeFunctionData } from \"viem\";\nimport type { Address, Hex } from \"viem\";\nimport { erc20ApproveOp, erc20TransferOp, rawCallOp } from \"../userop/operations\";\nimport { buildPartialUserOperation } from \"../userop/buildUserOperation\";\nimport type { Operation, PartialUserOperation } from \"../userop/types\";\n\n/**\n * Minimal ABI for the Orderly perp-deposit Relay (deployed at\n * `CONTRACT_ADDRESSES[chainId].orderlyRelay`).\n *\n * The Relay holds an ETH reserve and pays Orderly's LayerZero msg.value\n * out of it. Users only need USDC + the Relay-charged USDC fee — they\n * never hold ETH for msg.value, which unblocks the ERC-4337 sponsored\n * gas path for perp deposits (paymaster covers gas, NOT msg.value).\n */\nexport const ORDERLY_RELAY_ABI = [\n {\n type: \"function\",\n name: \"deposit\",\n stateMutability: \"nonpayable\",\n inputs: [\n {\n name: \"req\",\n type: \"tuple\",\n components: [\n { name: \"token\", type: \"address\" },\n { name: \"receiver\", type: \"address\" },\n { name: \"brokerHash\", type: \"bytes32\" },\n { name: \"totalAmount\", type: \"uint128\" },\n { name: \"maxFee\", type: \"uint128\" },\n ],\n },\n ],\n outputs: [],\n },\n {\n type: \"function\",\n name: \"quoteTokenFee\",\n stateMutability: \"view\",\n inputs: [\n {\n name: \"req\",\n type: \"tuple\",\n components: [\n { name: \"token\", type: \"address\" },\n { name: \"receiver\", type: \"address\" },\n { name: \"brokerHash\", type: \"bytes32\" },\n { name: \"totalAmount\", type: \"uint128\" },\n { name: \"maxFee\", type: \"uint128\" },\n ],\n },\n ],\n outputs: [{ name: \"\", type: \"uint128\" }],\n },\n] as const;\n\n/**\n * `Relay.DepositRequest` — the struct passed to `deposit()` and\n * `quoteTokenFee()`. The Relay pulls `totalAmount` of `token` from\n * `msg.sender`, computes a token-denominated fee (capped at `maxFee`)\n * to cover the LayerZero msg.value out of its ETH reserve, and forwards\n * `(totalAmount - tokenFee)` to Orderly Vault on behalf of `receiver`.\n */\nexport interface OrderlyRelayDepositRequest {\n /** ERC-20 to deposit (must be registered + enabled on the Relay). */\n token: Address;\n /** Orderly account owner — typically the user's EOA. */\n receiver: Address;\n /** Orderly broker hash (e.g. `BROKER_HASHES.woofi_pro`). */\n brokerHash: Hex;\n /** Total amount the user is sending to the Relay (raw token units, uint128). */\n totalAmount: bigint;\n /** Max acceptable token fee — slippage cap on the Relay's USD-pricing. */\n maxFee: bigint;\n}\n\nexport interface BuildPerpDepositViaRelayParams {\n /** User EOA (msg.sender via EIP-7702 delegation). */\n userAddress: Address;\n /** ERC-4337 account nonce. */\n aaNonce: bigint;\n /** Relay contract address — `getContractAddresses(chainId).orderlyRelay`. */\n relayAddress: Address;\n /** Deposit request (token, receiver, brokerHash, totalAmount, maxFee). */\n request: OrderlyRelayDepositRequest;\n\n /**\n * Optional USDC (input-token) gas-fee transfer prepended to the batch.\n * The user holds USDC at the start of the batch, so the fee comes out\n * of the same input token — no second-token requirement. Set both\n * `gasFeeUsdcRecipient` and `gasFeeUsdc` together for sponsored\n * flows (PAFI gas reimbursement). Pass `0n` / `undefined` for the\n * fallback path where the user pays ERC-4337 gas in ETH directly.\n *\n * Input-token fee position rule: user holds USDC BEFORE deposit\n * (token-availability), so charge there.\n */\n gasFeeUsdc?: bigint;\n gasFeeUsdcRecipient?: Address;\n\n gasLimits?: {\n callGasLimit?: bigint;\n verificationGasLimit?: bigint;\n preVerificationGas?: bigint;\n };\n}\n\n/**\n * Build a UserOp for Orderly perp deposit via the PAFI Relay.\n *\n * Sponsored ops: `[USDC.transfer(feeRecipient, gasFeeUsdc), USDC.approve(relay, total), Relay.deposit(req)]`\n * Fallback ops: `[ USDC.approve(relay, total), Relay.deposit(req)]`\n *\n * No `msg.value` — the Relay covers LayerZero out of its own ETH reserve.\n *\n * Fee position rule: user holds USDC at start of batch → fee transfer\n * runs FIRST in the same token. User must hold `totalAmount + gasFeeUsdc`\n * USDC; net deposit to Orderly is `totalAmount - relayTokenFee`.\n */\nexport function buildPerpDepositViaRelay(\n params: BuildPerpDepositViaRelayParams,\n): PartialUserOperation {\n if (params.request.totalAmount <= 0n) {\n throw new Error(\"buildPerpDepositViaRelay: totalAmount must be positive\");\n }\n if (params.request.maxFee < 0n) {\n throw new Error(\"buildPerpDepositViaRelay: maxFee cannot be negative\");\n }\n if (!params.relayAddress) {\n throw new Error(\"buildPerpDepositViaRelay: relayAddress required\");\n }\n\n const operations: Operation[] = [];\n\n // Optional USDC (input-token) gas fee transfer — sponsored path.\n // Position: BEFORE approve+deposit, because user holds USDC at this\n // point in the batch (token-availability rule).\n if (params.gasFeeUsdc && params.gasFeeUsdc > 0n) {\n if (!params.gasFeeUsdcRecipient) {\n throw new Error(\n \"buildPerpDepositViaRelay: gasFeeUsdcRecipient required when gasFeeUsdc > 0\",\n );\n }\n operations.push(\n erc20TransferOp(\n params.request.token,\n params.gasFeeUsdcRecipient,\n params.gasFeeUsdc,\n ),\n );\n }\n\n // USDC.approve(relay, totalAmount) — Relay pulls via transferFrom.\n operations.push(\n erc20ApproveOp(\n params.request.token,\n params.relayAddress,\n params.request.totalAmount,\n ),\n );\n\n // Relay.deposit(req) — Relay charges tokenFee, deposits the rest to Orderly.\n const depositCallData: Hex = encodeFunctionData({\n abi: ORDERLY_RELAY_ABI,\n functionName: \"deposit\",\n args: [\n {\n token: params.request.token,\n receiver: params.request.receiver,\n brokerHash: params.request.brokerHash,\n totalAmount: params.request.totalAmount,\n maxFee: params.request.maxFee,\n },\n ],\n });\n operations.push(rawCallOp(params.relayAddress, depositCallData));\n\n return buildPartialUserOperation({\n sender: params.userAddress,\n nonce: params.aaNonce,\n operations,\n gasLimits: {\n callGasLimit: params.gasLimits?.callGasLimit ?? 800_000n,\n verificationGasLimit: params.gasLimits?.verificationGasLimit ?? 150_000n,\n preVerificationGas: params.gasLimits?.preVerificationGas ?? 50_000n,\n },\n });\n}\n","import type { Address } from \"viem\";\nimport type { PartialUserOperation } from \"../userop/types\";\nimport { buildPartialUserOperation } from \"../userop/buildUserOperation\";\nimport { erc20TransferOp } from \"../userop/operations\";\n\n/**\n * Builder for the `erc20-transfer` sponsored scenario.\n *\n * Produces two UserOps from one request — same nonce, same sender:\n *\n * sponsored (2 calls):\n * [ token.transfer(PAFI_FEE_RECIPIENT, fee), token.transfer(recipient, amount) ]\n *\n * fallback (1 call):\n * [ token.transfer(recipient, amount) ]\n *\n * Both calls use the SAME token contract — the fee currency equals the\n * token being sent (decision documented in\n * `docs/SPONSORED_ERC20_TRANSFER_SPEC.md`). Caller is responsible for\n * validating the token is in the sponsorship allowlist; this function\n * just builds calldata, no policy enforcement.\n *\n * The sponsor-relayer's IntentValidator enforces:\n * - exactly 2 calls\n * - both target the same token address\n * - call[0] = transfer to PAFI_FEE_RECIPIENT, amount >= 95% quoted\n * - call[1] = transfer to recipient (not fee recipient, not 0x0)\n *\n * Fallback variant (1 call, no fee) submits no-paymaster — caller's\n * `sendWithPaymasterFallback` routes there when sponsorship is refused.\n */\nexport interface BuildErc20TransferParams {\n /** EIP-7702 delegated user EOA (== smart account sender). */\n userAddress: Address;\n /** ERC-4337 nonce from the EntryPoint. */\n aaNonce: bigint;\n /** Whitelisted ERC-20 to transfer. */\n tokenAddress: Address;\n /** Final recipient. Must NOT equal `feeRecipient` or `0x0...0`. */\n recipient: Address;\n /** Amount to send (token-native units; 6-dec for stables, 18-dec for PT). */\n amount: bigint;\n /**\n * Operator fee in the SAME token. Sponsored variant only — pass\n * `undefined` or 0n to skip and build the no-fee variant.\n */\n feeAmount?: bigint;\n /** Required when `feeAmount > 0`. */\n feeRecipient?: Address;\n /** Override gas limits if you have a tighter Pimlico estimate. */\n gasLimits?: {\n callGasLimit?: bigint;\n verificationGasLimit?: bigint;\n preVerificationGas?: bigint;\n };\n}\n\nexport function buildErc20TransferUserOp(\n params: BuildErc20TransferParams,\n): PartialUserOperation {\n if (params.amount <= 0n) {\n throw new Error(\"buildErc20TransferUserOp: amount must be positive\");\n }\n\n const operations = [];\n\n // Optional fee transfer (sponsored path) — same token as the user's\n // actual transfer, placed FIRST per token-availability convention\n // (matches the perp deposit + swap fee positioning).\n if (params.feeAmount && params.feeAmount > 0n) {\n if (!params.feeRecipient) {\n throw new Error(\n \"buildErc20TransferUserOp: feeRecipient required when feeAmount > 0\",\n );\n }\n operations.push(\n erc20TransferOp(\n params.tokenAddress,\n params.feeRecipient,\n params.feeAmount,\n ),\n );\n }\n\n operations.push(\n erc20TransferOp(params.tokenAddress, params.recipient, params.amount),\n );\n\n return buildPartialUserOperation({\n sender: params.userAddress,\n nonce: params.aaNonce,\n operations,\n gasLimits: {\n // 2 simple ERC-20 transfers + 7702 batch overhead. ~70-90k actual;\n // 200k matches the `delegate` scenario budget and covers premium.\n callGasLimit: params.gasLimits?.callGasLimit ?? 200_000n,\n verificationGasLimit:\n params.gasLimits?.verificationGasLimit ?? 150_000n,\n preVerificationGas: params.gasLimits?.preVerificationGas ?? 50_000n,\n },\n });\n}\n","import type { Address, Hex } from \"viem\";\nexport { ENTRY_POINT_V07, ENTRY_POINT_V08 } from \"../constants\";\n\n/**\n * A single call inside a batch. `BatchExecutor.execute(Call[])` iterates\n * and invokes each one. When the batch runs via EIP-7702 delegation,\n * `msg.sender` for each call is the user's EOA.\n */\nexport interface Operation {\n target: Address;\n value: bigint;\n data: Hex;\n}\n\n/**\n * Paymaster fields attached to a UserOperation. Populated by the\n * PAFI sponsor-relayer (via PAFI Backend proxy) before the user signs.\n */\nexport interface PaymasterFields {\n paymaster: Address;\n paymasterData: Hex;\n paymasterVerificationGasLimit: bigint;\n paymasterPostOpGasLimit: bigint;\n}\n\n/**\n * Partial UserOp used during preparation — before paymaster fields are\n * attached or the user signs.\n */\nexport interface PartialUserOperation {\n sender: Address;\n nonce: bigint;\n callData: Hex;\n callGasLimit: bigint;\n verificationGasLimit: bigint;\n preVerificationGas: bigint;\n maxFeePerGas: bigint;\n maxPriorityFeePerGas: bigint;\n}\n\n/**\n * Full ERC-4337 v0.7 UserOperation, ready for bundler submission.\n */\nexport interface UserOperation extends PartialUserOperation {\n paymaster: Address;\n paymasterData: Hex;\n paymasterVerificationGasLimit: bigint;\n paymasterPostOpGasLimit: bigint;\n signature: Hex;\n}\n\n/**\n * Receipt returned by a bundler after a UserOp lands on-chain.\n */\nexport interface UserOpReceipt {\n userOpHash: Hex;\n success: boolean;\n txHash: Hex;\n blockNumber: bigint;\n gasUsed: bigint;\n /** Effective gas cost paid (wei). */\n actualGasCost: bigint;\n}\n\n/**\n * Sentinel operation value used in tests + docs when `Operation.value`\n * is irrelevant (ERC-20 transfers, for example).\n */\nexport const ZERO_VALUE = 0n;\n","import type { Address, Hex } from \"viem\";\n\n/**\n * Serialize a fully assembled ERC-4337 v0.7 UserOperation into the\n * JSON-RPC wire format expected by `eth_sendUserOperation`.\n *\n * All numeric fields (nonce, gas limits, fees) are converted from bigint\n * to 0x-prefixed hex strings. Optional paymaster fields are included when\n * present, otherwise set to null.\n *\n * Use this on the **backend** (mobile submit path) right before calling\n * `PafiBackendClient.relayUserOperation()`.\n */\nexport function serializeUserOpToJsonRpc(\n userOp: {\n sender: Address;\n nonce: bigint;\n callData: Hex;\n callGasLimit: bigint;\n verificationGasLimit: bigint;\n preVerificationGas: bigint;\n maxFeePerGas: bigint;\n maxPriorityFeePerGas: bigint;\n paymaster?: Address;\n paymasterVerificationGasLimit?: bigint;\n paymasterPostOpGasLimit?: bigint;\n paymasterData?: Hex;\n },\n signature: Hex,\n): Record<string, string | null> {\n const p = userOp;\n return {\n sender: p.sender,\n nonce: `0x${p.nonce.toString(16)}`,\n factory: null,\n factoryData: null,\n callData: p.callData,\n callGasLimit: `0x${p.callGasLimit.toString(16)}`,\n verificationGasLimit: `0x${p.verificationGasLimit.toString(16)}`,\n preVerificationGas: `0x${p.preVerificationGas.toString(16)}`,\n maxFeePerGas: `0x${p.maxFeePerGas.toString(16)}`,\n maxPriorityFeePerGas: `0x${p.maxPriorityFeePerGas.toString(16)}`,\n paymaster: p.paymaster ?? null,\n paymasterData: p.paymasterData ?? null,\n paymasterVerificationGasLimit:\n p.paymasterVerificationGasLimit != null\n ? `0x${p.paymasterVerificationGasLimit.toString(16)}`\n : null,\n paymasterPostOpGasLimit:\n p.paymasterPostOpGasLimit != null\n ? `0x${p.paymasterPostOpGasLimit.toString(16)}`\n : null,\n signature,\n };\n}\n","import {\n concat,\n hashTypedData,\n pad,\n toHex,\n type Address,\n type Hex,\n type TypedDataDomain,\n} from \"viem\";\nimport { ENTRY_POINT_V08 } from \"./types\";\n\nconst PACKED_USER_OPERATION_TYPES = {\n PackedUserOperation: [\n { name: \"sender\", type: \"address\" },\n { name: \"nonce\", type: \"uint256\" },\n { name: \"initCode\", type: \"bytes\" },\n { name: \"callData\", type: \"bytes\" },\n { name: \"accountGasLimits\", type: \"bytes32\" },\n { name: \"preVerificationGas\", type: \"uint256\" },\n { name: \"gasFees\", type: \"bytes32\" },\n { name: \"paymasterAndData\", type: \"bytes\" },\n ],\n} as const;\n\nexport type PackedUserOperationMessage = {\n sender: Address;\n nonce: bigint;\n initCode: Hex;\n callData: Hex;\n accountGasLimits: Hex;\n preVerificationGas: bigint;\n gasFees: Hex;\n paymasterAndData: Hex;\n};\n\nexport type UserOpTypedData = {\n domain: TypedDataDomain;\n types: typeof PACKED_USER_OPERATION_TYPES;\n primaryType: \"PackedUserOperation\";\n message: PackedUserOperationMessage;\n};\n\n/**\n * Build the EIP-712 typed-data payload for an ERC-4337 v0.8 UserOp.\n *\n * The deployed Pimlico `Simple7702Account` (impl `0xe6Cae8...`) validates\n * the user's signature by calling `SignatureCheckerLib.isValidSignatureNow`\n * with the **raw** userOpHash (no EIP-191 prefix). For an EOA signer this\n * means `ecrecover(userOpHash, sig)` must equal the EOA address.\n *\n * Because `userOpHash` is itself the EIP-712 typed digest of the\n * `PackedUserOperation` struct, signing the typed data with\n * `eth_signTypedData_v4` produces a signature whose recover-from-raw-digest\n * == the userOpHash. That matches what the contract expects.\n *\n * Do NOT sign with `personal_sign` / `signMessage({ raw })` — that adds the\n * EIP-191 prefix, which the contract does not undo, so recovery returns a\n * different address and the bundler reverts with `AA24 signature error`.\n */\nexport function buildUserOpTypedData(\n userOp: {\n sender: Address;\n nonce: bigint;\n callData: Hex;\n callGasLimit: bigint;\n verificationGasLimit: bigint;\n preVerificationGas: bigint;\n maxFeePerGas: bigint;\n maxPriorityFeePerGas: bigint;\n paymaster?: Address;\n paymasterVerificationGasLimit?: bigint;\n paymasterPostOpGasLimit?: bigint;\n paymasterData?: Hex;\n },\n chainId: number,\n): UserOpTypedData {\n const accountGasLimits = pack128(\n userOp.verificationGasLimit,\n userOp.callGasLimit,\n );\n const gasFees = pack128(userOp.maxPriorityFeePerGas, userOp.maxFeePerGas);\n\n const paymasterAndData: Hex = userOp.paymaster\n ? concat([\n userOp.paymaster,\n pad(toHex(userOp.paymasterVerificationGasLimit ?? 0n), { size: 16 }),\n pad(toHex(userOp.paymasterPostOpGasLimit ?? 0n), { size: 16 }),\n (userOp.paymasterData ?? \"0x\") as Hex,\n ])\n : \"0x\";\n\n return {\n domain: {\n name: \"ERC4337\",\n version: \"1\",\n chainId,\n verifyingContract: ENTRY_POINT_V08,\n },\n types: PACKED_USER_OPERATION_TYPES,\n primaryType: \"PackedUserOperation\",\n message: {\n sender: userOp.sender,\n nonce: userOp.nonce,\n initCode: \"0x\" as Hex,\n callData: userOp.callData,\n accountGasLimits,\n preVerificationGas: userOp.preVerificationGas,\n gasFees,\n paymasterAndData,\n },\n };\n}\n\n/**\n * EIP-712 typed digest of an ERC-4337 v0.8 UserOp. Equals on-chain\n * `EntryPoint.getUserOpHash(userOp)`.\n */\nexport function computeUserOpHash(\n userOp: Parameters<typeof buildUserOpTypedData>[0],\n chainId: number,\n): Hex {\n const td = buildUserOpTypedData(userOp, chainId);\n return hashTypedData(td);\n}\n\nfunction pack128(hi: bigint, lo: bigint): Hex {\n return `0x${((hi << 128n) | lo).toString(16).padStart(64, \"0\")}` as Hex;\n}\n","import { type Address, type Hex, getAddress } from \"viem\";\n\n/**\n * EIP-7702 delegate impls supported by the PAFI mobile prepare/submit\n * flow. Each impl exposes `executeBatch((address,uint256,bytes)[])`\n * (selector `0x34fcd5be`) so callData encoding is identical, but each\n * may use a slightly different dummy-signature pattern that Pimlico's\n * `pm_sponsorUserOperation` simulation accepts.\n *\n * Current primary impl is `simple7702` (Pimlico's `Simple7702Account`).\n * `batchExecutor` is retained for back-compat with EOAs that delegated\n * to an earlier Coinbase Smart Wallet v2 BatchExecutor — Pimlico is\n * the canonical impl now (the Coinbase SignatureWrapper format caused\n * AA23 0x3c10b94e during validateUserOp).\n */\nexport type DelegateImpl =\n | \"simple7702\" // Pimlico's `to7702SimpleSmartAccount` impl — current\n | \"batchExecutor\" // Legacy Coinbase Smart Wallet v2 BatchExecutor — transitional\n | \"unknown\";\n\n/**\n * Pimlico's `Simple7702Account` implementation address on Base mainnet —\n * the canonical PAFI delegation target. Used by\n * `permissionless.to7702SimpleSmartAccount`.\n */\nexport const SIMPLE_7702_IMPL_BASE_MAINNET =\n \"0xe6Cae83BdE06E4c305530e199D7217f42808555B\" as Address;\n\n/**\n * Legacy Coinbase Smart Wallet v2 BatchExecutor — an earlier PAFI\n * delegation target. Same on Base mainnet + Base Sepolia. Detected here\n * so EOAs that delegated to this address still pass the impl check;\n * new delegations should target {@link SIMPLE_7702_IMPL_BASE_MAINNET}.\n */\nexport const BATCH_EXECUTOR_7702_IMPL =\n \"0x7702cb554e6bFb442cb743A7dF23154544a7176C\" as Address;\n\n/**\n * Standard ERC-4337 v0.7 ECDSA dummy signature — 65 bytes that\n * are well-formed (so signature recovery doesn't crash) but\n * obviously invalid (so they can't accidentally authorize a real\n * tx). Both Simple7702 and BatchExecutor accept this pattern as\n * the \"estimating\" signature during paymaster simulation.\n *\n * Source: viem-account-abstraction's default; matches what\n * `permissionless.toSimpleSmartAccount` and `to7702SimpleSmartAccount`\n * use for `getStubSignature()`.\n */\nexport const DUMMY_SIGNATURE_V07: Hex =\n \"0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c\";\n\n/**\n * Map an EIP-7702 delegate target address to its `DelegateImpl` kind.\n * Returns `'unknown'` when the address isn't a recognised PAFI-supported\n * impl — caller should reject or fall back to a default behaviour.\n */\nexport function detectDelegateImpl(delegate: Address | null | undefined): DelegateImpl {\n if (!delegate) return \"unknown\";\n const addr = getAddress(delegate).toLowerCase();\n if (addr === SIMPLE_7702_IMPL_BASE_MAINNET.toLowerCase()) return \"simple7702\";\n if (addr === BATCH_EXECUTOR_7702_IMPL.toLowerCase()) return \"batchExecutor\";\n return \"unknown\";\n}\n\n/**\n * Return a dummy signature appropriate for a given delegate impl. Used\n * by sponsor-relayer's `pm_sponsorUserOperation` forwarding so Pimlico\n * can simulate validateUserOp without the user's actual signature\n * (which is supplied later via `personal_sign(userOpHash)`).\n *\n * Currently both Simple7702 and BatchExecutor use the same v0.7 ECDSA\n * dummy. Kept impl-keyed so future impls (Safe, Kernel, Biconomy) can\n * supply their own pattern (e.g. ERC-1271 stubs) without forcing a\n * sponsor-relayer redeploy.\n */\nexport function getDummySignatureFor7702(impl: DelegateImpl): Hex {\n // Same dummy works for both impls — both use ECDSA recovery on\n // userOpHash, both accept the v0.7 standard stub.\n void impl;\n return DUMMY_SIGNATURE_V07;\n}\n","import type { Address, PublicClient } from \"viem\";\n\n/**\n * Submission path chosen by `checkEthAndBranch`.\n * - `normal`: initiator has enough ETH; submit via `walletClient.writeContract`\n * or a plain `eth_sendRawTransaction`. No paymaster round-trip.\n * - `paymaster`: initiator doesn't have enough ETH; wrap the batch as a\n * UserOperation and route through PAFI Backend → PAFI sponsor-relayer\n * → Bundler.\n */\nexport type SubmissionPath = \"normal\" | \"paymaster\";\n\nexport interface CheckEthAndBranchParams {\n /** viem PublicClient bound to the target chain. */\n client: PublicClient;\n /** The address whose ETH balance we check. */\n initiator: Address;\n /** Estimated gas cost in wei for the upcoming tx. */\n estimatedGasWei: bigint;\n /**\n * Optional safety margin multiplier (basis points). Defaults to\n * 11_000 (110%) — the initiator needs 10% above the estimate to\n * qualify for the normal path. Prevents edge cases where gas price\n * spikes between estimation and submission cause a \"has enough\"\n * decision to fail at broadcast time.\n */\n marginBps?: number;\n}\n\nconst DEFAULT_MARGIN_BPS = 11_000;\n\n/**\n * Step 3 of the Generalized Flow ([SPONSORED_PATH_FLOW.md §2]):\n * choose between the normal path (initiator pays ETH directly) and the\n * paymaster path (bundler + PAFI sponsor-relayer).\n *\n * Intentionally synchronous in spirit — the only network call is\n * `getBalance`. Callers can parallelize it with other reads.\n */\nexport async function checkEthAndBranch(\n params: CheckEthAndBranchParams,\n): Promise<SubmissionPath> {\n const marginBps = params.marginBps ?? DEFAULT_MARGIN_BPS;\n const required =\n (params.estimatedGasWei * BigInt(marginBps)) / 10_000n;\n\n const balance = await params.client.getBalance({\n address: params.initiator,\n });\n\n return balance >= required ? \"normal\" : \"paymaster\";\n}\n","import { concatHex, getAddress, getContractAddress, keccak256, pad, toHex } from \"viem\";\nimport type { Address, Hex } from \"viem\";\nimport type { V3Path } from \"../types\";\n\n/**\n * Encode a V3 swap path as packed bytes, oriented input→output:\n * `tokens[0] ‖ fees[0] ‖ tokens[1] ‖ fees[1] ‖ … ‖ tokens[N]`\n *\n * Each address is 20 bytes; each fee is uint24 (3 bytes). For an N-hop\n * swap the result is `(N + 1) * 20 + N * 3` bytes.\n *\n * For exact-output quoting the Quoter walks the path output→input —\n * reverse the underlying `tokens` and `fees` arrays before calling this\n * encoder (or use `encodeV3PathReversed`).\n *\n * @throws if `tokens.length !== fees.length + 1` or any input is malformed.\n */\nexport function encodeV3Path(path: V3Path): Hex {\n const { tokens, fees } = path;\n if (tokens.length < 2) {\n throw new Error(\n `encodeV3Path: tokens must contain at least 2 addresses, got ${tokens.length}`,\n );\n }\n if (tokens.length !== fees.length + 1) {\n throw new Error(\n `encodeV3Path: tokens.length must equal fees.length + 1, got ` +\n `tokens=${tokens.length} fees=${fees.length}`,\n );\n }\n\n // Normalize addresses to lowercase. The packed-bytes encoding is\n // case-insensitive on chain (Solidity address bytes carry no case),\n // and viem's `decodeAbiParameters({ type: \"bytes\" }, ...)` always\n // returns lowercase hex — keeping inputs lowercase here makes\n // round-trip comparisons exact equality without case folding.\n const parts: Hex[] = [];\n for (let i = 0; i < fees.length; i++) {\n parts.push(tokens[i]!.toLowerCase() as Hex);\n const fee = fees[i]!;\n if (!Number.isInteger(fee) || fee < 0 || fee > 0xff_ff_ff) {\n throw new Error(\n `encodeV3Path: fees[${i}] must be a uint24 (0..16777215), got ${fee}`,\n );\n }\n parts.push(pad(toHex(fee), { size: 3 }));\n }\n parts.push(tokens[tokens.length - 1]!.toLowerCase() as Hex);\n\n return concatHex(parts);\n}\n\n/**\n * Encode the same path reversed (output→input). Convenience used by\n * exact-output quoting and exact-output swap-builder paths.\n */\nexport function encodeV3PathReversed(path: V3Path): Hex {\n return encodeV3Path({\n tokens: [...path.tokens].reverse(),\n fees: [...path.fees].reverse(),\n });\n}\n\n/**\n * Compute a V3 pool address deterministically from factory + initCodeHash.\n *\n * Uses the standard Uniswap V3 derivation:\n * `keccak256(0xff ‖ factory ‖ keccak256(abi.encode(token0, token1, fee)) ‖ initCodeHash)`\n *\n * Tokens are sorted ascending — the helper accepts them in any order.\n * Returns a checksummed address.\n */\nexport function computeV3PoolAddress(params: {\n factory: Address;\n tokenA: Address;\n tokenB: Address;\n fee: number;\n initCodeHash: Hex;\n}): Address {\n const { factory, tokenA, tokenB, fee, initCodeHash } = params;\n if (!Number.isInteger(fee) || fee < 0 || fee > 0xff_ff_ff) {\n throw new Error(\n `computeV3PoolAddress: fee must be a uint24, got ${fee}`,\n );\n }\n\n const a = getAddress(tokenA).toLowerCase();\n const b = getAddress(tokenB).toLowerCase();\n if (a === b) {\n throw new Error(\n `computeV3PoolAddress: tokenA and tokenB must differ, got ${a}`,\n );\n }\n const [token0, token1] = a < b ? [tokenA, tokenB] : [tokenB, tokenA];\n\n // keccak256(abi.encode(token0, token1, fee)) — the CREATE2 salt.\n const salt = keccak256(\n concatHex([\n pad(getAddress(token0), { size: 32 }),\n pad(getAddress(token1), { size: 32 }),\n pad(toHex(fee), { size: 32 }),\n ]),\n );\n\n return getContractAddress({\n opcode: \"CREATE2\",\n from: factory,\n bytecodeHash: initCodeHash,\n salt,\n });\n}\n","import type { Address, Hex, PublicClient } from \"viem\";\n\n/**\n * EIP-7702 delegation designator prefix: 0xef0100 + 20-byte implementation address.\n *\n * When an EOA has been delegated via EIP-7702, `eth_getCode` returns bytecode\n * that starts with this magic prefix followed by the 20-byte address of the\n * implementation contract (BatchExecutor).\n *\n * References: EIP-7702 §4.2 — \"delegation designator\"\n */\nconst EIP7702_MAGIC = \"0xef0100\" as const;\n\n/**\n * Parse the implementation address out of an EIP-7702 delegation designator.\n *\n * Returns `null` when:\n * - `code` is undefined / empty / `\"0x\"` (plain EOA, no code)\n * - `code` does not contain the EIP-7702 magic prefix (regular contract)\n *\n * @param code - bytecode returned by `eth_getCode` / `client.getCode()`\n * @returns the 20-byte implementation address (checksummed), or `null`\n *\n * @example\n * const code = await client.getCode({ address });\n * const impl = parseEip7702DelegatedAddress(code);\n * // null → not delegated\n * // '0x7702cb554e6bFb442cb743A7dF23154544a7176C' → delegated to BatchExecutor\n */\nexport function parseEip7702DelegatedAddress(\n code: Hex | string | null | undefined,\n): Address | null {\n if (!code || code === \"0x\" || code === \"0x0\") return null;\n const normalized = code.toLowerCase();\n const magic = EIP7702_MAGIC.toLowerCase();\n // Strict prefix + exact-length check per EIP-7702 §4.2. Bytecode of\n // a delegated EOA is exactly 23 bytes (`0xef0100` || 20-byte impl)\n // → 40 hex chars AFTER the magic prefix. Substring search would\n // false-positive on regular contract bytecode containing `ef0100`\n // mid-stream.\n if (!normalized.startsWith(magic)) return null;\n if (normalized.length !== magic.length + 40) return null;\n return `0x${normalized.slice(magic.length)}` as Address;\n}\n\n/**\n * Read the EIP-7702 delegation status of an EOA from the chain.\n *\n * @param client - viem PublicClient (any provider — only `eth_getCode` is called)\n * @param address - EOA address to inspect\n * @returns the implementation address the EOA is delegated to, or `null`\n *\n * @example\n * const impl = await checkDelegation(publicClient, userAddress);\n * if (impl === null) {\n * // show \"Setup Wallet\" button\n * } else {\n * // EOA already delegated; safe to send UserOps\n * }\n */\nexport async function checkDelegation(\n client: PublicClient,\n address: Address,\n): Promise<Address | null> {\n const code = await client.getCode({ address });\n return parseEip7702DelegatedAddress(code);\n}\n\n/**\n * Return `true` when the EOA at `address` is currently delegated to `target`.\n *\n * Useful for asserting that the delegation points specifically at the expected\n * BatchExecutor rather than some other implementation (e.g. old deployment).\n *\n * @example\n * import { BATCH_EXECUTOR_ADDRESS_BASE_MAINNET } from '@pafi-dev/core';\n * const ok = await isDelegatedTo(client, userAddress, BATCH_EXECUTOR_ADDRESS_BASE_MAINNET);\n */\nexport async function isDelegatedTo(\n client: PublicClient,\n address: Address,\n target: Address,\n): Promise<boolean> {\n const impl = await checkDelegation(client, address);\n if (!impl) return false;\n return impl.toLowerCase() === target.toLowerCase();\n}\n","import type { Address, PublicClient } from \"viem\";\nimport type { PartialUserOperation } from \"../userop/types\";\nimport { buildPartialUserOperation } from \"../userop/buildUserOperation\";\nimport { ENTRY_POINT_V08 } from \"../constants\";\n\n/**\n * Parameters for building a delegation-only UserOperation.\n *\n * This UserOp carries no calldata — its sole purpose is to anchor the\n * EIP-7702 authorization (signed externally via `signAuthorization`) into\n * a sponsored transaction so the user doesn't need native ETH to delegate.\n */\nexport interface BuildDelegationUserOpParams {\n /** User EOA to delegate. */\n userAddress: Address;\n /** ERC-4337 account nonce — fetched from EntryPoint. Pass 0n on first ever op. */\n aaNonce: bigint;\n gasLimits?: {\n callGasLimit?: bigint;\n verificationGasLimit?: bigint;\n preVerificationGas?: bigint;\n };\n}\n\n/**\n * Build the minimal `PartialUserOperation` used to sponsor the one-time\n * EIP-7702 delegation.\n *\n * The caller must:\n * 1. Sign the EIP-7702 authorization via `signAuthorization()` (Privy hook).\n * 2. Pass the authorization as `authorization` alongside `calls` (or alone)\n * to `smartClient.sendTransaction()`.\n *\n * The permissionless SDK + Pimlico bundler handle the rest: they detect the\n * `authorization` field and submit an EIP-7702 type-4 transaction that sets\n * the EOA's bytecode to `0xef0100<batchExecutorAddress>`.\n *\n * This builder is a convenience wrapper — in practice you can also pass\n * `{ to: userAddress, value: 0n, data: '0x', authorization }` directly to\n * `smartClient.sendTransaction()` without calling this function at all.\n * Use it when you need a `PartialUserOperation` struct for inspection or\n * custom paymaster flows.\n *\n * @example\n * // Typical flow — no need to call this builder directly:\n * const nonce = await publicClient.getTransactionCount({ address, blockTag: 'pending' });\n * const authorization = await signAuthorization({ contractAddress: BATCH_EXECUTOR, chainId: 8453, nonce });\n * const txHash = await smartClient.sendTransaction({\n * to: address,\n * value: 0n,\n * data: '0x',\n * authorization,\n * paymasterContext: { sponsorshipPolicyId },\n * });\n */\nexport function buildDelegationUserOp(\n params: BuildDelegationUserOpParams,\n): PartialUserOperation {\n return buildPartialUserOperation({\n sender: params.userAddress,\n nonce: params.aaNonce,\n operations: [\n {\n // Self-call with no data — triggers EIP-7702 delegation without\n // executing any inner logic. The BatchExecutor.execute([]) call with\n // an empty array would revert, so we target the EOA itself (which\n // forwards to BatchExecutor that then no-ops on empty input).\n target: params.userAddress,\n value: 0n,\n data: \"0x\",\n },\n ],\n gasLimits: {\n callGasLimit: params.gasLimits?.callGasLimit ?? 50_000n,\n verificationGasLimit: params.gasLimits?.verificationGasLimit ?? 150_000n,\n preVerificationGas: params.gasLimits?.preVerificationGas ?? 50_000n,\n },\n });\n}\n\n/**\n * Fetch the AA nonce for a user EOA from the EntryPoint v0.7.\n *\n * Convenience helper so callers don't need to import the EntryPoint ABI\n * themselves when building the delegation UserOp.\n *\n * @param client - viem PublicClient\n * @param userAddress - EOA to query\n * @returns bigint nonce (0n on first-ever UserOp)\n */\nexport async function getAaNonce(\n client: PublicClient,\n userAddress: Address,\n): Promise<bigint> {\n const NONCE_ABI = [\n {\n inputs: [\n { name: \"sender\", type: \"address\" },\n { name: \"key\", type: \"uint192\" },\n ],\n name: \"getNonce\",\n outputs: [{ name: \"nonce\", type: \"uint256\" }],\n stateMutability: \"view\",\n type: \"function\",\n },\n ] as const;\n\n return client.readContract({\n address: ENTRY_POINT_V08,\n abi: NONCE_ABI,\n functionName: \"getNonce\",\n args: [userAddress, 0n],\n });\n}\n","import { concat, keccak256, toRlp, type Address, type Hex } from \"viem\";\n\n/**\n * EIP-7702 authorization tuple hash.\n *\n * keccak256(0x05 || rlp([chain_id, address, nonce]))\n *\n * The user signs this hash with a **raw secp256k1 sign** (no EIP-191 prefix).\n * On mobile, pass the result to the wallet's raw signing primitive.\n *\n * The resulting 65-byte signature is sent to `POST /delegate/submit` as `authSig`.\n *\n * Reference: EIP-7702 §3 — Authorization tuple\n */\nexport function computeAuthorizationHash(\n chainId: number,\n address: Address,\n nonce: bigint,\n): Hex {\n const rlpEncoded = toRlp([\n toMinimalHex(BigInt(chainId)),\n address,\n toMinimalHex(nonce),\n ]);\n return keccak256(concat([\"0x05\", rlpEncoded]));\n}\n\n/**\n * Check whether an EOA code string carries an EIP-7702 delegation to `target`.\n *\n * Sync alternative to `isDelegatedTo()` for callers that already have the\n * code from a prior `eth_getCode` call and don't want another RPC round-trip.\n *\n * Delegation designator format: 0xef0100 (3 bytes) || address (20 bytes)\n */\nexport function isDelegatedToTarget(\n code: string | null | undefined,\n target: Address,\n): boolean {\n if (!code || code === \"0x\") return false;\n const expected = `0xef0100${target.slice(2).toLowerCase()}`;\n return code.toLowerCase() === expected;\n}\n\n/** Minimal-length big-endian hex for RLP integer encoding (no leading zeros). */\nfunction toMinimalHex(n: bigint): Hex {\n if (n === 0n) return \"0x\";\n const hex = n.toString(16);\n return `0x${hex.length % 2 === 0 ? hex : \"0\" + hex}` as Hex;\n}\n","import type { Address, Hex } from \"viem\";\n\n/**\n * EIP-7702 authorization tuple in the JSON-RPC wire format that bundlers\n * (Pimlico, Stackup, etc.) accept on `eth_sendUserOperation`. All\n * numeric fields are 0x-prefixed hex strings.\n *\n * Constructed from the user's secp256k1 signature over\n * `keccak256(0x05 || rlp([chainId, address, nonce]))` — see\n * `computeAuthorizationHash`.\n */\nexport interface Eip7702AuthorizationJsonRpc {\n chainId: Hex;\n address: Address;\n nonce: Hex;\n r: Hex;\n s: Hex;\n yParity: Hex;\n}\n\n/**\n * Split a serialized 65-byte ECDSA signature (`r || s || v`) into the\n * `{ r, s, yParity }` triple required by EIP-7702 authorization tuples\n * and EIP-1559+ transactions.\n *\n * Accepts the legacy `v ∈ {27, 28}` form and converts to `yParity ∈ {0, 1}`\n * automatically. Already-normalized `v ∈ {0, 1}` passes through unchanged.\n *\n * @throws when the signature is not 65 bytes (130 hex chars).\n */\nexport function splitAuthorizationSig(authSig: Hex | string): {\n r: Hex;\n s: Hex;\n yParity: 0 | 1;\n} {\n const raw = authSig.startsWith(\"0x\")\n ? authSig.slice(2)\n : authSig;\n if (raw.length !== 130) {\n throw new Error(\n `splitAuthorizationSig: expected 65-byte signature (130 hex chars), got ${raw.length}`,\n );\n }\n const r = `0x${raw.slice(0, 64)}` as Hex;\n const s = `0x${raw.slice(64, 128)}` as Hex;\n const v = parseInt(raw.slice(128, 130), 16);\n const yParity: 0 | 1 = v === 27 ? 0 : v === 28 ? 1 : (v as 0 | 1);\n if (yParity !== 0 && yParity !== 1) {\n throw new Error(\n `splitAuthorizationSig: invalid recovery byte ${v} (expected 0/1/27/28)`,\n );\n }\n return { r, s, yParity };\n}\n\n/**\n * Build the JSON-RPC EIP-7702 authorization tuple from raw fields and a\n * 65-byte secp256k1 signature.\n *\n * The `authSig` is the user's signature over `computeAuthorizationHash(\n * chainId, address, nonce )` — typically obtained via\n * `wallet.signAuthorization(...)` on the client.\n */\nexport function buildEip7702Authorization(params: {\n chainId: number;\n address: Address;\n nonce: bigint;\n authSig: Hex | string;\n}): Eip7702AuthorizationJsonRpc {\n const { r, s, yParity } = splitAuthorizationSig(params.authSig);\n return {\n chainId: `0x${params.chainId.toString(16)}` as Hex,\n address: params.address,\n nonce: `0x${params.nonce.toString(16)}` as Hex,\n r,\n s,\n yParity: `0x${yParity}` as Hex,\n };\n}\n","import type { Address } from \"viem\";\n\n/**\n * Per-chain deployed contract addresses.\n *\n * Base mainnet (8453): live deployment. Fee on mint happens via\n * `MintFeeWrapper` (single global instance, multi-PT).\n *\n * Base Sepolia (84532): entire row is placeholder — not in active use.\n *\n * ## What lives where\n *\n * batchExecutor — EIP-7702 delegation target (Pimlico Simple7702Account)\n * usdt — Stablecoin used by Uniswap pools (6 decimals)\n * usdc — Stablecoin used as the perp-deposit fee token and\n * Orderly settlement asset (6 decimals). Optional —\n * may be undefined on chains without a canonical USDC.\n * issuerRegistry — registry of all issuers on this chain\n * mintingOracle — per-token mint cap enforcer\n * mintFeeWrapper — mint-time fee splitter (single global instance)\n * chainlinkEthUsd — ETH/USD price feed used by FeeManager\n * orderlyRelay — Orderly perp-deposit Relay\n * pafiFeeRecipient — PAFI-controlled fee recipient (sponsored gas reimbursement)\n * universalRouter — PAFI v3 fork UniversalRouter\n * permit2 — PAFI fork Permit2 (paired with the PAFI UR above)\n *\n * PointToken addresses are per-issuer and resolved from the issuer's\n * config (env / DB / PointTokenFactory readout). PointTokenFactory and\n * the PointToken implementation live in separate exports below.\n *\n * Fee logic lives in `MintFeeWrapper` on the mint path; swaps are\n * hook-free (`hooks = address(0)`).\n */\nexport interface ContractAddresses {\n batchExecutor: Address;\n usdt: Address;\n /**\n * Canonical USDC (6 decimals) on this chain — the fee token used by\n * the perp-deposit scenario (Orderly settlement asset) and accepted\n * by sponsor-relayer's stable fee path. Optional because not every\n * chain has a recognised canonical USDC.\n */\n usdc?: Address;\n issuerRegistry: Address;\n mintingOracle: Address;\n /**\n * Single-instance MintFeeWrapper that skims a fee on every sig-gated\n * mint and distributes across the registered recipient list for the\n * target PointToken. Issuers route mints through this by passing\n * `mintFeeWrapperAddress` to `prepareMint`.\n */\n mintFeeWrapper: Address;\n /** Chainlink ETH/USD price feed — used by FeeManager to convert gas cost to USDT. */\n chainlinkEthUsd: Address;\n /**\n * Orderly perp-deposit Relay — holds an ETH reserve to cover the\n * LayerZero msg.value, charges a USDC token-fee reimbursement.\n * Lets perp deposits ride the ERC-4337 sponsored gas path without\n * the user holding ETH for msg.value.\n */\n orderlyRelay: Address;\n /**\n * PAFI fee recipient — receives PT gas-reimbursement transfers from\n * the user on the sponsored path of every scenario (mint, burn,\n * swap, perp deposit). This is PAFI-controlled, NOT issuer-controlled,\n * because PAFI pays the ERC-4337 gas via the paymaster — issuers\n * shouldn't be able to redirect this fee to themselves.\n */\n pafiFeeRecipient: Address;\n /** Uniswap V4 UniversalRouter — swap entry point for the swap handler. */\n universalRouter: Address;\n /**\n * PAFI fork of Uniswap's Permit2. Acts as the unified token-approval\n * authority for UniversalRouter swaps and any other PAFI-paired flow\n * that needs gasless allowances. Differs from Uniswap canonical\n * Permit2 (`0x000000000022D473...`) — must use the PAFI fork because\n * the fork UR was deployed against this Permit2 instance. Keep in\n * sync with `PERMIT2_ADDRESS` top-level export.\n */\n permit2: Address;\n}\n\nconst PLACEHOLDER_DEAD = (suffix: string): Address =>\n `0x000000000000000000000000000000000000${suffix.toLowerCase().padStart(4, \"0\")}` as Address;\n\nexport const CONTRACT_ADDRESSES: Record<number, ContractAddresses> = {\n // Base mainnet — live deployment\n 8453: {\n batchExecutor: \"0xe6Cae83BdE06E4c305530e199D7217f42808555B\",\n usdt: \"0x3F7e71B150e97316Bb9f363A32c19CcD36ac2382\",\n usdc: \"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\",\n issuerRegistry: \"0xAB1d1e117c41636f30bb10194Fe6B774B6Da9E01\",\n mintingOracle: \"0x2f4cf8C5F8b41efC970c5b46a5d905CeA1f871a0\",\n mintFeeWrapper: \"0xD324EE2e3220B23d1b1BfbB85f5bC1EF2E917B93\",\n chainlinkEthUsd: \"0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70\",\n orderlyRelay: \"0xDA082DAce1522c185aeB5A713FcA6fa6B6E99e7f\",\n pafiFeeRecipient: \"0xa3F71eadEd101513a0151007590020dCFD7C495e\",\n // PAFI fork UniversalRouter — DIFFERS from Uniswap canonical Base UR\n // (`0x6fF5693b...`). The PAFI fork has its own factory + poolInitCodeHash,\n // so only this UR can route through PAFI v3 fork pools. Keep in sync with\n // `UNIVERSAL_ROUTER_ADDRESSES` in `chains.ts`.\n universalRouter: \"0x008887C992A5bDC24097E717Bfb71CE89483c5A2\",\n // PAFI fork Permit2 — DIFFERS from Uniswap canonical Permit2\n // (`0x000000000022D473...`). PAFI's UR was deployed against this\n // Permit2, so swaps signed against the canonical Permit2 fail\n // signature verification. Keep in sync with `PERMIT2_ADDRESS`\n // top-level export.\n permit2: \"0xEB450d21ae68D3303Cf5775A54Cc84EE7c3fC8eC\",\n },\n // Base Sepolia — not in active use; placeholders kept so the map\n // compiles for tooling that enumerates chains.\n 84532: {\n batchExecutor: PLACEHOLDER_DEAD(\"de01\"),\n usdt: PLACEHOLDER_DEAD(\"dead\"),\n issuerRegistry: PLACEHOLDER_DEAD(\"dead\"),\n mintingOracle: PLACEHOLDER_DEAD(\"dead\"),\n mintFeeWrapper: PLACEHOLDER_DEAD(\"dead\"),\n chainlinkEthUsd: PLACEHOLDER_DEAD(\"de02\"),\n orderlyRelay: PLACEHOLDER_DEAD(\"de03\"),\n pafiFeeRecipient: PLACEHOLDER_DEAD(\"de04\"),\n universalRouter: PLACEHOLDER_DEAD(\"de05\"),\n permit2: PLACEHOLDER_DEAD(\"de06\"),\n },\n};\n\n/**\n * PointTokenFactory address — separate from `ContractAddresses` because\n * it's only used at provisioning time (issuer onboard + token creation),\n * not in the hot mint/burn path.\n */\nexport const POINT_TOKEN_FACTORY_ADDRESSES: Record<number, Address> = {\n 8453: \"0xA08274458b43E7D6F4ff61ddFe8A9852c6531085\",\n 84532: PLACEHOLDER_DEAD(\"dead\"),\n};\n\n/**\n * PointToken implementation address — the EIP-1167 clone target used\n * by PointTokenFactory. Exposed for observability / proxy verification;\n * consumers should never call this directly.\n */\nexport const POINT_TOKEN_IMPL_ADDRESSES: Record<number, Address> = {\n 8453: \"0xc41c3F8A0380c7760Ee1209d6d19C4b81dE994e4\",\n 84532: PLACEHOLDER_DEAD(\"dead\"),\n};\n\n/**\n * Lookup helper — throws if the chain isn't in the map so callers fail\n * loudly on misconfiguration instead of silently using `undefined`.\n */\nexport function getContractAddresses(chainId: number): ContractAddresses {\n const addrs = CONTRACT_ADDRESSES[chainId];\n if (!addrs) {\n throw new Error(\n `getContractAddresses: no addresses for chainId ${chainId}. ` +\n `Supported: ${Object.keys(CONTRACT_ADDRESSES).join(\", \")}`,\n );\n }\n return addrs;\n}\n","import type {\n Address,\n Hex,\n PublicClient,\n WalletClient,\n TransactionReceipt,\n} from \"viem\";\nimport { getContractAddresses } from \"../contracts/real/addresses\";\nimport { parseEip7702DelegatedAddress } from \"./checkDelegation\";\n\n/**\n * Privy-style EIP-7702 authorization signer. Matches the shape returned\n * by `useSign7702Authorization()` from `@privy-io/react-auth` /\n * `@privy-io/expo`. Pass it via `params.signAuthorization` and the\n * helper takes care of the rest.\n *\n * The signer MUST be the user's Privy embedded wallet — external\n * wallets (MetaMask, WalletConnect, …) cannot produce raw secp256k1\n * EIP-7702 authorizations.\n */\nexport type SignAuthorizationFn = (args: {\n contractAddress: Address;\n chainId: number;\n nonce: number;\n}) => Promise<{\n contractAddress?: Address;\n address?: Address;\n chainId: number;\n nonce: number;\n r: Hex | string;\n s: Hex | string;\n // Privy returns `v` as bigint, viem types it as bigint, some SDKs as\n // string/number. Accept all and ignore — we use yParity directly.\n v?: bigint | string | number;\n // Privy returns 0 | 1 (number); some SDKs return '0x0' / '0x1' (hex\n // string). Accept both shapes — `delegateDirect` normalizes\n // internally.\n yParity: number | string;\n}>;\n\n/**\n * Authorization tuple in the shape `walletClient.sendTransaction`\n * expects on its `authorizationList` field. Mirrors viem's\n * `SignedAuthorization` so callers can also build it manually.\n */\nexport interface SignedAuthorization {\n contractAddress: Address;\n chainId: number;\n nonce: number;\n r: Hex;\n s: Hex;\n yParity: 0 | 1;\n}\n\nexport interface DelegateDirectParams {\n /** User EOA — must equal `walletClient.account.address`. */\n userAddress: Address;\n chainId: number;\n /** viem PublicClient — used for `getCode` + `getTransactionCount`. */\n publicClient: PublicClient;\n /** viem WalletClient (or any sender that exposes `sendTransaction`). */\n walletClient: WalletClient;\n /**\n * Privy hook that produces the EIP-7702 authorization signature.\n * Pass `useSign7702Authorization().signAuthorization` directly.\n */\n signAuthorization: SignAuthorizationFn;\n /**\n * Override the impl the EOA delegates to. Defaults to\n * `getContractAddresses(chainId).batchExecutor` (Pimlico\n * Simple7702Account on Base mainnet — the canonical PAFI delegate\n * target).\n */\n contractAddress?: Address;\n /**\n * When the user already has a 7702 delegation pointing at the\n * expected impl, skip the tx and return early. Default `true`.\n * Set `false` to force re-delegate even if status check passes.\n */\n skipIfAlreadyDelegated?: boolean;\n /**\n * Wait for the transaction receipt before returning. Default\n * `true` — caller usually wants to know \"delegation completed\"\n * before proceeding to claim/redeem flows.\n */\n waitForReceipt?: boolean;\n /** Optional onWarning hook for non-fatal warnings (logger surface). */\n onWarning?: (msg: string) => void;\n}\n\nexport interface DelegateDirectResult {\n /** `'sent'` when a tx was broadcast; `'already-delegated'` when skipped. */\n status: \"sent\" | \"already-delegated\";\n /** Transaction hash. `undefined` on `already-delegated`. */\n txHash?: Hex;\n /** Receipt — present when `waitForReceipt` AND status is `'sent'`. */\n receipt?: TransactionReceipt;\n /** EIP-7702 authorization tuple actually sent. */\n authorization: SignedAuthorization;\n /** Impl address user delegated to. */\n delegatedTo: Address;\n}\n\n/**\n * One-shot helper for the FE-direct EIP-7702 delegation path —\n * **no AA, no paymaster, no PAFI sponsor-relayer**. The user EOA\n * pays gas in ETH and broadcasts a single type-4 transaction with the\n * authorization attached.\n *\n * Use this when:\n * - The FE already has a Privy embedded wallet ready and the user\n * has a small ETH balance for gas (~$0.01–0.10 on Base).\n * - You don't want to depend on `permissionless` / Pimlico bundlers\n * / sponsor-relayer for the one-time delegation step.\n * - You're testing or running a self-hosted dev environment without\n * the full PAFI infra.\n *\n * Flow:\n * 1. Read on-chain code; short-circuit if already delegated to the\n * expected impl (`skipIfAlreadyDelegated`).\n * 2. Read EOA tx nonce (pending).\n * 3. Call `signAuthorization` (Privy hook) → r/s/yParity.\n * 4. Send EIP-7702 type-4 tx with `authorizationList: [auth]` and\n * `to: userAddress, data: '0x'` (no-op self-call; the work is\n * bundling the authorization).\n * 5. Wait for receipt (optional).\n *\n * Caller's `signAuthorization` MUST be wired to the user's Privy\n * embedded wallet — external wallets (MetaMask, …) do NOT support\n * raw secp256k1 EIP-7702 sign.\n *\n * @example\n * ```ts\n * import { useSign7702Authorization, useWallets } from \"@privy-io/react-auth\";\n * import { delegateDirect } from \"@pafi-dev/core\";\n * import { createWalletClient, custom } from \"viem\";\n * import { base } from \"viem/chains\";\n *\n * function DelegateButton() {\n * const { wallets } = useWallets();\n * const { signAuthorization } = useSign7702Authorization();\n * const wallet = wallets.find(w => w.walletClientType === \"privy\"); // embedded\n *\n * async function handleClick() {\n * const provider = await wallet.getEthereumProvider();\n * const walletClient = createWalletClient({\n * account: wallet.address,\n * chain: base,\n * transport: custom(provider),\n * });\n *\n * const result = await delegateDirect({\n * userAddress: wallet.address as `0x${string}`,\n * chainId: 8453,\n * publicClient,\n * walletClient,\n * signAuthorization,\n * });\n *\n * if (result.status === \"already-delegated\") {\n * console.log(\"Already delegated to\", result.delegatedTo);\n * } else {\n * console.log(\"Delegated! tx:\", result.txHash);\n * }\n * }\n * }\n * ```\n */\nexport async function delegateDirect(\n params: DelegateDirectParams,\n): Promise<DelegateDirectResult> {\n const target =\n params.contractAddress ??\n (getContractAddresses(params.chainId).batchExecutor as Address);\n\n // 1. Short-circuit if already delegated to the expected impl.\n if (params.skipIfAlreadyDelegated !== false) {\n const code = await params.publicClient.getCode({\n address: params.userAddress,\n });\n const current = parseEip7702DelegatedAddress(code);\n if (current && current.toLowerCase() === target.toLowerCase()) {\n // Build a no-op authorization placeholder for the response —\n // we didn't actually sign one because we skipped.\n return {\n status: \"already-delegated\",\n delegatedTo: current,\n authorization: {\n contractAddress: target,\n chainId: params.chainId,\n nonce: 0,\n r: \"0x\" as Hex,\n s: \"0x\" as Hex,\n yParity: 0,\n },\n };\n }\n }\n\n // 2. Read EOA tx nonce (pending — covers any in-flight sends).\n const nonce = await params.publicClient.getTransactionCount({\n address: params.userAddress,\n blockTag: \"pending\",\n });\n\n // 3. Sign authorization via Privy hook.\n const raw = await params.signAuthorization({\n contractAddress: target,\n chainId: params.chainId,\n nonce,\n });\n\n // Normalize to viem's SignedAuthorization shape. Privy returns\n // `yParity` as either `0/1` (number) or `'0x0'/'0x1'` (hex string)\n // depending on SDK version — handle both.\n const yParityRaw = raw.yParity;\n const yParityNum =\n typeof yParityRaw === \"number\"\n ? yParityRaw\n : String(yParityRaw) === \"1\" || String(yParityRaw) === \"0x1\"\n ? 1\n : 0;\n const yParity: 0 | 1 = yParityNum === 1 ? 1 : 0;\n\n const authorization: SignedAuthorization = {\n contractAddress: target,\n chainId: params.chainId,\n nonce,\n r: normalizeHex32(raw.r),\n s: normalizeHex32(raw.s),\n yParity,\n };\n\n // 4. Send EIP-7702 type-4 tx. viem auto-encodes when\n // `authorizationList` is supplied. Self-call (data: '0x') is a\n // no-op — the work is the authorization itself.\n const account = params.walletClient.account;\n if (!account) {\n throw new Error(\n \"delegateDirect: walletClient has no account attached — cannot send tx\",\n );\n }\n\n // Cast to bypass viem's strict generic chain inference; runtime\n // shape matches `EIP7702Transaction` regardless of declared chain.\n const txHash = (await (\n params.walletClient as WalletClient & {\n sendTransaction: (args: unknown) => Promise<Hex>;\n }\n ).sendTransaction({\n account,\n chain: params.walletClient.chain,\n to: params.userAddress,\n value: 0n,\n data: \"0x\" as Hex,\n authorizationList: [authorization],\n })) as Hex;\n\n // 5. Wait for receipt (optional).\n const waitForReceipt = params.waitForReceipt !== false;\n let receipt: TransactionReceipt | undefined;\n if (waitForReceipt) {\n try {\n receipt = await params.publicClient.waitForTransactionReceipt({\n hash: txHash,\n });\n } catch (err) {\n params.onWarning?.(\n `delegateDirect: tx ${txHash} sent but receipt fetch failed: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n }\n\n return {\n status: \"sent\",\n txHash,\n receipt,\n authorization,\n delegatedTo: target,\n };\n}\n\n/**\n * Normalize a hex r/s component to exactly `0x` + 64 hex chars\n * (32 bytes). Privy occasionally returns less-than-32-byte values\n * when the leading byte is zero; viem's `authorizationList` expects\n * full-width hex.\n */\nfunction normalizeHex32(value: Hex | string | undefined): Hex {\n if (!value) return \"0x\" as Hex;\n const stripped = value.replace(/^0x/i, \"\");\n return (\"0x\" + stripped.padStart(64, \"0\")) as Hex;\n}\n","import { http } from \"viem\";\nimport type { HttpTransport } from \"viem\";\n\n/**\n * Parameters for `createPafiProxyTransport`.\n */\nexport interface PafiProxyTransportParams {\n /**\n * Full URL of the PAFI sponsor-relayer Pimlico proxy endpoint.\n * @example \"https://sponsor-relayer.pacificfinance.org/pimlico\"\n * @example \"http://localhost:4000/pimlico\"\n */\n proxyUrl: string;\n\n /**\n * Called on every request to get the current Privy identity token.\n * Use a getter (or a ref's `.current`) so the transport always sends\n * the latest token without needing to be rebuilt when it rotates.\n *\n * @example () => identityTokenRef.current\n * @example () => privyHook.identityToken\n */\n getIdentityToken: () => string | null | undefined;\n\n /**\n * Issuer ID sent as `X-Issuer-Id` header.\n * sponsor-relayer uses this to look up per-issuer rate limits and policy.\n * Obtain from PAFI team during onboarding.\n */\n issuerId: string;\n}\n\n/**\n * Create a viem `HttpTransport` that proxies all Pimlico JSON-RPC calls\n * through the PAFI sponsor-relayer (`POST /pimlico`).\n *\n * The transport:\n * - Sets `Authorization: Bearer <identityToken>` on every request\n * - Sets `X-Issuer-Id: <issuerId>` on every request\n * - Reads the identity token lazily via `getIdentityToken()` so it is\n * always fresh without rebuilding the SmartAccountClient on each rotation\n *\n * Pass the returned transport to both `createPimlicoClient` and\n * `createSmartAccountClient` so ALL Pimlico RPC calls (paymaster + bundler)\n * go through the proxy with auth headers attached.\n *\n * @example\n * ```ts\n * import { createPafiProxyTransport, BATCH_EXECUTOR_ADDRESS_BASE_MAINNET } from '@pafi-dev/core';\n * import { createSmartAccountClient } from 'permissionless';\n * import { createPimlicoClient } from 'permissionless/clients/pimlico';\n * import { useIdentityToken } from '@privy-io/react-auth';\n *\n * const { identityToken } = useIdentityToken();\n * const identityTokenRef = useRef<string | null>(null);\n * useEffect(() => { identityTokenRef.current = identityToken; }, [identityToken]);\n *\n * const proxyTransport = createPafiProxyTransport({\n * proxyUrl: process.env.NEXT_PUBLIC_PIMLICO_PROXY_URL!,\n * getIdentityToken: () => identityTokenRef.current,\n * issuerId: process.env.NEXT_PUBLIC_ISSUER_ID!,\n * });\n *\n * const pimlicoClient = createPimlicoClient({ chain: base, transport: proxyTransport });\n * const smartClient = createSmartAccountClient({\n * client: publicClient,\n * chain: base,\n * account,\n * paymaster: pimlicoClient,\n * bundlerTransport: proxyTransport,\n * });\n * ```\n */\nexport function createPafiProxyTransport(\n params: PafiProxyTransportParams,\n): HttpTransport {\n const { proxyUrl, getIdentityToken, issuerId } = params;\n\n return http(proxyUrl, {\n fetchOptions: {},\n // fetchFn intercepts every fetch call the viem http transport makes,\n // injecting the auth headers before the request leaves the browser.\n fetchFn: (input: RequestInfo | URL, init?: RequestInit) => {\n const headers = new Headers(init?.headers);\n const token = getIdentityToken();\n if (token) {\n headers.set(\"authorization\", `Bearer ${token}`);\n }\n headers.set(\"x-issuer-id\", issuerId);\n return fetch(input, { ...init, headers });\n },\n });\n}\n","import type { Hex } from \"viem\";\n\n/**\n * Minimal interface for any smart-account client capable of submitting\n * transactions. Deliberately duck-typed so the SDK does not need a hard\n * dependency on permissionless — any client with this shape works.\n */\nexport interface SmartAccountSender {\n sendTransaction(params: Record<string, unknown>): Promise<Hex>;\n}\n\n/**\n * HTTP statuses returned by the PAFI sponsor-relayer / Pimlico proxy\n * that mean \"paymaster declined sponsorship, retrying without it is a\n * sensible UX.\" 401/403/429/503 only — NOT 4xx/5xx generally (a\n * generic 500 could be anything).\n */\nconst PAYMASTER_HTTP_STATUSES = new Set([401, 403, 429, 503]);\n\n/**\n * Word-boundary patterns matching paymaster-layer failures. Use\n * regex (not substring) so arbitrary error text containing the\n * digits \"503\" / \"403\" / etc. (transit IDs, addresses, log lines)\n * doesn't false-positive.\n *\n * - `\\bAA3[1-4]\\b` — ERC-4337 paymaster validation codes (AA31..AA34)\n * - `\\bpaymaster\\b` — Pimlico / sponsor-relayer plain text\n * - `\\bsponsorship\\b` — Pimlico declines\n * - `\\bpm_\\w+` — JSON-RPC method (pm_getPaymasterData, pm_sponsorUserOperation)\n * - `\\brate ?limit\\b` — generic rate limiter\n * - `\\bunauthorized\\b` — auth failure (also caught by HTTP status check below)\n */\nconst PAYMASTER_PATTERNS: RegExp[] = [\n /\\bAA3[1-4]\\b/i,\n /\\bpaymaster\\b/i,\n /\\bsponsorship\\b/i,\n /\\bpm_\\w+/i,\n /\\brate ?limit\\b/i,\n /\\bunauthorized\\b/i,\n];\n\ninterface MaybeHttpError {\n status?: number;\n statusCode?: number;\n response?: { status?: number };\n cause?: { status?: number };\n message?: string;\n}\n\n/**\n * Returns true when `err` originates from the **paymaster layer** rather\n * than the contract or bundler layer. Covers:\n *\n * - ERC-4337 AA3x validation errors (AA31–AA34, word-boundary match)\n * - Pimlico sponsorship rejections (`pm_*` methods, \"sponsorship\" / \"paymaster\")\n * - PAFI proxy HTTP errors — checked via typed `status` / `statusCode`\n * field when present, NOT substring match\n * - Generic \"rate limit\" / \"unauthorized\" strings\n *\n * Use this to decide whether retrying without a paymaster makes sense.\n * Contract reverts (AA23, AA24, AA25) and bundler errors are NOT matched.\n */\nexport function isPaymasterError(err: unknown): boolean {\n if (err == null || typeof err !== \"object\") return false;\n const e = err as MaybeHttpError;\n\n // 1. Typed HTTP status check — strict integer match, no substring drift.\n const status =\n e.status ?? e.statusCode ?? e.response?.status ?? e.cause?.status;\n if (typeof status === \"number\" && PAYMASTER_HTTP_STATUSES.has(status)) {\n return true;\n }\n\n // 2. Word-boundary regex against the error message. Avoids false\n // positives from arbitrary strings containing the digits \"503\"\n // or substrings like \"403\" inside a hex address.\n const msg = e.message ?? String(err);\n return PAYMASTER_PATTERNS.some((re) => re.test(msg));\n}\n\nexport interface SendWithPaymasterFallbackParams {\n /** Smart-account client with paymaster configured — tried first. */\n primaryClient: SmartAccountSender;\n /**\n * Smart-account client without paymaster — used when `primaryClient`\n * throws a paymaster error. User pays gas from their ETH balance.\n * Pass `null` or `undefined` to disable fallback (error re-thrown as-is).\n */\n fallbackClient: SmartAccountSender | null | undefined;\n /** Transaction params forwarded verbatim to `sendTransaction`. */\n txParams: Record<string, unknown>;\n /**\n * Optional alternate `txParams` used **only on the fallback attempt**.\n * Lets callers strip operator-fee transfers from the calldata when the\n * paymaster refuses — there's no point charging a \"PAFI gas\n * reimbursement\" fee in PT/USDC if PAFI didn't actually sponsor the\n * gas. When omitted, the same `txParams` is reused for both attempts.\n */\n txParamsFallback?: Record<string, unknown>;\n /**\n * Called just before the fallback attempt so the caller can log or\n * update UI. Receives the error message from the failed primary attempt.\n */\n onFallback?: (errorMessage: string) => void;\n}\n\n/**\n * Submit a UserOp with paymaster sponsorship, falling back to user-paid gas\n * if the paymaster refuses.\n *\n * Flow:\n * 1. Call `primaryClient.sendTransaction(txParams)`.\n * 2. If it throws and `isPaymasterError` returns true, call `onFallback` then\n * retry with `fallbackClient` (no paymaster — user pays gas).\n * 3. All other errors (contract revert, bundler rejection, network) are\n * re-thrown immediately without a retry.\n *\n * @example\n * ```ts\n * import { sendWithPaymasterFallback } from '@pafi-dev/core';\n *\n * const txHash = await sendWithPaymasterFallback({\n * primaryClient: smartClientWithPaymaster,\n * fallbackClient: smartClientNoPaymaster,\n * txParams: { calls, paymasterContext: { sponsorshipPolicyId } },\n * onFallback: (msg) => addLog(`Paymaster refused (${msg}) — you will pay gas.`),\n * });\n * ```\n */\nexport async function sendWithPaymasterFallback(\n params: SendWithPaymasterFallbackParams,\n): Promise<Hex> {\n const { primaryClient, fallbackClient, txParams, txParamsFallback, onFallback } = params;\n try {\n return await primaryClient.sendTransaction(txParams);\n } catch (err) {\n if (isPaymasterError(err) && fallbackClient) {\n const msg = (err as any)?.message ?? String(err);\n onFallback?.(msg);\n return await fallbackClient.sendTransaction(txParamsFallback ?? txParams);\n }\n throw err;\n }\n}\n","import { parseAbi } from \"viem\";\nimport type { Address, PublicClient } from \"viem\";\nimport { PAFI_SUBGRAPH_URL } from \"../subgraph/pools\";\nimport { getContractAddresses } from \"../contracts/real/addresses\";\nimport { OracleStaleError } from \"../errors\";\n\n/**\n * Operator-fee quoter — pure on-chain + subgraph reads, no issuer\n * backend involvement.\n *\n * Computes the PT amount the user must transfer to PAFI's fee\n * recipient to reimburse the ERC-4337 gas cost of a sponsored UserOp:\n *\n * nativeWei = gasUnits × gasPrice\n * premium = nativeWei × premiumBps / 10_000\n * feeInUsdt = premium × ethPriceUsd / 10^(18+8-6) // Chainlink\n * feeInPt = feeInUsdt × ptPerUsdt_18dec / 10^(18-6) // V4 pool spot\n *\n * Used by the SDK Direct path so callers don't need to hit any\n * issuer-specific endpoint to figure out the fee. The sponsor-relayer\n * `FeeValidatorService` runs the same quote server-side with a 5%\n * tolerance window — minor drift between client and server is\n * accepted.\n *\n * @example\n * ```ts\n * import { quoteOperatorFeePt } from \"@pafi-dev/core\";\n *\n * const fee = await quoteOperatorFeePt({\n * provider: publicClient,\n * chainId: 8453,\n * pointTokenAddress: POINT_TOKEN,\n * });\n *\n * await trading.handleSwap({\n * ...,\n * gasFeePt: fee,\n * feeRecipient: getContractAddresses(8453).pafiFeeRecipient,\n * });\n * ```\n */\n\nconst CHAINLINK_ABI = parseAbi([\n \"function latestRoundData() external view returns (uint80, int256, uint256, uint256, uint80)\",\n]);\n\n// Base ETH/USD heartbeat — feed updates hourly or on 0.5% deviation.\nconst CHAINLINK_MAX_AGE_S = 3_600n;\n\nconst POOL_PRICE_QUERY = `\n query GetPoolPrice($id: ID!) {\n pafiToken(id: $id) {\n pool {\n token0 { id }\n token1 { id }\n token0Price\n token1Price\n }\n }\n }\n`;\n\ninterface GraphQLPriceResponse {\n data?: {\n pafiToken: {\n pool: {\n token0: { id: string };\n token1: { id: string };\n token0Price: string;\n token1Price: string;\n } | null;\n } | null;\n };\n errors?: { message: string }[];\n}\n\n/**\n * Per-scenario gas budgets (callGasLimit + verificationGas + preVerify)\n * used by `quoteOperatorFee*`. Defaults are calibrated against the\n * actual UserOps the issuer + trading SDKs build:\n *\n * mint — `RelayService.prepareMint` callGasLimit 300k → 500k margin\n * burn — `RelayService.prepareBurn` 300k → 500k margin\n * swap — `buildSwapUserOp` callGasLimit 700k (UR.execute heavy)\n * perp-deposit — `buildPerpDepositViaRelay` 800k (LayerZero msg)\n * delegate — minimal no-op self-call, 200k margin\n *\n * Pass an explicit `gasUnits` to override.\n */\nexport type FeeScenario =\n | \"mint\"\n | \"burn\"\n | \"swap\"\n | \"perp-deposit\"\n | \"delegate\"\n | \"erc20-transfer\";\n\nexport const SCENARIO_GAS_UNITS: Record<FeeScenario, bigint> = {\n mint: 500_000n,\n burn: 500_000n,\n swap: 700_000n,\n \"perp-deposit\": 800_000n,\n delegate: 200_000n,\n // 2-call batch: 1 ERC-20 fee transfer + 1 ERC-20 transfer + 7702\n // delegation overhead. ~70-90k empirical; 200k matches `delegate`\n // budget and gives headroom for premium variance.\n \"erc20-transfer\": 200_000n,\n};\n\nexport interface QuoteOperatorFeePtConfig {\n provider: PublicClient;\n chainId: number;\n pointTokenAddress: Address;\n /**\n * Scenario tag — picks default `gasUnits` from `SCENARIO_GAS_UNITS`.\n * Ignored when `gasUnits` is explicit. Default `\"mint\"` for back-\n * compat (matches the legacy 500_000 constant).\n */\n scenario?: FeeScenario;\n /**\n * ERC-4337 gas units the UserOp will consume on chain. Defaults to\n * `SCENARIO_GAS_UNITS[scenario]`, falling back to 500_000n. Pass a\n * tighter number when you have a Pimlico estimate to feed in.\n */\n gasUnits?: bigint;\n /**\n * Premium applied on top of raw gas cost in basis points. Default\n * 12_000 (= 1.2×, 20% buffer to cover oracle drift + paymaster\n * overhead). Same default the issuer SDK's `FeeManager` uses.\n */\n premiumBps?: number;\n /** Chainlink ETH/USD feed override. Defaults to chain's address. */\n chainlinkFeedAddress?: Address;\n /** Subgraph URL override. Defaults to `PAFI_SUBGRAPH_URL`. */\n subgraphUrl?: string;\n /** USDT token decimals. Default 6 (USDC/USDT on Base/Ethereum). */\n usdtDecimals?: number;\n /**\n * Opt-in fallback prices used when Chainlink / subgraph is\n * unavailable. Default `false` — throw `OracleStaleError`. Set\n * `true` AND configure `fallbackEthPriceUsd` / `fallbackPtPriceUsdt`\n * to absorb oracle outages instead of surfacing them.\n *\n * When fallback fires, callers SHOULD observe via `onFallback` so\n * production can flag the under-priced quote (e.g. show \"fee priced\n * against stale oracle\" banner in FE; alert on-call).\n */\n allowStaleFallback?: boolean;\n /** Fallback ETH price (USD) when Chainlink is unreachable. Only used when `allowStaleFallback: true`. Default 3000. */\n fallbackEthPriceUsd?: number;\n /** Fallback PT price (USDT per 1 PT) when subgraph is unreachable. Only used when `allowStaleFallback: true`. Default 0.1. */\n fallbackPtPriceUsdt?: number;\n /**\n * Observability hook fired when the quoter falls back to stale\n * prices. Callers SHOULD wire this to their alert pipeline (Sentry,\n * Datadog, PagerDuty) so under-priced quotes don't go unnoticed.\n * When omitted, falls back to `console.warn` (kept for back-compat\n * but invisible in most prod log shippers).\n */\n onFallback?: (info: FallbackInfo) => void;\n fetchImpl?: typeof fetch;\n}\n\n/**\n * Detail surfaced via `onFallback` when the quoter substitutes a\n * stale-fallback price. Callers can format this into a UI banner or\n * log line.\n */\nexport interface FallbackInfo {\n /** Which oracle failed. */\n source: \"chainlink\" | \"subgraph\";\n /** Underlying reason from the oracle call (e.g. RPC error message). */\n reason: string;\n /** The fallback value that was used (in source-native units). */\n fallbackValue: number;\n}\n\n/**\n * Subset of `QuoteOperatorFeePtConfig` that doesn't need the V4 pool\n * step — used by `quoteOperatorFeeUsdt` (FE cashout-preview / `/gas-fee`\n * replacement). No `pointTokenAddress`, no `subgraphUrl`, no PT-side\n * fallback.\n */\nexport interface QuoteOperatorFeeUsdtConfig {\n provider: PublicClient;\n chainId: number;\n /** See `QuoteOperatorFeePtConfig.scenario`. v0.7.4. */\n scenario?: FeeScenario;\n gasUnits?: bigint;\n premiumBps?: number;\n chainlinkFeedAddress?: Address;\n usdtDecimals?: number;\n /**\n * Opt-in fallback price when Chainlink is unavailable. Default\n * `false` — throw `OracleStaleError`.\n */\n allowStaleFallback?: boolean;\n fallbackEthPriceUsd?: number;\n /** See QuoteOperatorFeePtConfig.onFallback. */\n onFallback?: (info: FallbackInfo) => void;\n}\n\nconst DEFAULT_GAS_UNITS = 500_000n;\nconst DEFAULT_PREMIUM_BPS = 12_000;\nconst DEFAULT_USDT_DECIMALS = 6;\n\n/**\n * Operator fee quoted in USDT raw units (default 6 decimals). Returns\n * the same value the legacy `GET /gas-fee` endpoint produced —\n * `withPremium_wei × ethPrice / 10^(18 + 8 - usdtDecimals)`. Does NOT\n * convert to PT, so no V4 subgraph call needed; pure on-chain\n * Chainlink read + RPC `getGasPrice`.\n *\n * Use this on the FE for the cashout preview screen (or anywhere a\n * USDT-denominated estimate is wanted) instead of round-tripping the\n * issuer backend's `/gas-fee` endpoint.\n *\n * @example\n * ```ts\n * import { quoteOperatorFeeUsdt } from \"@pafi-dev/core\";\n * const gasFeeUsdt = await quoteOperatorFeeUsdt({\n * provider, chainId: 8453,\n * });\n * // gasFeeUsdt is a bigint in 6-decimal USDT raw units\n * ```\n */\nexport async function quoteOperatorFeeUsdt(\n config: QuoteOperatorFeeUsdtConfig,\n): Promise<bigint> {\n const {\n provider,\n chainId,\n scenario = \"mint\",\n gasUnits = SCENARIO_GAS_UNITS[scenario] ?? DEFAULT_GAS_UNITS,\n premiumBps = DEFAULT_PREMIUM_BPS,\n usdtDecimals = DEFAULT_USDT_DECIMALS,\n allowStaleFallback = false,\n fallbackEthPriceUsd = 3_000,\n } = config;\n\n const chainlinkFeedAddress =\n config.chainlinkFeedAddress ??\n getContractAddresses(chainId).chainlinkEthUsd;\n\n const gasPrice = await provider.getGasPrice();\n const nativeCost = gasPrice * gasUnits;\n const withPremium = (nativeCost * BigInt(premiumBps)) / 10_000n;\n\n const ethPrice8dec = await getEthPrice8dec(\n provider,\n chainlinkFeedAddress,\n allowStaleFallback ? fallbackEthPriceUsd : null,\n config.onFallback,\n );\n\n // feeUsdt_<usdtDec> = withPremium_wei × ethPrice_8dec / 10^(18 + 8 - usdtDec)\n const denomExp = 18 + 8 - usdtDecimals;\n return (withPremium * ethPrice8dec) / 10n ** BigInt(denomExp);\n}\n\nexport async function quoteOperatorFeePt(\n config: QuoteOperatorFeePtConfig,\n): Promise<bigint> {\n const {\n provider,\n chainId,\n pointTokenAddress,\n scenario = \"mint\",\n gasUnits = SCENARIO_GAS_UNITS[scenario] ?? DEFAULT_GAS_UNITS,\n premiumBps = DEFAULT_PREMIUM_BPS,\n subgraphUrl = PAFI_SUBGRAPH_URL,\n usdtDecimals = DEFAULT_USDT_DECIMALS,\n allowStaleFallback = false,\n fallbackEthPriceUsd = 3_000,\n fallbackPtPriceUsdt = 0.1,\n fetchImpl = globalThis.fetch,\n } = config;\n\n const chainlinkFeedAddress =\n config.chainlinkFeedAddress ??\n getContractAddresses(chainId).chainlinkEthUsd;\n\n const gasPrice = await provider.getGasPrice();\n const nativeCost = gasPrice * gasUnits;\n const withPremium = (nativeCost * BigInt(premiumBps)) / 10_000n;\n\n const [ethPrice8dec, ptPerUsdt18dec] = await Promise.all([\n getEthPrice8dec(\n provider,\n chainlinkFeedAddress,\n allowStaleFallback ? fallbackEthPriceUsd : null,\n config.onFallback,\n ),\n getPtPerUsdt18dec(\n fetchImpl,\n subgraphUrl,\n pointTokenAddress,\n allowStaleFallback ? fallbackPtPriceUsdt : null,\n config.onFallback,\n ),\n ]);\n\n // feeInUsdt_<usdtDec> = withPremium_wei × ethPrice_8dec / 10^(18+8-usdtDec)\n // feeInPt_18dec = feeInUsdt × ptPerUsdt_18dec / 10^(18-usdtDec)\n // combined = withPremium × ethPrice × ptPerUsdt / 10^(18+8-usdtDec + 18-usdtDec)\n // = / 10^(44 - 2·usdtDec)\n // For usdtDecimals=6: 10^(44-12) = 10^32. But ptPerUsdt is already 18-scaled\n // so we subtract another 18: 10^(32-18) = 10^14? No — let me redo cleanly.\n //\n // Working in fractional units to keep it readable:\n // eth_human = withPremium / 10^18\n // usdt_human = eth_human × ethPriceFloat\n // pt_human = usdt_human × ptPerUsdtFloat\n // pt_18dec = pt_human × 10^18\n //\n // Substituting bigint scales:\n // ethPrice_8dec = ethPriceFloat × 10^8\n // ptPerUsdt_18dec = ptPerUsdtFloat × 10^18 (PT raw per 1 *human* USDT)\n //\n // pt_18dec = (withPremium / 10^18) × (ethPrice_8dec / 10^8)\n // × (ptPerUsdt_18dec / 10^18) × 10^18\n // = withPremium × ethPrice_8dec × ptPerUsdt_18dec / 10^26\n return (withPremium * ethPrice8dec * ptPerUsdt18dec) / 10n ** 26n;\n}\n\n/**\n * Router quoter for the `erc20-transfer` sponsored scenario — picks\n * the correct underlying quoter based on the token being transferred:\n *\n * USDC / USDT → `quoteOperatorFeeUsdt` (Chainlink-based, 6-dec)\n * active PT → `quoteOperatorFeePt` (Chainlink + V3 pool quote)\n *\n * Fee is denominated in the SAME token the user is sending — no extra\n * approval / swap step needed. Caller is responsible for verifying the\n * token IS in the sponsored allowlist before calling; this function\n * just routes by address equality.\n *\n * Returns the fee in the token's native units (6-dec for USDC/USDT,\n * 18-dec for PT). The sponsor-relayer reproduces the same quote\n * server-side with a 5% slack window.\n *\n * @example\n * ```ts\n * const fee = await quoteOperatorFeeForTransfer({\n * provider, chainId: 8453,\n * tokenAddress: USDC_ADDRESS,\n * });\n * ```\n */\nexport interface QuoteOperatorFeeForTransferConfig\n extends Omit<QuoteOperatorFeeUsdtConfig, \"scenario\"> {\n /** The ERC-20 the user is transferring. Drives both quote logic + fee currency. */\n tokenAddress: Address;\n /**\n * Subgraph URL override — only consumed when `tokenAddress` resolves\n * to a PT (USDC/USDT paths don't hit the subgraph).\n */\n subgraphUrl?: string;\n /** PT fallback (only used when `tokenAddress` is a PT and subgraph fails). */\n fallbackPtPriceUsdt?: number;\n fetchImpl?: typeof fetch;\n}\n\nexport async function quoteOperatorFeeForTransfer(\n config: QuoteOperatorFeeForTransferConfig,\n): Promise<bigint> {\n const { provider, chainId, tokenAddress } = config;\n const { usdc, usdt } = getContractAddresses(chainId);\n const tokenLower = tokenAddress.toLowerCase();\n\n const isStable =\n (usdc && tokenLower === usdc.toLowerCase()) ||\n tokenLower === usdt.toLowerCase();\n\n if (isStable) {\n return quoteOperatorFeeUsdt({\n provider,\n chainId,\n scenario: \"erc20-transfer\",\n gasUnits: config.gasUnits,\n premiumBps: config.premiumBps,\n chainlinkFeedAddress: config.chainlinkFeedAddress,\n usdtDecimals: config.usdtDecimals,\n allowStaleFallback: config.allowStaleFallback,\n fallbackEthPriceUsd: config.fallbackEthPriceUsd,\n onFallback: config.onFallback,\n });\n }\n\n // Non-stable → assume PT. Caller validates allowlist upstream.\n return quoteOperatorFeePt({\n provider,\n chainId,\n pointTokenAddress: tokenAddress,\n scenario: \"erc20-transfer\",\n gasUnits: config.gasUnits,\n premiumBps: config.premiumBps,\n chainlinkFeedAddress: config.chainlinkFeedAddress,\n subgraphUrl: config.subgraphUrl,\n usdtDecimals: config.usdtDecimals,\n allowStaleFallback: config.allowStaleFallback,\n fallbackEthPriceUsd: config.fallbackEthPriceUsd,\n fallbackPtPriceUsdt: config.fallbackPtPriceUsdt,\n onFallback: config.onFallback,\n fetchImpl: config.fetchImpl,\n });\n}\n\n/**\n * Fetch ETH/USD from Chainlink. When the feed is unavailable or stale,\n * either:\n * - fallback is `null` → throw `OracleStaleError` (default, v0.7.1+).\n * - fallback is a number → return `Math.round(fallback * 1e8)` after\n * `console.warn`. Caller opted in via `allowStaleFallback: true`.\n */\nasync function getEthPrice8dec(\n provider: PublicClient,\n feed: Address,\n fallback: number | null,\n onFallback?: (info: FallbackInfo) => void,\n): Promise<bigint> {\n try {\n const result = await provider.readContract({\n address: feed,\n abi: CHAINLINK_ABI,\n functionName: \"latestRoundData\",\n });\n const answer = result[1];\n const updatedAt = result[3];\n\n if (answer <= 0n) throw new Error(\"Chainlink: non-positive price\");\n\n const ageS = BigInt(Math.floor(Date.now() / 1000)) - updatedAt;\n if (ageS > CHAINLINK_MAX_AGE_S) {\n throw new Error(`Chainlink: price stale by ${ageS}s`);\n }\n\n return answer;\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n if (fallback === null) {\n throw new OracleStaleError(\"chainlink\", reason);\n }\n if (onFallback) {\n onFallback({ source: \"chainlink\", reason, fallbackValue: fallback });\n } else {\n // Back-compat: default to console.warn when caller hasn't wired\n // an observability hook. Production callers SHOULD pass\n // `onFallback` to surface the fallback in their alert pipeline.\n // eslint-disable-next-line no-console\n console.warn(\n \"[quoteOperatorFee] Chainlink unavailable, using opt-in fallback:\",\n reason,\n );\n }\n return BigInt(Math.round(fallback * 1e8));\n }\n}\n\n/**\n * Fetch PT/USDT price from PAFI subgraph. When subgraph is unavailable\n * either:\n * - `fallbackPtPriceUsdt` is `null` → throw `OracleStaleError`\n * (default, v0.7.1+).\n * - is a number → fall back to `1 / fallback` after `console.warn`.\n */\nasync function getPtPerUsdt18dec(\n fetchImpl: typeof fetch,\n subgraphUrl: string,\n pointTokenAddress: Address,\n fallbackPtPriceUsdt: number | null,\n onFallback?: (info: FallbackInfo) => void,\n): Promise<bigint> {\n try {\n const response = await fetchImpl(subgraphUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n query: POOL_PRICE_QUERY,\n variables: { id: pointTokenAddress.toLowerCase() },\n }),\n });\n\n if (!response.ok) throw new Error(`subgraph HTTP ${response.status}`);\n\n const json = (await response.json()) as GraphQLPriceResponse;\n if (json.errors?.length) {\n throw new Error(json.errors.map((e) => e.message).join(\"; \"));\n }\n\n const pool = json.data?.pafiToken?.pool;\n if (!pool) throw new Error(\"pafiToken or pool not found\");\n\n // Subgraph V4 returns HUMAN-readable price ratios (decimals already\n // applied). For pool {token0=USDT, token1=PT}:\n // token0Price = USDT_human per 1 PT_human (e.g. \"2.0015\")\n // token1Price = PT_human per 1 USDT_human (e.g. \"0.4996\")\n //\n // We want `ptPerUsdt_18dec` = PT_raw per 1 USDT_human, i.e. the price\n // in PT-raw units per one human USDT. That's:\n // PT_human_per_USDT_human × 10^18 = PT_raw_per_USDT_human\n //\n // So always pick the price string that's \"PT per USDT in human\":\n // isPtToken0 → token0Price is \"PT/USDT_other\" but we have USDT here\n // (PT is token0, token1 is USDT) → token0Price is\n // USDT_human per 1 PT_human → invert\n // !isPtToken0 → token1Price is PT_human per 1 USDT_human → use as-is\n //\n // (Older comment claiming subgraph returns raw ratio + the\n // 10^24 / raw formula was wrong — it returned values off by ~10^11\n // for the typical 18/6 decimals split, making operator fee in PT\n // effectively zero.)\n const isPtToken0 =\n pool.token0.id.toLowerCase() === pointTokenAddress.toLowerCase();\n\n let ptPerUsdtHumanStr: string;\n if (isPtToken0) {\n // token0Price = USDT_human per 1 PT_human → invert to PT/USDT\n const usdtPerPtHuman = Number(pool.token0Price);\n if (!Number.isFinite(usdtPerPtHuman) || usdtPerPtHuman <= 0) {\n throw new Error(`invalid pool token0Price from subgraph: ${pool.token0Price}`);\n }\n ptPerUsdtHumanStr = (1 / usdtPerPtHuman).toFixed(18);\n } else {\n // token1Price = PT_human per 1 USDT_human → use directly\n ptPerUsdtHumanStr = pool.token1Price;\n if (!ptPerUsdtHumanStr || Number(ptPerUsdtHumanStr) <= 0) {\n throw new Error(`invalid pool token1Price from subgraph: ${ptPerUsdtHumanStr}`);\n }\n }\n\n const ptPerUsdt18dec = parseBigDecimalTo18(ptPerUsdtHumanStr);\n if (ptPerUsdt18dec === 0n) {\n throw new Error(`pool price parsed to zero: ${ptPerUsdtHumanStr}`);\n }\n return ptPerUsdt18dec;\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n if (fallbackPtPriceUsdt === null) {\n throw new OracleStaleError(\"subgraph\", reason);\n }\n if (onFallback) {\n onFallback({\n source: \"subgraph\",\n reason,\n fallbackValue: fallbackPtPriceUsdt,\n });\n } else {\n // eslint-disable-next-line no-console\n console.warn(\n \"[quoteOperatorFeePt] subgraph unavailable, using opt-in fallback:\",\n reason,\n );\n }\n const ptPerUsdtHuman = 1 / fallbackPtPriceUsdt;\n return parseBigDecimalTo18(ptPerUsdtHuman.toFixed(18));\n }\n}\n\nfunction parseBigDecimalTo18(s: string): bigint {\n const SCALE = 18;\n const [whole = \"0\", frac = \"\"] = s.split(\".\");\n const padded = (frac + \"0\".repeat(SCALE)).slice(0, SCALE);\n return BigInt(whole + padded);\n}\n","import { isAddress } from \"viem\";\nimport type { Address } from \"viem\";\nimport type { PoolKey } from \"../types\";\n\n/**\n * PAFI-hosted subgraph endpoint — single source of truth across all\n * SDK packages.\n *\n * Indexes IssuerRegistry, PointTokenFactory, MintingOracle, and\n * MintFeeWrapper. Schema entities: `MintFeeWrapper`, `MintFeeConfig`,\n * `FeeRecipient`, `MintWithFeeEvent`, `FeeDistribution`.\n */\nexport const PAFI_SUBGRAPH_URL =\n \"https://graph-base-mainnet.pacificfinance.org/subgraphs/name/pafi-subgraph-v4\";\n\n/**\n * Pool query — requests `feeTier`, `token0`, `token1` (the V3-required\n * fields). `tickSpacing` and `hooks` were V4-only; the parser ignores\n * them if the subgraph still returns them, so this is forward- and\n * backward-compatible while the subgraph team finishes their own\n * schema migration.\n */\nconst POOL_QUERY = `\n query GetPoolForPointToken($id: ID!) {\n pafiToken(id: $id) {\n id\n pool {\n id\n feeTier\n token0 { id }\n token1 { id }\n }\n }\n }\n`;\n\ninterface GraphQLResponse {\n data?: {\n pafiToken: {\n id: string;\n pool: {\n id: string;\n feeTier: string;\n /** V4-era field; ignored if present, allowed to be absent. */\n tickSpacing?: string | null;\n /** V4-era field; ignored if present, allowed to be absent. */\n hooks?: string | null;\n token0: { id: string };\n token1: { id: string };\n } | null;\n } | null;\n };\n errors?: { message: string }[];\n}\n\nfunction sortTokens(a: Address, b: Address): [Address, Address] {\n return a.toLowerCase() < b.toLowerCase() ? [a, b] : [b, a];\n}\n\n/**\n * Fetch the Uniswap V3 pool(s) for a PAFI PointToken from the subgraph.\n *\n * Browser and Node compatible — uses globalThis.fetch. Returns an empty\n * array (never throws) when the token has no pool yet or the subgraph is\n * unreachable, so callers can show \"quote unavailable\" without crashing.\n *\n * Tolerates subgraph responses still carrying the V4-era `tickSpacing`\n * and `hooks` fields — those are silently ignored. Subgraph migration\n * can land independently of the SDK migration.\n *\n * @param chainId - Chain ID (reserved for multi-subgraph routing; currently unused)\n * @param pointTokenAddress - The PointToken contract address\n * @param subgraphUrl - Override the default PAFI subgraph URL\n */\nexport async function fetchPafiPools(\n _chainId: number,\n pointTokenAddress: Address,\n subgraphUrl: string = PAFI_SUBGRAPH_URL,\n): Promise<PoolKey[]> {\n let response: Response;\n try {\n response = await fetch(subgraphUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n query: POOL_QUERY,\n variables: { id: pointTokenAddress.toLowerCase() },\n }),\n });\n } catch (err) {\n console.warn(\"[fetchPafiPools] subgraph unreachable:\", (err as Error).message);\n return [];\n }\n\n if (!response.ok) {\n console.warn(`[fetchPafiPools] subgraph returned ${response.status}`);\n return [];\n }\n\n let json: GraphQLResponse;\n try {\n json = (await response.json()) as GraphQLResponse;\n } catch (err) {\n console.warn(\n \"[fetchPafiPools] subgraph returned non-JSON:\",\n (err as Error).message,\n );\n return [];\n }\n if (json.errors && json.errors.length > 0) {\n console.warn(\n \"[fetchPafiPools] subgraph errors:\",\n json.errors.map((e) => e.message).join(\"; \"),\n );\n return [];\n }\n\n const pool = json.data?.pafiToken?.pool;\n if (!pool) return [];\n\n if (\n !isAddress(pool.token0.id) ||\n !isAddress(pool.token1.id) ||\n !Number.isFinite(Number(pool.feeTier))\n ) {\n console.error(\"[fetchPafiPools] invalid pool data in subgraph response — skipping\");\n return [];\n }\n\n const [token0, token1] = sortTokens(\n pool.token0.id as Address,\n pool.token1.id as Address,\n );\n\n return [{\n token0,\n token1,\n fee: Number(pool.feeTier),\n }];\n}\n","import { getContractAddresses } from \"./addresses\";\n\n// Derived from addresses.ts — single source of truth.\n// Update addresses.ts when SC team redeploys; these follow automatically.\nexport const BATCH_EXECUTOR_ADDRESS_BASE_MAINNET = getContractAddresses(8453).batchExecutor;\nexport const BATCH_EXECUTOR_ADDRESS_BASE_SEPOLIA = getContractAddresses(84532).batchExecutor;\n\n// The ABI itself is fully real — re-exported from userop/ for\n// convenience so consumers don't need two imports.\nexport { BATCH_EXECUTOR_ABI } from \"../../userop/batchExecute\";\n","/**\n * PAFI HTTP service URLs by chainId. Mirrors the `getContractAddresses`\n * pattern: SDK ships with the URL for each supported chain so issuers\n * don't pass URLs as env vars / constructor args.\n *\n * When PAFI changes URLs (rebrand, re-host, environment migration),\n * bump the SDK version and re-publish. Issuers pin a version, get the\n * URLs that match.\n *\n * No env override is provided on purpose — different environments use\n * different SDK versions (e.g. an `@pafi-dev/issuer-dev` channel for\n * staging). This keeps issuer integration to chainId + creds only.\n */\nexport interface PafiServiceUrls {\n /**\n * sponsor-relayer base URL (paymaster proxy). PafiBackendClient\n * appends `/paymaster/sponsor`, `/bundler/receipt`, etc.\n */\n sponsorRelayer: string;\n\n /**\n * issuer-api base URL. SettlementClient appends\n * `/issuers/:issuerId/redemption-policy`.\n */\n issuerApi: string;\n}\n\n/**\n * Per-chain URL map. URLs follow the pattern\n * `https://<host>/api/<service>` so Kong gateway can route by path.\n *\n * NOTE: placeholder URLs — Phi will swap in real prod/staging hosts\n * once the deploy targets are finalized. SDK version bump captures\n * the change.\n */\nexport const PAFI_SERVICE_URLS: Record<number, PafiServiceUrls> = {\n // Base mainnet\n 8453: {\n sponsorRelayer: \"https://api-dev.pacificfinance.org/api/sponsor\",\n issuerApi: \"https://api-dev.pacificfinance.org/api/issuer\",\n },\n // Base sepolia\n 84532: {\n sponsorRelayer: \"https://api-dev.pacificfinance.org/api/sponsor\",\n issuerApi: \"https://api-dev.pacificfinance.org/api/issuer\",\n },\n};\n\nexport function getPafiServiceUrls(chainId: number): PafiServiceUrls {\n const urls = PAFI_SERVICE_URLS[chainId];\n if (!urls) {\n throw new Error(\n `getPafiServiceUrls: no PAFI service URLs for chainId ${chainId}. ` +\n `Supported: ${Object.keys(PAFI_SERVICE_URLS).join(\", \")}`,\n );\n }\n return urls;\n}\n","import type {\n ModalOpenOptions,\n PafiWebModalAdapter,\n PafiWebModalHandle,\n} from \"./types\";\n\nconst DEFAULT_WIDTH = 900;\nconst DEFAULT_HEIGHT = 700;\nconst DEFAULT_NAME = \"pafi-web\";\n\n/**\n * Web browser popup adapter. Opens the given URL in a centered\n * `window.open()` popup, polls for close, and optionally forwards\n * `postMessage` events back to the caller.\n *\n * Runtime requirement: `window.open` must exist. Throws if called\n * under Node / SSR / React Native — use `setPafiWebModalAdapter()` to\n * provide a platform-specific adapter in those environments.\n *\n * ## Popup blocking\n *\n * Browsers block `window.open()` unless it happens inside a user\n * gesture (click handler). Callers MUST wire this into a direct click\n * handler — wrapping it in a `setTimeout` or async await before the\n * open call will trigger the blocker.\n */\nexport function openWebPopup(\n url: string,\n options: ModalOpenOptions = {},\n): PafiWebModalHandle {\n if (typeof window === \"undefined\" || typeof window.open !== \"function\") {\n throw new Error(\n \"openWebPopup: `window.open` is not available in this runtime. \" +\n \"Register a platform adapter via setPafiWebModalAdapter() instead.\",\n );\n }\n\n const width = options.width ?? DEFAULT_WIDTH;\n const height = options.height ?? DEFAULT_HEIGHT;\n const name = options.windowName ?? DEFAULT_NAME;\n\n // Center on the screen — works in all major browsers. Falls back\n // gracefully if `screen.availWidth`/`screenX` are unavailable.\n const screenW = window.screen?.availWidth ?? window.innerWidth;\n const screenH = window.screen?.availHeight ?? window.innerHeight;\n const left = Math.max(0, Math.floor((screenW - width) / 2));\n const top = Math.max(0, Math.floor((screenH - height) / 2));\n\n const features = [\n `width=${width}`,\n `height=${height}`,\n `left=${left}`,\n `top=${top}`,\n \"resizable=yes\",\n \"scrollbars=yes\",\n \"noopener=no\", // we need opener.postMessage to work\n ].join(\",\");\n\n const popup = window.open(url, name, features);\n if (!popup) {\n throw new Error(\n \"openWebPopup: popup was blocked. Ensure this call runs inside a user gesture (click handler).\",\n );\n }\n\n // Set up state + listeners --------------------------------------------\n\n let closed = false;\n let pollId: ReturnType<typeof setInterval> | null = null;\n let messageListener: ((e: MessageEvent) => void) | null = null;\n\n const dispose = (): void => {\n if (closed) return;\n closed = true;\n if (pollId !== null) {\n clearInterval(pollId);\n pollId = null;\n }\n if (messageListener) {\n window.removeEventListener(\"message\", messageListener);\n messageListener = null;\n }\n options.onClose?.();\n };\n\n // Poll every 500ms for popup close — there's no `close` event.\n // Stops when `popup.closed` flips to true OR our handle is closed.\n pollId = setInterval(() => {\n if (popup.closed) {\n dispose();\n }\n }, 500);\n\n // Wire postMessage filtering by origin — secure-by-default. An\n // empty `allowedOrigins` would silently reject every message (UI\n // hangs forever waiting for a callback that never arrives), so we\n // throw at construction to surface misconfiguration at startup.\n if (options.onMessage) {\n const allowed = options.allowedOrigins ?? [];\n if (allowed.length === 0) {\n throw new Error(\n \"openPafiWebModal: `allowedOrigins` is empty/missing while `onMessage` \" +\n \"is supplied. The popup-message listener would silently reject every \" +\n \"message — caller must explicitly whitelist PAFI Web's host (e.g. \" +\n \"`['https://app.pacificfinance.org']`) before any payload reaches the \" +\n \"callback. To accept any origin (insecure), pass an explicit list \" +\n \"containing '*' or omit `onMessage` entirely.\",\n );\n }\n const onMessage = options.onMessage;\n messageListener = (event: MessageEvent): void => {\n if (event.source !== popup) return;\n // Allow caller-side wildcard explicitly opt-in; never silently allow.\n if (!allowed.includes(\"*\") && !allowed.includes(event.origin)) return;\n onMessage(event.data, event.origin);\n };\n window.addEventListener(\"message\", messageListener);\n }\n\n // Handle object -------------------------------------------------------\n\n return {\n get isOpen(): boolean {\n return !closed && !popup.closed;\n },\n close(): void {\n if (closed) return;\n try {\n popup.close();\n } catch {\n // Some browsers block close() unless the popup was opened by\n // the same document. Dispose our listeners anyway.\n }\n dispose();\n },\n focus(): void {\n if (closed || popup.closed) return;\n try {\n popup.focus();\n } catch {\n // No-op on failure — focus is best-effort.\n }\n },\n postMessage(data: unknown): void {\n if (closed || popup.closed) return;\n try {\n // targetOrigin '*' is fine here because the caller owns the\n // data being sent. For security on the receiving side, the\n // PAFI Web page should check event.origin itself.\n popup.postMessage(data, \"*\");\n } catch {\n // No-op.\n }\n },\n };\n}\n\n/**\n * The web popup packaged as a {@link PafiWebModalAdapter} so callers\n * can register it explicitly (e.g. in a test harness).\n */\nexport const webPopupAdapter: PafiWebModalAdapter = {\n open(url, options) {\n return openWebPopup(url, options);\n },\n};\n","import { openWebPopup } from \"./webPopup\";\nimport type {\n ModalOpenOptions,\n PafiWebModalAdapter,\n PafiWebModalHandle,\n} from \"./types\";\n\nexport type {\n ModalOpenOptions,\n PafiWebModalAdapter,\n PafiWebModalHandle,\n} from \"./types\";\nexport { openWebPopup, webPopupAdapter } from \"./webPopup\";\n\n/**\n * Module-level adapter registry — allows platform consumers (React\n * Native, Electron, Tauri, etc.) to plug in their own handoff\n * implementation without forking the SDK.\n *\n * Callers set this once at app boot; `openPafiWebModal()` below uses\n * whatever is registered, or falls back to the web popup adapter\n * when `window.open` is available.\n */\nlet registeredAdapter: PafiWebModalAdapter | null = null;\n\n/**\n * Register the adapter used by `openPafiWebModal()`. Typically called\n * once during app initialization — mobile apps register a\n * SFSafariViewController / Chrome Custom Tabs adapter, desktop apps\n * can leave it unset (web popup is the default).\n *\n * Pass `null` to unregister and fall back to the default.\n */\nexport function setPafiWebModalAdapter(\n adapter: PafiWebModalAdapter | null,\n): void {\n registeredAdapter = adapter;\n}\n\n/**\n * Return the currently registered adapter, or `null` when none is set.\n * Useful for tests that want to snapshot-and-restore the adapter.\n */\nexport function getPafiWebModalAdapter(): PafiWebModalAdapter | null {\n return registeredAdapter;\n}\n\n/**\n * Open PAFI Web in the host platform's appropriate UX:\n *\n * - Browser (window.open): centered popup, 900×700 by default\n * - React Native (with adapter registered): SFSafariViewController\n * / Chrome Custom Tabs via `react-native-inappbrowser-reborn` or\n * `expo-web-browser`\n * - Desktop (with adapter registered): custom BrowserWindow / new tab\n *\n * Resolution order:\n * 1. If an adapter was registered via `setPafiWebModalAdapter()`, use it.\n * 2. Else if `window.open` is available, use the built-in web popup.\n * 3. Else throw with a clear error pointing at the adapter registry.\n *\n * @example\n * ```ts\n * // User clicks \"Trade on PAFI\" button\n * button.addEventListener('click', async () => {\n * const modal = await openPafiWebModal('https://app.pacificfinance.org', {\n * allowedOrigins: ['https://app.pacificfinance.org'],\n * onMessage: (data, origin) => {\n * if (typeof data === 'object' && data && 'txHash' in data) {\n * console.log('Swap confirmed:', data.txHash);\n * modal.close();\n * }\n * },\n * onClose: () => {\n * console.log('User closed modal');\n * },\n * });\n * });\n * ```\n */\nexport async function openPafiWebModal(\n url: string,\n options: ModalOpenOptions = {},\n): Promise<PafiWebModalHandle> {\n if (!url || typeof url !== \"string\") {\n throw new Error(\"openPafiWebModal: `url` is required\");\n }\n\n if (registeredAdapter) {\n return Promise.resolve(registeredAdapter.open(url, options));\n }\n\n if (typeof window !== \"undefined\" && typeof window.open === \"function\") {\n return openWebPopup(url, options);\n }\n\n throw new Error(\n \"openPafiWebModal: no adapter registered and `window.open` is unavailable. \" +\n \"Call `setPafiWebModalAdapter()` with a platform-specific adapter (e.g. SFSafariViewController on iOS) during app boot.\",\n );\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["/Users/phitran/Pacific-Finance/pafi-backend/pafi-sdk/packages/core/dist/index.cjs","../src/index.ts","../src/errors.ts","../src/perp/buildPerpDepositWithGasDeduction.ts","../src/contracts/real/orderlyVault.ts","../src/userop/operations.ts","../src/userop/batchExecute.ts","../src/userop/buildUserOperation.ts","../src/perp/buildPerpDepositViaRelay.ts","../src/transfer/buildErc20Transfer.ts","../src/userop/types.ts","../src/userop/serializeUserOp.ts","../src/userop/computeUserOpHash.ts","../src/userop/eip7702Helpers.ts","../src/utils/checkEthAndBranch.ts","../src/utils/v3Path.ts","../src/delegation/checkDelegation.ts","../src/delegation/buildDelegationUserOp.ts","../src/delegation/computeAuthorizationHash.ts","../src/delegation/eip7702Authorization.ts","../src/contracts/real/addresses.ts","../src/delegation/delegateDirect.ts","../src/delegation/attachDelegationIfNeeded.ts","../src/transport/proxyTransport.ts","../src/transport/paymasterFallback.ts","../src/fee/operatorFeeQuoter.ts","../src/subgraph/pools.ts","../src/contracts/real/pointToken.ts","../src/contracts/real/batchExecutor.ts","../src/contracts/real/pafi-services.ts","../src/web-handoff/webPopup.ts","../src/web-handoff/index.ts"],"names":["encodeFunctionData","erc20Abi","keccak256"],"mappings":"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ClD,EAAA;AAEsE,EAAA;AAChC,IAAA;AACS,IAAA;AAC/C,EAAA;AAKE,EAAA;AAGoC,IAAA;AAC7B,IAAA;AACL,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAM8D,EAAA;AACrD,IAAA;AACgB,MAAA;AACE,MAAA;AACvB,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAQmB,EAAA;AACiB,IAAA;AACE,IAAA;AACb,IAAA;AACT,IAAA;AACiB,MAAA;AAC/B,IAAA;AACgD,IAAA;AAClD,EAAA;AAAA;AAGsD,EAAA;AAClB,IAAA;AACX,IAAA;AACT,IAAA;AACiB,MAAA;AAC/B,IAAA;AAC8C,IAAA;AAChD,EAAA;AACF;AD2/CwD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/Users/phitran/Pacific-Finance/pafi-backend/pafi-sdk/packages/core/dist/index.cjs","sourcesContent":[null,"import { createPublicClient, http } from \"viem\";\nimport type { Address, Hex, PublicClient, WalletClient } from \"viem\";\n\n// -------------------------------------------------------------------------\n// Re-export all sub-modules\n// -------------------------------------------------------------------------\nexport * from \"./types\";\nexport * from \"./constants\";\nexport * from \"./errors\";\nexport * from \"./abi/index\";\nexport * from \"./eip712/index\";\nexport * from \"./contract/index\";\n// Quoting + swap orchestration live in @pafi-dev/trading. Core retains\n// the underlying ABI primitives (universalRouterAbi, permit2Abi,\n// v3QuoterV2Abi) + types (PoolKey, V3Path, QuoteResult) — used by\n// trading — but no longer ships swap UserOp builders / quote\n// orchestration.\nexport * from \"./perp/index\";\nexport * from \"./transfer/index\";\nexport * from \"./auth/index\";\n\n// v1.4 — Account Abstraction primitives (EIP-7702 + ERC-4337 v0.7)\nexport * from \"./userop/index\";\nexport * from \"./paymaster/index\";\nexport * from \"./utils/index\";\nexport * from \"./delegation/index\";\nexport * from \"./transport/index\";\n// Operator-fee quoter — pure on-chain + subgraph reads, lets the SDK\n// Direct path compute the PT operator fee without calling the issuer\n// backend. Sponsor-relayer's `FeeValidatorService` runs the same\n// math server-side (with a 5% tolerance window).\nexport * from \"./fee/index\";\n\n// Contract ABIs + addresses. ABI matches deployed PointToken on Base\n// mainnet; the Base Sepolia row stays placeholder. EIP-712 helpers live\n// under `./eip712/` and are already exported above — not re-exported here.\n// Consumers: `import { POINT_TOKEN_ABI, CONTRACT_ADDRESSES, ... } from '@pafi-dev/core'`\nexport * from \"./contracts/index\";\n\n// PAFI Web handoff (mobile + desktop modal helper).\n// `openPafiWebModal()` + adapter registry for platform-specific UX.\nexport * from \"./web-handoff/index\";\n\n// Subgraph helpers — pool discovery via PAFI-hosted subgraph.\n// Browser and Node compatible (plain fetch).\nexport * from \"./subgraph/index\";\n\n// -------------------------------------------------------------------------\n// Internal imports for PafiSDK class\n// -------------------------------------------------------------------------\nimport { buildMintRequestTypedData, signMintRequest, verifyMintRequest } from \"./eip712/mintRequest\";\nimport {\n getMintRequestNonce,\n getTokenName,\n} from \"./contract/pointToken\";\nimport { createLoginMessage } from \"./auth/loginMessage\";\nimport type { LoginMessageParams } from \"./auth/types\";\nimport { ConfigurationError } from \"./errors\";\nimport type {\n EIP712Signature,\n MintRequest,\n PafiSDKConfig,\n PointTokenDomainConfig,\n SignatureVerification,\n SignatureVerifyOptions,\n} from \"./types\";\n\n// -------------------------------------------------------------------------\n// PafiSDK — convenience class wrapping all contract + crypto primitives.\n//\n// This class is HTTP-client-free on purpose. It covers signing, verifying,\n// contract reads, and calldata encoding — the things that need a signer\n// or a provider but no HTTP layer. The issuer backend defines its own HTTP\n// contract via `@pafi-dev/issuer`; frontends build `fetch()` calls against\n// those types directly.\n// -------------------------------------------------------------------------\n\nexport class PafiSDK {\n private _pointTokenAddress?: Address;\n private _signer?: WalletClient;\n private _provider?: PublicClient;\n private _chainId?: number;\n\n // -------------------------------------------------------------------------\n // Domain namespaces — grouped by concern for better IDE autocomplete.\n // Each property is a plain object of bound methods; the underlying logic\n // lives in the flat methods below so both access styles work.\n // -------------------------------------------------------------------------\n\n readonly mint: {\n buildTypedData: PafiSDK[\"buildMintRequestTypedData\"];\n sign: PafiSDK[\"signMintRequest\"];\n verify: PafiSDK[\"verifyMintRequest\"];\n getNonce: PafiSDK[\"getMintRequestNonce\"];\n };\n\n readonly auth: {\n createLoginMessage: PafiSDK[\"createLoginMessage\"];\n signMessage: PafiSDK[\"signLoginMessage\"];\n };\n\n constructor(config: PafiSDKConfig) {\n this._pointTokenAddress = config.pointTokenAddress;\n this._signer = config.signer;\n this._chainId = config.chainId;\n\n if (config.provider) {\n this._provider = config.provider;\n } else if (config.rpcUrl) {\n this._provider = createPublicClient({\n transport: http(config.rpcUrl),\n });\n }\n\n this.mint = {\n buildTypedData: this.buildMintRequestTypedData.bind(this),\n sign: this.signMintRequest.bind(this),\n verify: this.verifyMintRequest.bind(this),\n getNonce: this.getMintRequestNonce.bind(this),\n };\n\n this.auth = {\n createLoginMessage: this.createLoginMessage.bind(this),\n signMessage: this.signLoginMessage.bind(this),\n };\n }\n\n // -------------------------------------------------------------------------\n // Setters\n // -------------------------------------------------------------------------\n\n setPointTokenAddress(address: Address): void {\n this._pointTokenAddress = address;\n }\n\n setSigner(signer: WalletClient): void {\n this._signer = signer;\n }\n\n setProvider(provider: PublicClient): void {\n this._provider = provider;\n }\n\n // -------------------------------------------------------------------------\n // Private guards\n // -------------------------------------------------------------------------\n\n private requirePointToken(): Address {\n if (!this._pointTokenAddress) {\n throw new ConfigurationError(\"pointTokenAddress not set\");\n }\n return this._pointTokenAddress;\n }\n\n private requireProvider(): PublicClient {\n if (!this._provider) {\n throw new ConfigurationError(\"provider not set\");\n }\n return this._provider;\n }\n\n private requireSigner(): WalletClient {\n if (!this._signer) {\n throw new ConfigurationError(\"signer not set\");\n }\n return this._signer;\n }\n\n private requireChainId(): number {\n if (this._chainId === undefined) {\n throw new ConfigurationError(\"chainId not set\");\n }\n return this._chainId;\n }\n\n // -------------------------------------------------------------------------\n // Domain\n // -------------------------------------------------------------------------\n\n async getDomain(): Promise<PointTokenDomainConfig> {\n const provider = this.requireProvider();\n const pointToken = this.requirePointToken();\n const chainId = this.requireChainId();\n const name = await getTokenName(provider, pointToken);\n return { name, verifyingContract: pointToken, chainId };\n }\n\n // -------------------------------------------------------------------------\n // EIP-712 — delegates to pure functions\n // -------------------------------------------------------------------------\n\n async buildMintRequestTypedData(message: MintRequest) {\n const domain = await this.getDomain();\n return buildMintRequestTypedData(domain, message);\n }\n\n async signMintRequest(message: MintRequest): Promise<EIP712Signature> {\n const domain = await this.getDomain();\n return signMintRequest(this.requireSigner(), domain, message);\n }\n\n async verifyMintRequest(\n message: MintRequest,\n signature: Hex,\n expectedMinter: Address,\n options?: SignatureVerifyOptions,\n ): Promise<SignatureVerification> {\n const domain = await this.getDomain();\n return verifyMintRequest(\n domain,\n message,\n signature,\n expectedMinter,\n options,\n );\n }\n\n // -------------------------------------------------------------------------\n // Contract reads\n // -------------------------------------------------------------------------\n\n async getMintRequestNonce(receiver: Address): Promise<bigint> {\n return getMintRequestNonce(\n this.requireProvider(),\n this.requirePointToken(),\n receiver,\n );\n }\n\n // -------------------------------------------------------------------------\n // Auth — EIP-4361 login helpers (offline, stateless)\n // -------------------------------------------------------------------------\n\n async createLoginMessage(\n params: Omit<LoginMessageParams, \"address\" | \"chainId\">,\n ): Promise<string> {\n const signer = this.requireSigner();\n const chainId = this.requireChainId();\n const account = signer.account;\n if (!account) {\n throw new ConfigurationError(\"signer has no account attached\");\n }\n return createLoginMessage({ ...params, address: account.address, chainId });\n }\n\n /** Sign a login message string with the current signer (personal_sign) */\n async signLoginMessage(message: string): Promise<Hex> {\n const signer = this.requireSigner();\n const account = signer.account;\n if (!account) {\n throw new ConfigurationError(\"signer has no account attached\");\n }\n return signer.signMessage({ account, message });\n }\n}\n\n// `buildUniversalRouterExecuteArgs` and other swap helpers moved to\n// `@pafi-dev/trading` along with `findBestQuote`.\n","/**\n * Unified error base for the entire SDK (core + issuer + trading).\n * Subclasses declare `code` (machine-readable) and `httpStatus`\n * (recommended HTTP status for issuer's controller).\n *\n * Issuer's `createSdkErrorMapper` routes any `PafiSdkError` instance\n * through framework-specific exception factories; non-PafiSdkError\n * still becomes 500.\n */\nexport type SdkErrorHttpStatus =\n | \"not_found\"\n | \"forbidden\"\n | \"unprocessable\"\n | \"service_unavailable\";\n\n/**\n * Stripe-style error taxonomy. The SDK emits one of these on every\n * error so consumers can branch UI behavior on `type` (toast vs modal\n * vs retry banner) without whitelisting individual `code` values.\n */\nexport type PafiErrorType =\n | \"validation_error\"\n | \"authentication_error\"\n | \"authorization_error\"\n | \"not_found_error\"\n | \"business_logic_error\"\n | \"rate_limit_error\"\n | \"server_error\"\n | \"service_unavailable_error\";\n\n/** Numeric HTTP status implied by an `SdkErrorHttpStatus` slot. */\nexport const SDK_ERROR_HTTP_STATUS_CODE: Record<SdkErrorHttpStatus, number> = {\n not_found: 404,\n forbidden: 403,\n unprocessable: 422,\n service_unavailable: 503,\n};\n\n/** Default `type` slot for a numeric HTTP status. */\nexport function defaultErrorTypeForStatus(status: number): PafiErrorType {\n if (status === 400) return \"validation_error\";\n if (status === 401) return \"authentication_error\";\n if (status === 403) return \"authorization_error\";\n if (status === 404) return \"not_found_error\";\n if (status === 422) return \"business_logic_error\";\n if (status === 429) return \"rate_limit_error\";\n if (status === 503) return \"service_unavailable_error\";\n return \"server_error\";\n}\n\nexport abstract class PafiSdkError extends Error {\n abstract readonly code: string;\n /**\n * `true` when the FE should consider a retry safe — typically because\n * the failure is transient.\n */\n readonly safeToRetry: boolean = false;\n readonly details?: unknown;\n abstract readonly httpStatus: SdkErrorHttpStatus;\n /**\n * Optional Stripe-style taxonomy override. Defaults to the type\n * implied by `httpStatus` (forbidden→authorization_error,\n * unprocessable→business_logic_error, etc).\n */\n readonly type?: PafiErrorType;\n /**\n * Optional name of the request field that triggered the error (e.g.\n * `\"amount\"`, `\"chainId\"`). Surfaced on validation failures so the\n * client can highlight the offending field.\n */\n readonly param?: string;\n /**\n * Optional structured context (e.g. `{ available, requested }` for a\n * cap denial). Distinct from `details` — `metadata` is meant for\n * UI consumption; `details` carries raw debug info.\n */\n readonly metadata?: Record<string, unknown>;\n\n constructor(message: string) {\n super(message);\n this.name = new.target.name;\n }\n}\n\n/**\n * SDK-level misconfiguration — required input missing on the\n * `PafiSDK` class or a helper that needs a provider/signer/chainId.\n * The SDK can't service the request until the deployment is fixed,\n * so this routes to **503** in `createSdkErrorMapper`.\n *\n * Note: `@pafi-dev/issuer` has a separate `ConfigurationError` with\n * the same name but a different shape (carries a caller-supplied\n * `code`). Both extend `PafiSdkError`; pick based on which package\n * raised the error.\n */\nexport class ConfigurationError extends PafiSdkError {\n readonly httpStatus = \"service_unavailable\" as const;\n readonly code = \"CONFIGURATION_ERROR\" as const;\n readonly type = \"server_error\" as const;\n constructor(message: string) {\n super(message);\n }\n}\n\n/**\n * EIP-712 / EIP-7702 signing failed inside the SDK — typically the\n * signer wallet rejected, the KMS was unreachable, or a signature\n * post-condition (recover-to-expected-address) didn't hold.\n *\n * Routes to **503** because the most common cause is transient signer\n * infrastructure (KMS hiccup); retry is often safe. Set\n * `safeToRetry = true` from the call site if you can prove it.\n */\nexport class SigningError extends PafiSdkError {\n readonly httpStatus = \"service_unavailable\" as const;\n readonly code = \"SIGNING_FAILED\" as const;\n readonly type = \"server_error\" as const;\n constructor(message: string) {\n super(message);\n }\n}\n\n/**\n * `eth_call` dry-run reverted. The on-chain tx would also revert —\n * caller's params are bad (slippage, insufficient balance, expired\n * deadline). Routes to **422** as a business-logic failure.\n *\n * `operation` is a short tag (`\"swap\"`, `\"perp-deposit\"`, etc.) for\n * log-grouping; `reason` carries the raw revert string surfaced by\n * the simulator.\n */\nexport class SimulationError extends PafiSdkError {\n readonly httpStatus = \"unprocessable\" as const;\n readonly code = \"SIMULATION_FAILED\" as const;\n readonly type = \"business_logic_error\" as const;\n readonly operation: string;\n readonly reason: string;\n constructor(operation: string, reason: string) {\n super(`Simulation failed for ${operation}: ${reason}`);\n this.operation = operation;\n this.reason = reason;\n }\n}\n\n/**\n * External HTTP call (Pimlico bundler, PAFI sponsor-relayer, PAFI\n * issuer-api) failed. Routes to **503**. `upstreamStatus` carries the\n * remote HTTP status when known (e.g. 502 from Pimlico for a bundler\n * outage) — useful for log-grouping but distinct from `httpStatus`\n * which is the status PAFI returns to its OWN caller.\n */\nexport class ApiError extends PafiSdkError {\n readonly httpStatus = \"service_unavailable\" as const;\n readonly code = \"API_ERROR\" as const;\n readonly type = \"service_unavailable_error\" as const;\n readonly upstreamStatus?: number;\n constructor(message: string, upstreamStatus?: number) {\n super(message);\n this.upstreamStatus = upstreamStatus;\n }\n}\n\n/**\n * Thrown by `quoteOperatorFee*` when an upstream price source\n * (Chainlink ETH/USD, PAFI subgraph) is unavailable or stale and the\n * caller did not opt in to the hardcoded fallback prices via\n * `allowStaleFallback: true`.\n *\n * Extends the unified `PafiSdkError` so issuer's `createSdkErrorMapper`\n * routes it to 503 instead of leaking as a 500.\n */\nexport class OracleStaleError extends PafiSdkError {\n readonly httpStatus = \"service_unavailable\" as const;\n readonly code = \"ORACLE_STALE\" as const;\n readonly source: \"chainlink\" | \"subgraph\";\n readonly reason: string;\n constructor(source: \"chainlink\" | \"subgraph\", reason: string) {\n super(`Oracle ${source} unavailable: ${reason}`);\n this.source = source;\n this.reason = reason;\n }\n}\n\n/**\n * Generic 4xx-class validation failure for input checks at any SDK\n * boundary (core helpers, trading handlers, etc.). Issuer's\n * `createSdkErrorMapper` routes to 422.\n *\n * Uses an instance `details` field so subclasses can override the\n * declaration; matches the issuer-side shape that pre-existed here.\n */\nexport class ValidationError extends PafiSdkError {\n readonly httpStatus = \"unprocessable\" as const;\n readonly type = \"validation_error\" as const;\n readonly code: string;\n declare readonly details?: Record<string, unknown>;\n declare readonly param?: string;\n declare readonly metadata?: Record<string, unknown>;\n\n constructor(\n code: string,\n message: string,\n details?: Record<string, unknown>,\n options?: { param?: string; metadata?: Record<string, unknown> },\n ) {\n super(message);\n this.code = code;\n (this as { details?: Record<string, unknown> }).details = details;\n if (options?.param) {\n (this as { param?: string }).param = options.param;\n }\n if (options?.metadata) {\n (this as { metadata?: Record<string, unknown> }).metadata = options.metadata;\n }\n }\n}\n","import { encodeFunctionData } from \"viem\";\nimport type { Address, Hex } from \"viem\";\nimport {\n ORDERLY_VAULT_ABI,\n ORDERLY_VAULT_ADDRESSES,\n type VaultDepositFE,\n} from \"../contracts/real/orderlyVault\";\nimport { erc20ApproveOp, rawCallOp } from \"../userop/operations\";\nimport { buildPartialUserOperation } from \"../userop/buildUserOperation\";\nimport type { Operation, PartialUserOperation } from \"../userop/types\";\n\n/**\n * Deposit USDC from the user's wallet into the Orderly perp Vault on\n * Base mainnet.\n *\n * Builds a `PartialUserOperation` packaging the 2 inner calls:\n *\n * 1. `USDC.approve(orderlyVault, amount)` — let the Vault pull USDC\n * 2. `OrderlyVault.deposit{value: layerZeroFee}(VaultDepositFE)` —\n * transfer USDC into the Vault + emit a LayerZero message that\n * credits the user's perp account on the Orderly chain\n *\n * ## ⚠️ Native ETH constraint\n *\n * PAFI sponsor-relayer sponsors GAS, **not `msg.value`**. The LayerZero\n * cross-chain fee MUST come from the user's own native ETH balance on\n * Base — even when the rest of the UserOp is sponsored.\n *\n * Quote the fee BEFORE calling this builder via:\n *\n * const fee = await client.readContract({\n * address: orderlyVault,\n * abi: ORDERLY_VAULT_ABI,\n * functionName: 'getDepositFee',\n * args: [user, depositData],\n * });\n *\n * If `user.eth < fee`, surface this error to the FE so the user can\n * top up before retrying — the Vault `deposit{value: 0}` call will\n * revert otherwise.\n *\n * ## Why no PT/USDC fee deduction?\n *\n * Unlike Scenario 1/2/4 which deduct an operator fee in PT/USDC inside\n * the batch, perp deposit doesn't append a fee transfer because:\n *\n * - User is paying Orderly's LayerZero fee directly via `msg.value`\n * - There's no PAFI-specific operator cost on top — Orderly handles\n * the off-chain accounting once the LayerZero message lands\n *\n * If we want to charge a PAFI service fee on top later, append a\n * `USDC.transfer(feeRecipient, fee)` op — same pattern as the swap\n * builder.\n */\nexport interface BuildPerpDepositWithGasDeductionParams {\n /** User EOA (will be `msg.sender` via EIP-7702 delegation). */\n userAddress: Address;\n /** ERC-4337 account nonce — fetched from EntryPoint by the caller. */\n aaNonce: bigint;\n\n /** Chain ID for Orderly Vault address resolution. */\n chainId: number;\n /**\n * Override Orderly Vault address (e.g. for fork tests). Defaults to\n * `ORDERLY_VAULT_ADDRESSES[chainId]`.\n */\n vaultAddress?: Address;\n\n /**\n * USDC ERC-20 address — the actual token Orderly accepts. Caller\n * resolves this via `vault.getAllowedToken(TOKEN_HASHES.USDC)` so we\n * don't hardcode the wrong USDC variant (native vs bridged).\n */\n usdcAddress: Address;\n\n /** USDC amount to deposit (uint128, 6 decimals). */\n amount: bigint;\n\n /**\n * Pre-built `VaultDepositFE` struct — caller computes accountId via\n * `computeAccountId(user, brokerHash)` and supplies tokenHash.\n */\n depositData: VaultDepositFE;\n\n /**\n * LayerZero fee in wei — from `vault.getDepositFee(user, data)`.\n * Becomes the `msg.value` on the `deposit()` call. User MUST hold\n * ≥ this much native ETH or the call reverts.\n */\n layerZeroFee: bigint;\n\n gasLimits?: {\n callGasLimit?: bigint;\n verificationGasLimit?: bigint;\n preVerificationGas?: bigint;\n };\n}\n\n/**\n * Build an unsigned UserOp for Scenario 3. Returns a\n * `PartialUserOperation` — caller attaches paymaster sponsorship for\n * gas + the user's UserOp-hash signature, then submits to the Bundler.\n */\nexport function buildPerpDepositWithGasDeduction(\n params: BuildPerpDepositWithGasDeductionParams,\n): PartialUserOperation {\n if (params.amount <= 0n) {\n throw new Error(\"buildPerpDepositWithGasDeduction: amount must be positive\");\n }\n if (params.layerZeroFee < 0n) {\n throw new Error(\n \"buildPerpDepositWithGasDeduction: layerZeroFee cannot be negative\",\n );\n }\n if (params.depositData.tokenAmount !== params.amount) {\n throw new Error(\n `buildPerpDepositWithGasDeduction: depositData.tokenAmount (${params.depositData.tokenAmount}) ` +\n `must equal amount (${params.amount})`,\n );\n }\n\n const vault =\n params.vaultAddress ?? ORDERLY_VAULT_ADDRESSES[params.chainId];\n if (!vault) {\n throw new Error(\n `buildPerpDepositWithGasDeduction: no Orderly Vault address for chainId ${params.chainId}`,\n );\n }\n\n const depositCallData: Hex = encodeFunctionData({\n abi: ORDERLY_VAULT_ABI,\n functionName: \"deposit\",\n args: [params.depositData],\n });\n\n const operations: Operation[] = [\n erc20ApproveOp(params.usdcAddress, vault, params.amount),\n {\n ...rawCallOp(vault, depositCallData),\n // BatchExecutor passes `value` from the inner call along; this\n // becomes the LayerZero fee. The aggregated `msg.value` for the\n // top-level UserOp must equal the sum of inner `value`s.\n value: params.layerZeroFee,\n },\n ];\n\n return buildPartialUserOperation({\n sender: params.userAddress,\n nonce: params.aaNonce,\n operations,\n gasLimits: {\n callGasLimit: params.gasLimits?.callGasLimit ?? 800_000n,\n verificationGasLimit:\n params.gasLimits?.verificationGasLimit ?? 150_000n,\n preVerificationGas: params.gasLimits?.preVerificationGas ?? 50_000n,\n },\n });\n}\n","import { keccak256, encodePacked, encodeAbiParameters } from \"viem\";\nimport type { Address, Hex } from \"viem\";\n\n/**\n * Orderly Network Vault — entrypoint for **perp deposit** flow on\n * Base mainnet (Scenario 3).\n *\n * Source of truth for addresses + integration:\n * https://orderly.network/docs/build-on-omnichain/addresses#base\n *\n * ## Architecture\n *\n * User wallet (Base)\n * │ 1. USDC.approve(vault, amount)\n * │ 2. vault.deposit{value: layerZeroFee}({\n * │ accountId, brokerHash, tokenHash, tokenAmount\n * │ })\n * ▼\n * OrderlyVault (Base)\n * │ — locks USDC, emits LayerZero message\n * ▼\n * Orderly chain (off-chain matching engine)\n * │ — credits the user's perp account\n * ▼\n * User can now place perp orders via Orderly REST/WS API\n *\n * **Important:** `value` is required and is the LayerZero cross-chain\n * fee (paid in native ETH on Base). Quote it via\n * `vault.getDepositFee(user, data)` BEFORE calling deposit. Fees vary\n * with destination chain congestion.\n *\n * ## Sponsored vs direct\n *\n * ERC-4337 paymasters sponsor GAS, not `msg.value`. Even on the\n * sponsored path the user MUST hold enough native ETH on Base to cover\n * `getDepositFee()` (typically ~0.0002 ETH at quiet times).\n */\n\n/** Orderly Vault on Base mainnet (EIP-55 checksum). */\nexport const ORDERLY_VAULT_BASE_MAINNET: Address =\n \"0x816f722424B49Cf1275cc86DA9840Fbd5a6167e9\";\n\n/**\n * Per-chain registry of the Orderly Vault. Add chains here as Orderly\n * deploys them.\n */\nexport const ORDERLY_VAULT_ADDRESSES: Record<number, Address> = {\n 8453: ORDERLY_VAULT_BASE_MAINNET,\n};\n\n/**\n * Pre-computed broker hashes — `keccak256(abi.encodePacked(brokerId))`.\n * Add new brokers as they launch.\n *\n * Reference: https://orderly.network/docs/build-on-omnichain/user-flows/accountId\n */\nexport const BROKER_HASHES = {\n /** Default partner broker on Base — most commonly whitelisted. */\n woofi_pro: keccak256(encodePacked([\"string\"], [\"woofi_pro\"])),\n /** Orderly's own white-label broker. */\n orderly: keccak256(encodePacked([\"string\"], [\"orderly\"])),\n /** LogX. */\n logx: keccak256(encodePacked([\"string\"], [\"logx\"])),\n /** Orderly's demo broker — whitelisted on Base mainnet for dev/test\n * integrations. Same Vault, same path; an Orderly demo account is\n * independent from production accounts. */\n demo: keccak256(encodePacked([\"string\"], [\"demo\"])),\n} as const satisfies Record<string, Hex>;\n\n/**\n * Pre-computed token hashes — `keccak256(abi.encodePacked(tokenSymbol))`.\n * The hash maps to the actual ERC-20 address on each chain via\n * `vault.getAllowedToken(tokenHash)`.\n *\n * Note: Orderly canonicalises by symbol (USDC, not \"USD Coin\").\n */\nexport const TOKEN_HASHES = {\n USDC: keccak256(encodePacked([\"string\"], [\"USDC\"])),\n} as const satisfies Record<string, Hex>;\n\n/**\n * Compute Orderly's `accountId` — uniquely identifies a user+broker\n * tuple. Matches the off-chain formula used by the Orderly SDK so\n * deposits + future REST API calls reference the same account.\n *\n * accountId = keccak256(abi.encode(user, brokerHash))\n *\n * Note `abi.encode` (not `encodePacked`) — uses 32-byte left-padding.\n */\nexport function computeAccountId(\n user: Address,\n brokerHash: Hex,\n): Hex {\n return keccak256(\n encodeAbiParameters(\n [\n { type: \"address\" },\n { type: \"bytes32\" },\n ],\n [user, brokerHash],\n ),\n );\n}\n\n/**\n * Minimal Orderly Vault ABI — just the functions PAFI needs for\n * Scenario 3 (perp deposit). Full ABI is on Orderly's docs site.\n */\nexport const ORDERLY_VAULT_ABI = [\n // Deposit USDC into Orderly perp account. Emits LayerZero message\n // to the Orderly chain. `msg.value` MUST equal `getDepositFee(...)`.\n {\n type: \"function\",\n name: \"deposit\",\n stateMutability: \"payable\",\n inputs: [\n {\n name: \"data\",\n type: \"tuple\",\n components: [\n { name: \"accountId\", type: \"bytes32\" },\n { name: \"brokerHash\", type: \"bytes32\" },\n { name: \"tokenHash\", type: \"bytes32\" },\n { name: \"tokenAmount\", type: \"uint128\" },\n ],\n },\n ],\n outputs: [],\n },\n // Quote the LayerZero fee for a deposit message. Pass the same\n // VaultDepositFE struct you intend to send.\n {\n type: \"function\",\n name: \"getDepositFee\",\n stateMutability: \"view\",\n inputs: [\n { name: \"user\", type: \"address\" },\n {\n name: \"data\",\n type: \"tuple\",\n components: [\n { name: \"accountId\", type: \"bytes32\" },\n { name: \"brokerHash\", type: \"bytes32\" },\n { name: \"tokenHash\", type: \"bytes32\" },\n { name: \"tokenAmount\", type: \"uint128\" },\n ],\n },\n ],\n outputs: [{ name: \"\", type: \"uint256\" }],\n },\n // Pre-flight check — is this brokerId whitelisted on this Vault?\n {\n type: \"function\",\n name: \"getAllowedBroker\",\n stateMutability: \"view\",\n inputs: [{ name: \"brokerHash\", type: \"bytes32\" }],\n outputs: [{ name: \"\", type: \"bool\" }],\n },\n // Pre-flight check — what ERC-20 address does this token symbol\n // resolve to on this Vault?\n {\n type: \"function\",\n name: \"getAllowedToken\",\n stateMutability: \"view\",\n inputs: [{ name: \"tokenHash\", type: \"bytes32\" }],\n outputs: [{ name: \"\", type: \"address\" }],\n },\n] as const;\n\n/** Struct mirror of `IOrderlyVault.VaultDepositFE` for type-safe builders. */\nexport interface VaultDepositFE {\n accountId: Hex;\n brokerHash: Hex;\n tokenHash: Hex;\n /** uint128 — USDC has 6 decimals. */\n tokenAmount: bigint;\n}\n","import { encodeFunctionData, erc20Abi, parseAbi } from \"viem\";\nimport type { Address } from \"viem\";\nimport type { Operation } from \"./types\";\n\n/**\n * ERC20Burnable extension — `burn(uint256 amount)` burns from msg.sender.\n * The EOA (via EIP-7702 delegation) is the `msg.sender` when a batch\n * runs — so this burns the user's balance without any role check.\n */\nconst ERC20_BURNABLE_ABI = parseAbi([\"function burn(uint256 amount)\"]);\n\n/**\n * Build an ERC-20 `transfer(to, amount)` operation. Used inside a batch\n * to move fee tokens from the user to the fee recipient atomically with\n * the main action.\n */\nexport function erc20TransferOp(\n token: Address,\n to: Address,\n amount: bigint,\n): Operation {\n return {\n target: token,\n value: 0n,\n data: encodeFunctionData({\n abi: erc20Abi,\n functionName: \"transfer\",\n args: [to, amount],\n }),\n };\n}\n\n/**\n * Build an ERC-20 `approve(spender, amount)` operation. Used inside a\n * batch before a swap / deposit call so the AMM / protocol can pull\n * tokens from the user.\n */\nexport function erc20ApproveOp(\n token: Address,\n spender: Address,\n amount: bigint,\n): Operation {\n return {\n target: token,\n value: 0n,\n data: encodeFunctionData({\n abi: erc20Abi,\n functionName: \"approve\",\n args: [spender, amount],\n }),\n };\n}\n\n/**\n * Build an ERC-20 `burn(amount)` operation (OpenZeppelin ERC20Burnable\n * extension). Burns from `msg.sender`, which — via EIP-7702 — is the\n * user's EOA.\n *\n * Requires the PointToken contract to expose a public `burn(uint256)`\n * without a role check. See\n * [SDK_V1.4_TASKS.md §11 — PointToken.burn callable via batch].\n */\nexport function erc20BurnOp(token: Address, amount: bigint): Operation {\n return {\n target: token,\n value: 0n,\n data: encodeFunctionData({\n abi: ERC20_BURNABLE_ABI,\n functionName: \"burn\",\n args: [amount],\n }),\n };\n}\n\n/**\n * Build a raw call operation with caller-supplied calldata. Useful for\n * non-ERC-20 contracts (PoolManager.swap, PerpDEX.deposit, Relayer.mint)\n * where the encoding is specific to that protocol.\n */\nexport function rawCallOp(\n target: Address,\n data: `0x${string}`,\n value: bigint = 0n,\n): Operation {\n return { target, value, data };\n}\n","import { decodeFunctionData, encodeFunctionData, parseAbi } from \"viem\";\nimport type { Address, Hex } from \"viem\";\nimport type { Operation } from \"./types\";\n\n/**\n * Standard BatchExecutor ABI — a contract that takes an array of calls\n * and invokes each one in sequence, reverting all if any fail.\n *\n * Function name is `executeBatch` (selector `0x34fcd5be`) to match\n * Pimlico's `Simple7702Account` — the EIP-7702 delegate impl PAFI\n * pins on Base mainnet. The shorter `execute(Call[])` (selector\n * `0x3f707e6b`) only exists on standalone batch executor contracts;\n * calling it on a 7702 delegated EOA falls through to the fallback\n * function and reverts with \"account: not from EntryPoint\" (AA23).\n */\nexport const BATCH_EXECUTOR_ABI = parseAbi([\n \"function executeBatch((address target, uint256 value, bytes data)[] calls)\",\n]);\n\n/**\n * Encode a batch of operations into calldata for\n * `BatchExecutor.executeBatch((address,uint256,bytes)[])`. The\n * resulting calldata goes into `UserOperation.callData`.\n *\n * When the EOA has an EIP-7702 delegation to a BatchExecutor, calling\n * this calldata against the EOA runs all operations with\n * `msg.sender = EOA` for each inner call.\n *\n * @param operations batch of calls, in execution order\n * @returns calldata bytes for `executeBatch((address,uint256,bytes)[])`\n */\nexport function encodeBatchExecute(operations: Operation[]): Hex {\n if (operations.length === 0) {\n throw new Error(\"encodeBatchExecute: operations array must not be empty\");\n }\n return encodeFunctionData({\n abi: BATCH_EXECUTOR_ABI,\n functionName: \"executeBatch\",\n args: [\n operations.map((op) => ({\n target: op.target,\n value: op.value,\n data: op.data,\n })),\n ],\n });\n}\n\n/**\n * Decode `BatchExecutor.executeBatch(calls[])` callData back into\n * individual `{ to, data, value }` objects.\n *\n * Used by issuer backends to convert a `PartialUserOperation.callData`\n * into the `calls[]` array returned to web/FE clients (which submit via\n * `permissionless.sendTransaction({ calls })`).\n */\nexport function decodeBatchExecuteCalls(\n callData: Hex,\n): Array<{ to: string; data: string; value: string }> {\n const { args } = decodeFunctionData({\n abi: BATCH_EXECUTOR_ABI,\n data: callData,\n });\n return (\n args[0] as ReadonlyArray<{ target: Address; value: bigint; data: Hex }>\n ).map((c) => ({\n to: c.target,\n data: c.data,\n value: c.value.toString(),\n }));\n}\n","import type { Address } from \"viem\";\nimport type { Operation, PartialUserOperation, UserOperation } from \"./types\";\nimport { encodeBatchExecute } from \"./batchExecute\";\n\n/**\n * Default gas limits — rough upper bounds for a 2-op batch on Base.\n * Bundler re-estimates before submission, so these are only used when\n * the caller doesn't supply their own.\n */\nconst DEFAULT_CALL_GAS_LIMIT = 500_000n;\nconst DEFAULT_VERIFICATION_GAS_LIMIT = 150_000n;\nconst DEFAULT_PRE_VERIFICATION_GAS = 50_000n;\n\nexport interface BuildPartialUserOpParams {\n /** User's EOA (with EIP-7702 delegation). */\n sender: Address;\n /** Batch of operations — encoded into callData via `encodeBatchExecute`. */\n operations: Operation[];\n /** EntryPoint nonce for this sender. Caller fetches from the EntryPoint contract. */\n nonce: bigint;\n /** Optional gas overrides; bundler re-estimates before submission. */\n gasLimits?: {\n callGasLimit?: bigint;\n verificationGasLimit?: bigint;\n preVerificationGas?: bigint;\n };\n /** Optional fee overrides; bundler usually fills these. */\n feeOverrides?: {\n maxFeePerGas?: bigint;\n maxPriorityFeePerGas?: bigint;\n };\n}\n\n/**\n * Build a partial ERC-4337 UserOperation from a batch of operations.\n * Paymaster fields and signature are populated later:\n * 1. Call `PafiBackendClient.requestSponsorship()` → get paymaster fields\n * 2. Compute userOpHash and have the user sign it (via Privy)\n * 3. Attach `signature` → submit to bundler\n *\n * This function is a pure struct builder — no network calls.\n */\nexport function buildPartialUserOperation(\n params: BuildPartialUserOpParams,\n): PartialUserOperation {\n return {\n sender: params.sender,\n nonce: params.nonce,\n callData: encodeBatchExecute(params.operations),\n callGasLimit: params.gasLimits?.callGasLimit ?? DEFAULT_CALL_GAS_LIMIT,\n verificationGasLimit:\n params.gasLimits?.verificationGasLimit ??\n DEFAULT_VERIFICATION_GAS_LIMIT,\n preVerificationGas:\n params.gasLimits?.preVerificationGas ?? DEFAULT_PRE_VERIFICATION_GAS,\n maxFeePerGas: params.feeOverrides?.maxFeePerGas ?? 0n,\n maxPriorityFeePerGas: params.feeOverrides?.maxPriorityFeePerGas ?? 0n,\n };\n}\n\n/**\n * Assemble a full UserOperation once paymaster fields + signature are\n * known. Used after `PafiBackendClient.requestSponsorship()` and user\n * signing complete.\n */\nexport function assembleUserOperation(\n partial: PartialUserOperation,\n paymaster: {\n paymaster: Address;\n paymasterData: `0x${string}`;\n paymasterVerificationGasLimit: bigint;\n paymasterPostOpGasLimit: bigint;\n },\n signature: `0x${string}`,\n): UserOperation {\n return {\n ...partial,\n paymaster: paymaster.paymaster,\n paymasterData: paymaster.paymasterData,\n paymasterVerificationGasLimit: paymaster.paymasterVerificationGasLimit,\n paymasterPostOpGasLimit: paymaster.paymasterPostOpGasLimit,\n signature,\n };\n}\n","import { encodeFunctionData } from \"viem\";\nimport type { Address, Hex } from \"viem\";\nimport { erc20ApproveOp, erc20TransferOp, rawCallOp } from \"../userop/operations\";\nimport { buildPartialUserOperation } from \"../userop/buildUserOperation\";\nimport type { Operation, PartialUserOperation } from \"../userop/types\";\n\n/**\n * Minimal ABI for the Orderly perp-deposit Relay (deployed at\n * `CONTRACT_ADDRESSES[chainId].orderlyRelay`).\n *\n * The Relay holds an ETH reserve and pays Orderly's LayerZero msg.value\n * out of it. Users only need USDC + the Relay-charged USDC fee — they\n * never hold ETH for msg.value, which unblocks the ERC-4337 sponsored\n * gas path for perp deposits (paymaster covers gas, NOT msg.value).\n */\nexport const ORDERLY_RELAY_ABI = [\n {\n type: \"function\",\n name: \"deposit\",\n stateMutability: \"nonpayable\",\n inputs: [\n {\n name: \"req\",\n type: \"tuple\",\n components: [\n { name: \"token\", type: \"address\" },\n { name: \"receiver\", type: \"address\" },\n { name: \"brokerHash\", type: \"bytes32\" },\n { name: \"totalAmount\", type: \"uint128\" },\n { name: \"maxFee\", type: \"uint128\" },\n ],\n },\n ],\n outputs: [],\n },\n {\n type: \"function\",\n name: \"quoteTokenFee\",\n stateMutability: \"view\",\n inputs: [\n {\n name: \"req\",\n type: \"tuple\",\n components: [\n { name: \"token\", type: \"address\" },\n { name: \"receiver\", type: \"address\" },\n { name: \"brokerHash\", type: \"bytes32\" },\n { name: \"totalAmount\", type: \"uint128\" },\n { name: \"maxFee\", type: \"uint128\" },\n ],\n },\n ],\n outputs: [{ name: \"\", type: \"uint128\" }],\n },\n] as const;\n\n/**\n * `Relay.DepositRequest` — the struct passed to `deposit()` and\n * `quoteTokenFee()`. The Relay pulls `totalAmount` of `token` from\n * `msg.sender`, computes a token-denominated fee (capped at `maxFee`)\n * to cover the LayerZero msg.value out of its ETH reserve, and forwards\n * `(totalAmount - tokenFee)` to Orderly Vault on behalf of `receiver`.\n */\nexport interface OrderlyRelayDepositRequest {\n /** ERC-20 to deposit (must be registered + enabled on the Relay). */\n token: Address;\n /** Orderly account owner — typically the user's EOA. */\n receiver: Address;\n /** Orderly broker hash (e.g. `BROKER_HASHES.woofi_pro`). */\n brokerHash: Hex;\n /** Total amount the user is sending to the Relay (raw token units, uint128). */\n totalAmount: bigint;\n /** Max acceptable token fee — slippage cap on the Relay's USD-pricing. */\n maxFee: bigint;\n}\n\nexport interface BuildPerpDepositViaRelayParams {\n /** User EOA (msg.sender via EIP-7702 delegation). */\n userAddress: Address;\n /** ERC-4337 account nonce. */\n aaNonce: bigint;\n /** Relay contract address — `getContractAddresses(chainId).orderlyRelay`. */\n relayAddress: Address;\n /** Deposit request (token, receiver, brokerHash, totalAmount, maxFee). */\n request: OrderlyRelayDepositRequest;\n\n /**\n * Optional USDC (input-token) gas-fee transfer prepended to the batch.\n * The user holds USDC at the start of the batch, so the fee comes out\n * of the same input token — no second-token requirement. Set both\n * `gasFeeUsdcRecipient` and `gasFeeUsdc` together for sponsored\n * flows (PAFI gas reimbursement). Pass `0n` / `undefined` for the\n * fallback path where the user pays ERC-4337 gas in ETH directly.\n *\n * Input-token fee position rule: user holds USDC BEFORE deposit\n * (token-availability), so charge there.\n */\n gasFeeUsdc?: bigint;\n gasFeeUsdcRecipient?: Address;\n\n gasLimits?: {\n callGasLimit?: bigint;\n verificationGasLimit?: bigint;\n preVerificationGas?: bigint;\n };\n}\n\n/**\n * Build a UserOp for Orderly perp deposit via the PAFI Relay.\n *\n * Sponsored ops: `[USDC.transfer(feeRecipient, gasFeeUsdc), USDC.approve(relay, total), Relay.deposit(req)]`\n * Fallback ops: `[ USDC.approve(relay, total), Relay.deposit(req)]`\n *\n * No `msg.value` — the Relay covers LayerZero out of its own ETH reserve.\n *\n * Fee position rule: user holds USDC at start of batch → fee transfer\n * runs FIRST in the same token. User must hold `totalAmount + gasFeeUsdc`\n * USDC; net deposit to Orderly is `totalAmount - relayTokenFee`.\n */\nexport function buildPerpDepositViaRelay(\n params: BuildPerpDepositViaRelayParams,\n): PartialUserOperation {\n if (params.request.totalAmount <= 0n) {\n throw new Error(\"buildPerpDepositViaRelay: totalAmount must be positive\");\n }\n if (params.request.maxFee < 0n) {\n throw new Error(\"buildPerpDepositViaRelay: maxFee cannot be negative\");\n }\n if (!params.relayAddress) {\n throw new Error(\"buildPerpDepositViaRelay: relayAddress required\");\n }\n\n const operations: Operation[] = [];\n\n // Optional USDC (input-token) gas fee transfer — sponsored path.\n // Position: BEFORE approve+deposit, because user holds USDC at this\n // point in the batch (token-availability rule).\n if (params.gasFeeUsdc && params.gasFeeUsdc > 0n) {\n if (!params.gasFeeUsdcRecipient) {\n throw new Error(\n \"buildPerpDepositViaRelay: gasFeeUsdcRecipient required when gasFeeUsdc > 0\",\n );\n }\n operations.push(\n erc20TransferOp(\n params.request.token,\n params.gasFeeUsdcRecipient,\n params.gasFeeUsdc,\n ),\n );\n }\n\n // USDC.approve(relay, totalAmount) — Relay pulls via transferFrom.\n operations.push(\n erc20ApproveOp(\n params.request.token,\n params.relayAddress,\n params.request.totalAmount,\n ),\n );\n\n // Relay.deposit(req) — Relay charges tokenFee, deposits the rest to Orderly.\n const depositCallData: Hex = encodeFunctionData({\n abi: ORDERLY_RELAY_ABI,\n functionName: \"deposit\",\n args: [\n {\n token: params.request.token,\n receiver: params.request.receiver,\n brokerHash: params.request.brokerHash,\n totalAmount: params.request.totalAmount,\n maxFee: params.request.maxFee,\n },\n ],\n });\n operations.push(rawCallOp(params.relayAddress, depositCallData));\n\n return buildPartialUserOperation({\n sender: params.userAddress,\n nonce: params.aaNonce,\n operations,\n gasLimits: {\n callGasLimit: params.gasLimits?.callGasLimit ?? 800_000n,\n verificationGasLimit: params.gasLimits?.verificationGasLimit ?? 150_000n,\n preVerificationGas: params.gasLimits?.preVerificationGas ?? 50_000n,\n },\n });\n}\n","import type { Address } from \"viem\";\nimport type { PartialUserOperation } from \"../userop/types\";\nimport { buildPartialUserOperation } from \"../userop/buildUserOperation\";\nimport { erc20TransferOp } from \"../userop/operations\";\n\n/**\n * Builder for the `erc20-transfer` sponsored scenario.\n *\n * Produces two UserOps from one request — same nonce, same sender:\n *\n * sponsored (2 calls):\n * [ token.transfer(PAFI_FEE_RECIPIENT, fee), token.transfer(recipient, amount) ]\n *\n * fallback (1 call):\n * [ token.transfer(recipient, amount) ]\n *\n * Both calls use the SAME token contract — the fee currency equals the\n * token being sent (decision documented in\n * `docs/SPONSORED_ERC20_TRANSFER_SPEC.md`). Caller is responsible for\n * validating the token is in the sponsorship allowlist; this function\n * just builds calldata, no policy enforcement.\n *\n * The sponsor-relayer's IntentValidator enforces:\n * - exactly 2 calls\n * - both target the same token address\n * - call[0] = transfer to PAFI_FEE_RECIPIENT, amount >= 95% quoted\n * - call[1] = transfer to recipient (not fee recipient, not 0x0)\n *\n * Fallback variant (1 call, no fee) submits no-paymaster — caller's\n * `sendWithPaymasterFallback` routes there when sponsorship is refused.\n */\nexport interface BuildErc20TransferParams {\n /** EIP-7702 delegated user EOA (== smart account sender). */\n userAddress: Address;\n /** ERC-4337 nonce from the EntryPoint. */\n aaNonce: bigint;\n /** Whitelisted ERC-20 to transfer. */\n tokenAddress: Address;\n /** Final recipient. Must NOT equal `feeRecipient` or `0x0...0`. */\n recipient: Address;\n /** Amount to send (token-native units; 6-dec for stables, 18-dec for PT). */\n amount: bigint;\n /**\n * Operator fee in the SAME token. Sponsored variant only — pass\n * `undefined` or 0n to skip and build the no-fee variant.\n */\n feeAmount?: bigint;\n /** Required when `feeAmount > 0`. */\n feeRecipient?: Address;\n /** Override gas limits if you have a tighter Pimlico estimate. */\n gasLimits?: {\n callGasLimit?: bigint;\n verificationGasLimit?: bigint;\n preVerificationGas?: bigint;\n };\n}\n\nexport function buildErc20TransferUserOp(\n params: BuildErc20TransferParams,\n): PartialUserOperation {\n if (params.amount <= 0n) {\n throw new Error(\"buildErc20TransferUserOp: amount must be positive\");\n }\n\n const operations = [];\n\n // Optional fee transfer (sponsored path) — same token as the user's\n // actual transfer, placed FIRST per token-availability convention\n // (matches the perp deposit + swap fee positioning).\n if (params.feeAmount && params.feeAmount > 0n) {\n if (!params.feeRecipient) {\n throw new Error(\n \"buildErc20TransferUserOp: feeRecipient required when feeAmount > 0\",\n );\n }\n operations.push(\n erc20TransferOp(\n params.tokenAddress,\n params.feeRecipient,\n params.feeAmount,\n ),\n );\n }\n\n operations.push(\n erc20TransferOp(params.tokenAddress, params.recipient, params.amount),\n );\n\n return buildPartialUserOperation({\n sender: params.userAddress,\n nonce: params.aaNonce,\n operations,\n gasLimits: {\n // 2 simple ERC-20 transfers + 7702 batch overhead. ~70-90k actual;\n // 200k matches the `delegate` scenario budget and covers premium.\n callGasLimit: params.gasLimits?.callGasLimit ?? 200_000n,\n verificationGasLimit:\n params.gasLimits?.verificationGasLimit ?? 150_000n,\n preVerificationGas: params.gasLimits?.preVerificationGas ?? 50_000n,\n },\n });\n}\n","import type { Address, Hex } from \"viem\";\nexport { ENTRY_POINT_V07, ENTRY_POINT_V08 } from \"../constants\";\n\n/**\n * A single call inside a batch. `BatchExecutor.execute(Call[])` iterates\n * and invokes each one. When the batch runs via EIP-7702 delegation,\n * `msg.sender` for each call is the user's EOA.\n */\nexport interface Operation {\n target: Address;\n value: bigint;\n data: Hex;\n}\n\n/**\n * Paymaster fields attached to a UserOperation. Populated by the\n * PAFI sponsor-relayer (via PAFI Backend proxy) before the user signs.\n */\nexport interface PaymasterFields {\n paymaster: Address;\n paymasterData: Hex;\n paymasterVerificationGasLimit: bigint;\n paymasterPostOpGasLimit: bigint;\n}\n\n/**\n * Partial UserOp used during preparation — before paymaster fields are\n * attached or the user signs.\n */\nexport interface PartialUserOperation {\n sender: Address;\n nonce: bigint;\n callData: Hex;\n callGasLimit: bigint;\n verificationGasLimit: bigint;\n preVerificationGas: bigint;\n maxFeePerGas: bigint;\n maxPriorityFeePerGas: bigint;\n}\n\n/**\n * Full ERC-4337 v0.7 UserOperation, ready for bundler submission.\n */\nexport interface UserOperation extends PartialUserOperation {\n paymaster: Address;\n paymasterData: Hex;\n paymasterVerificationGasLimit: bigint;\n paymasterPostOpGasLimit: bigint;\n signature: Hex;\n}\n\n/**\n * Receipt returned by a bundler after a UserOp lands on-chain.\n */\nexport interface UserOpReceipt {\n userOpHash: Hex;\n success: boolean;\n txHash: Hex;\n blockNumber: bigint;\n gasUsed: bigint;\n /** Effective gas cost paid (wei). */\n actualGasCost: bigint;\n}\n\n/**\n * Sentinel operation value used in tests + docs when `Operation.value`\n * is irrelevant (ERC-20 transfers, for example).\n */\nexport const ZERO_VALUE = 0n;\n","import type { Address, Hex } from \"viem\";\n\n/**\n * Serialize a fully assembled ERC-4337 v0.7 UserOperation into the\n * JSON-RPC wire format expected by `eth_sendUserOperation`.\n *\n * All numeric fields (nonce, gas limits, fees) are converted from bigint\n * to 0x-prefixed hex strings. Optional paymaster fields are included when\n * present, otherwise set to null.\n *\n * Use this on the **backend** (mobile submit path) right before calling\n * `PafiBackendClient.relayUserOperation()`.\n */\nexport function serializeUserOpToJsonRpc(\n userOp: {\n sender: Address;\n nonce: bigint;\n callData: Hex;\n callGasLimit: bigint;\n verificationGasLimit: bigint;\n preVerificationGas: bigint;\n maxFeePerGas: bigint;\n maxPriorityFeePerGas: bigint;\n paymaster?: Address;\n paymasterVerificationGasLimit?: bigint;\n paymasterPostOpGasLimit?: bigint;\n paymasterData?: Hex;\n },\n signature: Hex,\n): Record<string, string | null> {\n const p = userOp;\n return {\n sender: p.sender,\n nonce: `0x${p.nonce.toString(16)}`,\n factory: null,\n factoryData: null,\n callData: p.callData,\n callGasLimit: `0x${p.callGasLimit.toString(16)}`,\n verificationGasLimit: `0x${p.verificationGasLimit.toString(16)}`,\n preVerificationGas: `0x${p.preVerificationGas.toString(16)}`,\n maxFeePerGas: `0x${p.maxFeePerGas.toString(16)}`,\n maxPriorityFeePerGas: `0x${p.maxPriorityFeePerGas.toString(16)}`,\n paymaster: p.paymaster ?? null,\n paymasterData: p.paymasterData ?? null,\n paymasterVerificationGasLimit:\n p.paymasterVerificationGasLimit != null\n ? `0x${p.paymasterVerificationGasLimit.toString(16)}`\n : null,\n paymasterPostOpGasLimit:\n p.paymasterPostOpGasLimit != null\n ? `0x${p.paymasterPostOpGasLimit.toString(16)}`\n : null,\n signature,\n };\n}\n","import {\n concat,\n hashTypedData,\n pad,\n toHex,\n type Address,\n type Hex,\n type TypedDataDomain,\n} from \"viem\";\nimport { ENTRY_POINT_V08 } from \"./types\";\n\nconst PACKED_USER_OPERATION_TYPES = {\n PackedUserOperation: [\n { name: \"sender\", type: \"address\" },\n { name: \"nonce\", type: \"uint256\" },\n { name: \"initCode\", type: \"bytes\" },\n { name: \"callData\", type: \"bytes\" },\n { name: \"accountGasLimits\", type: \"bytes32\" },\n { name: \"preVerificationGas\", type: \"uint256\" },\n { name: \"gasFees\", type: \"bytes32\" },\n { name: \"paymasterAndData\", type: \"bytes\" },\n ],\n} as const;\n\nexport type PackedUserOperationMessage = {\n sender: Address;\n nonce: bigint;\n initCode: Hex;\n callData: Hex;\n accountGasLimits: Hex;\n preVerificationGas: bigint;\n gasFees: Hex;\n paymasterAndData: Hex;\n};\n\nexport type UserOpTypedData = {\n domain: TypedDataDomain;\n types: typeof PACKED_USER_OPERATION_TYPES;\n primaryType: \"PackedUserOperation\";\n message: PackedUserOperationMessage;\n};\n\n/**\n * Build the EIP-712 typed-data payload for an ERC-4337 v0.8 UserOp.\n *\n * The deployed Pimlico `Simple7702Account` (impl `0xe6Cae8...`) validates\n * the user's signature by calling `SignatureCheckerLib.isValidSignatureNow`\n * with the **raw** userOpHash (no EIP-191 prefix). For an EOA signer this\n * means `ecrecover(userOpHash, sig)` must equal the EOA address.\n *\n * Because `userOpHash` is itself the EIP-712 typed digest of the\n * `PackedUserOperation` struct, signing the typed data with\n * `eth_signTypedData_v4` produces a signature whose recover-from-raw-digest\n * == the userOpHash. That matches what the contract expects.\n *\n * Do NOT sign with `personal_sign` / `signMessage({ raw })` — that adds the\n * EIP-191 prefix, which the contract does not undo, so recovery returns a\n * different address and the bundler reverts with `AA24 signature error`.\n */\nexport function buildUserOpTypedData(\n userOp: {\n sender: Address;\n nonce: bigint;\n callData: Hex;\n callGasLimit: bigint;\n verificationGasLimit: bigint;\n preVerificationGas: bigint;\n maxFeePerGas: bigint;\n maxPriorityFeePerGas: bigint;\n paymaster?: Address;\n paymasterVerificationGasLimit?: bigint;\n paymasterPostOpGasLimit?: bigint;\n paymasterData?: Hex;\n },\n chainId: number,\n): UserOpTypedData {\n const accountGasLimits = pack128(\n userOp.verificationGasLimit,\n userOp.callGasLimit,\n );\n const gasFees = pack128(userOp.maxPriorityFeePerGas, userOp.maxFeePerGas);\n\n const paymasterAndData: Hex = userOp.paymaster\n ? concat([\n userOp.paymaster,\n pad(toHex(userOp.paymasterVerificationGasLimit ?? 0n), { size: 16 }),\n pad(toHex(userOp.paymasterPostOpGasLimit ?? 0n), { size: 16 }),\n (userOp.paymasterData ?? \"0x\") as Hex,\n ])\n : \"0x\";\n\n return {\n domain: {\n name: \"ERC4337\",\n version: \"1\",\n chainId,\n verifyingContract: ENTRY_POINT_V08,\n },\n types: PACKED_USER_OPERATION_TYPES,\n primaryType: \"PackedUserOperation\",\n message: {\n sender: userOp.sender,\n nonce: userOp.nonce,\n initCode: \"0x\" as Hex,\n callData: userOp.callData,\n accountGasLimits,\n preVerificationGas: userOp.preVerificationGas,\n gasFees,\n paymasterAndData,\n },\n };\n}\n\n/**\n * EIP-712 typed digest of an ERC-4337 v0.8 UserOp. Equals on-chain\n * `EntryPoint.getUserOpHash(userOp)`.\n */\nexport function computeUserOpHash(\n userOp: Parameters<typeof buildUserOpTypedData>[0],\n chainId: number,\n): Hex {\n const td = buildUserOpTypedData(userOp, chainId);\n return hashTypedData(td);\n}\n\nfunction pack128(hi: bigint, lo: bigint): Hex {\n return `0x${((hi << 128n) | lo).toString(16).padStart(64, \"0\")}` as Hex;\n}\n","import { type Address, type Hex, getAddress } from \"viem\";\n\n/**\n * EIP-7702 delegate impls supported by the PAFI mobile prepare/submit\n * flow. Each impl exposes `executeBatch((address,uint256,bytes)[])`\n * (selector `0x34fcd5be`) so callData encoding is identical, but each\n * may use a slightly different dummy-signature pattern that Pimlico's\n * `pm_sponsorUserOperation` simulation accepts.\n *\n * Current primary impl is `simple7702` (Pimlico's `Simple7702Account`).\n * `batchExecutor` is retained for back-compat with EOAs that delegated\n * to an earlier Coinbase Smart Wallet v2 BatchExecutor — Pimlico is\n * the canonical impl now (the Coinbase SignatureWrapper format caused\n * AA23 0x3c10b94e during validateUserOp).\n */\nexport type DelegateImpl =\n | \"simple7702\" // Pimlico's `to7702SimpleSmartAccount` impl — current\n | \"batchExecutor\" // Legacy Coinbase Smart Wallet v2 BatchExecutor — transitional\n | \"unknown\";\n\n/**\n * Pimlico's `Simple7702Account` implementation address on Base mainnet —\n * the canonical PAFI delegation target. Used by\n * `permissionless.to7702SimpleSmartAccount`.\n */\nexport const SIMPLE_7702_IMPL_BASE_MAINNET =\n \"0xe6Cae83BdE06E4c305530e199D7217f42808555B\" as Address;\n\n/**\n * Legacy Coinbase Smart Wallet v2 BatchExecutor — an earlier PAFI\n * delegation target. Same on Base mainnet + Base Sepolia. Detected here\n * so EOAs that delegated to this address still pass the impl check;\n * new delegations should target {@link SIMPLE_7702_IMPL_BASE_MAINNET}.\n */\nexport const BATCH_EXECUTOR_7702_IMPL =\n \"0x7702cb554e6bFb442cb743A7dF23154544a7176C\" as Address;\n\n/**\n * Standard ERC-4337 v0.7 ECDSA dummy signature — 65 bytes that\n * are well-formed (so signature recovery doesn't crash) but\n * obviously invalid (so they can't accidentally authorize a real\n * tx). Both Simple7702 and BatchExecutor accept this pattern as\n * the \"estimating\" signature during paymaster simulation.\n *\n * Source: viem-account-abstraction's default; matches what\n * `permissionless.toSimpleSmartAccount` and `to7702SimpleSmartAccount`\n * use for `getStubSignature()`.\n */\nexport const DUMMY_SIGNATURE_V07: Hex =\n \"0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c\";\n\n/**\n * Map an EIP-7702 delegate target address to its `DelegateImpl` kind.\n * Returns `'unknown'` when the address isn't a recognised PAFI-supported\n * impl — caller should reject or fall back to a default behaviour.\n */\nexport function detectDelegateImpl(delegate: Address | null | undefined): DelegateImpl {\n if (!delegate) return \"unknown\";\n const addr = getAddress(delegate).toLowerCase();\n if (addr === SIMPLE_7702_IMPL_BASE_MAINNET.toLowerCase()) return \"simple7702\";\n if (addr === BATCH_EXECUTOR_7702_IMPL.toLowerCase()) return \"batchExecutor\";\n return \"unknown\";\n}\n\n/**\n * Return a dummy signature appropriate for a given delegate impl. Used\n * by sponsor-relayer's `pm_sponsorUserOperation` forwarding so Pimlico\n * can simulate validateUserOp without the user's actual signature\n * (which is supplied later via `personal_sign(userOpHash)`).\n *\n * Currently both Simple7702 and BatchExecutor use the same v0.7 ECDSA\n * dummy. Kept impl-keyed so future impls (Safe, Kernel, Biconomy) can\n * supply their own pattern (e.g. ERC-1271 stubs) without forcing a\n * sponsor-relayer redeploy.\n */\nexport function getDummySignatureFor7702(impl: DelegateImpl): Hex {\n // Same dummy works for both impls — both use ECDSA recovery on\n // userOpHash, both accept the v0.7 standard stub.\n void impl;\n return DUMMY_SIGNATURE_V07;\n}\n","import type { Address, PublicClient } from \"viem\";\n\n/**\n * Submission path chosen by `checkEthAndBranch`.\n * - `normal`: initiator has enough ETH; submit via `walletClient.writeContract`\n * or a plain `eth_sendRawTransaction`. No paymaster round-trip.\n * - `paymaster`: initiator doesn't have enough ETH; wrap the batch as a\n * UserOperation and route through PAFI Backend → PAFI sponsor-relayer\n * → Bundler.\n */\nexport type SubmissionPath = \"normal\" | \"paymaster\";\n\nexport interface CheckEthAndBranchParams {\n /** viem PublicClient bound to the target chain. */\n client: PublicClient;\n /** The address whose ETH balance we check. */\n initiator: Address;\n /** Estimated gas cost in wei for the upcoming tx. */\n estimatedGasWei: bigint;\n /**\n * Optional safety margin multiplier (basis points). Defaults to\n * 11_000 (110%) — the initiator needs 10% above the estimate to\n * qualify for the normal path. Prevents edge cases where gas price\n * spikes between estimation and submission cause a \"has enough\"\n * decision to fail at broadcast time.\n */\n marginBps?: number;\n}\n\nconst DEFAULT_MARGIN_BPS = 11_000;\n\n/**\n * Step 3 of the Generalized Flow ([SPONSORED_PATH_FLOW.md §2]):\n * choose between the normal path (initiator pays ETH directly) and the\n * paymaster path (bundler + PAFI sponsor-relayer).\n *\n * Intentionally synchronous in spirit — the only network call is\n * `getBalance`. Callers can parallelize it with other reads.\n */\nexport async function checkEthAndBranch(\n params: CheckEthAndBranchParams,\n): Promise<SubmissionPath> {\n const marginBps = params.marginBps ?? DEFAULT_MARGIN_BPS;\n const required =\n (params.estimatedGasWei * BigInt(marginBps)) / 10_000n;\n\n const balance = await params.client.getBalance({\n address: params.initiator,\n });\n\n return balance >= required ? \"normal\" : \"paymaster\";\n}\n","import { concatHex, getAddress, getContractAddress, keccak256, pad, toHex } from \"viem\";\nimport type { Address, Hex } from \"viem\";\nimport type { V3Path } from \"../types\";\n\n/**\n * Encode a V3 swap path as packed bytes, oriented input→output:\n * `tokens[0] ‖ fees[0] ‖ tokens[1] ‖ fees[1] ‖ … ‖ tokens[N]`\n *\n * Each address is 20 bytes; each fee is uint24 (3 bytes). For an N-hop\n * swap the result is `(N + 1) * 20 + N * 3` bytes.\n *\n * For exact-output quoting the Quoter walks the path output→input —\n * reverse the underlying `tokens` and `fees` arrays before calling this\n * encoder (or use `encodeV3PathReversed`).\n *\n * @throws if `tokens.length !== fees.length + 1` or any input is malformed.\n */\nexport function encodeV3Path(path: V3Path): Hex {\n const { tokens, fees } = path;\n if (tokens.length < 2) {\n throw new Error(\n `encodeV3Path: tokens must contain at least 2 addresses, got ${tokens.length}`,\n );\n }\n if (tokens.length !== fees.length + 1) {\n throw new Error(\n `encodeV3Path: tokens.length must equal fees.length + 1, got ` +\n `tokens=${tokens.length} fees=${fees.length}`,\n );\n }\n\n // Normalize addresses to lowercase. The packed-bytes encoding is\n // case-insensitive on chain (Solidity address bytes carry no case),\n // and viem's `decodeAbiParameters({ type: \"bytes\" }, ...)` always\n // returns lowercase hex — keeping inputs lowercase here makes\n // round-trip comparisons exact equality without case folding.\n const parts: Hex[] = [];\n for (let i = 0; i < fees.length; i++) {\n parts.push(tokens[i]!.toLowerCase() as Hex);\n const fee = fees[i]!;\n if (!Number.isInteger(fee) || fee < 0 || fee > 0xff_ff_ff) {\n throw new Error(\n `encodeV3Path: fees[${i}] must be a uint24 (0..16777215), got ${fee}`,\n );\n }\n parts.push(pad(toHex(fee), { size: 3 }));\n }\n parts.push(tokens[tokens.length - 1]!.toLowerCase() as Hex);\n\n return concatHex(parts);\n}\n\n/**\n * Encode the same path reversed (output→input). Convenience used by\n * exact-output quoting and exact-output swap-builder paths.\n */\nexport function encodeV3PathReversed(path: V3Path): Hex {\n return encodeV3Path({\n tokens: [...path.tokens].reverse(),\n fees: [...path.fees].reverse(),\n });\n}\n\n/**\n * Compute a V3 pool address deterministically from factory + initCodeHash.\n *\n * Uses the standard Uniswap V3 derivation:\n * `keccak256(0xff ‖ factory ‖ keccak256(abi.encode(token0, token1, fee)) ‖ initCodeHash)`\n *\n * Tokens are sorted ascending — the helper accepts them in any order.\n * Returns a checksummed address.\n */\nexport function computeV3PoolAddress(params: {\n factory: Address;\n tokenA: Address;\n tokenB: Address;\n fee: number;\n initCodeHash: Hex;\n}): Address {\n const { factory, tokenA, tokenB, fee, initCodeHash } = params;\n if (!Number.isInteger(fee) || fee < 0 || fee > 0xff_ff_ff) {\n throw new Error(\n `computeV3PoolAddress: fee must be a uint24, got ${fee}`,\n );\n }\n\n const a = getAddress(tokenA).toLowerCase();\n const b = getAddress(tokenB).toLowerCase();\n if (a === b) {\n throw new Error(\n `computeV3PoolAddress: tokenA and tokenB must differ, got ${a}`,\n );\n }\n const [token0, token1] = a < b ? [tokenA, tokenB] : [tokenB, tokenA];\n\n // keccak256(abi.encode(token0, token1, fee)) — the CREATE2 salt.\n const salt = keccak256(\n concatHex([\n pad(getAddress(token0), { size: 32 }),\n pad(getAddress(token1), { size: 32 }),\n pad(toHex(fee), { size: 32 }),\n ]),\n );\n\n return getContractAddress({\n opcode: \"CREATE2\",\n from: factory,\n bytecodeHash: initCodeHash,\n salt,\n });\n}\n","import type { Address, Hex, PublicClient } from \"viem\";\n\n/**\n * EIP-7702 delegation designator prefix: 0xef0100 + 20-byte implementation address.\n *\n * When an EOA has been delegated via EIP-7702, `eth_getCode` returns bytecode\n * that starts with this magic prefix followed by the 20-byte address of the\n * implementation contract (BatchExecutor).\n *\n * References: EIP-7702 §4.2 — \"delegation designator\"\n */\nconst EIP7702_MAGIC = \"0xef0100\" as const;\n\n/**\n * Parse the implementation address out of an EIP-7702 delegation designator.\n *\n * Returns `null` when:\n * - `code` is undefined / empty / `\"0x\"` (plain EOA, no code)\n * - `code` does not contain the EIP-7702 magic prefix (regular contract)\n *\n * @param code - bytecode returned by `eth_getCode` / `client.getCode()`\n * @returns the 20-byte implementation address (checksummed), or `null`\n *\n * @example\n * const code = await client.getCode({ address });\n * const impl = parseEip7702DelegatedAddress(code);\n * // null → not delegated\n * // '0x7702cb554e6bFb442cb743A7dF23154544a7176C' → delegated to BatchExecutor\n */\nexport function parseEip7702DelegatedAddress(\n code: Hex | string | null | undefined,\n): Address | null {\n if (!code || code === \"0x\" || code === \"0x0\") return null;\n const normalized = code.toLowerCase();\n const magic = EIP7702_MAGIC.toLowerCase();\n // Strict prefix + exact-length check per EIP-7702 §4.2. Bytecode of\n // a delegated EOA is exactly 23 bytes (`0xef0100` || 20-byte impl)\n // → 40 hex chars AFTER the magic prefix. Substring search would\n // false-positive on regular contract bytecode containing `ef0100`\n // mid-stream.\n if (!normalized.startsWith(magic)) return null;\n if (normalized.length !== magic.length + 40) return null;\n return `0x${normalized.slice(magic.length)}` as Address;\n}\n\n/**\n * Read the EIP-7702 delegation status of an EOA from the chain.\n *\n * @param client - viem PublicClient (any provider — only `eth_getCode` is called)\n * @param address - EOA address to inspect\n * @returns the implementation address the EOA is delegated to, or `null`\n *\n * @example\n * const impl = await checkDelegation(publicClient, userAddress);\n * if (impl === null) {\n * // show \"Setup Wallet\" button\n * } else {\n * // EOA already delegated; safe to send UserOps\n * }\n */\nexport async function checkDelegation(\n client: PublicClient,\n address: Address,\n): Promise<Address | null> {\n const code = await client.getCode({ address });\n return parseEip7702DelegatedAddress(code);\n}\n\n/**\n * Return `true` when the EOA at `address` is currently delegated to `target`.\n *\n * Useful for asserting that the delegation points specifically at the expected\n * BatchExecutor rather than some other implementation (e.g. old deployment).\n *\n * @example\n * import { BATCH_EXECUTOR_ADDRESS_BASE_MAINNET } from '@pafi-dev/core';\n * const ok = await isDelegatedTo(client, userAddress, BATCH_EXECUTOR_ADDRESS_BASE_MAINNET);\n */\nexport async function isDelegatedTo(\n client: PublicClient,\n address: Address,\n target: Address,\n): Promise<boolean> {\n const impl = await checkDelegation(client, address);\n if (!impl) return false;\n return impl.toLowerCase() === target.toLowerCase();\n}\n","import type { Address, PublicClient } from \"viem\";\nimport type { PartialUserOperation } from \"../userop/types\";\nimport { buildPartialUserOperation } from \"../userop/buildUserOperation\";\nimport { ENTRY_POINT_V08 } from \"../constants\";\n\n/**\n * Parameters for building a delegation-only UserOperation.\n *\n * This UserOp carries no calldata — its sole purpose is to anchor the\n * EIP-7702 authorization (signed externally via `signAuthorization`) into\n * a sponsored transaction so the user doesn't need native ETH to delegate.\n */\nexport interface BuildDelegationUserOpParams {\n /** User EOA to delegate. */\n userAddress: Address;\n /** ERC-4337 account nonce — fetched from EntryPoint. Pass 0n on first ever op. */\n aaNonce: bigint;\n gasLimits?: {\n callGasLimit?: bigint;\n verificationGasLimit?: bigint;\n preVerificationGas?: bigint;\n };\n}\n\n/**\n * Build the minimal `PartialUserOperation` used to sponsor the one-time\n * EIP-7702 delegation.\n *\n * The caller must:\n * 1. Sign the EIP-7702 authorization via `signAuthorization()` (Privy hook).\n * 2. Pass the authorization as `authorization` alongside `calls` (or alone)\n * to `smartClient.sendTransaction()`.\n *\n * The permissionless SDK + Pimlico bundler handle the rest: they detect the\n * `authorization` field and submit an EIP-7702 type-4 transaction that sets\n * the EOA's bytecode to `0xef0100<batchExecutorAddress>`.\n *\n * This builder is a convenience wrapper — in practice you can also pass\n * `{ to: userAddress, value: 0n, data: '0x', authorization }` directly to\n * `smartClient.sendTransaction()` without calling this function at all.\n * Use it when you need a `PartialUserOperation` struct for inspection or\n * custom paymaster flows.\n *\n * @example\n * // Typical flow — no need to call this builder directly:\n * const nonce = await publicClient.getTransactionCount({ address, blockTag: 'pending' });\n * const authorization = await signAuthorization({ contractAddress: BATCH_EXECUTOR, chainId: 8453, nonce });\n * const txHash = await smartClient.sendTransaction({\n * to: address,\n * value: 0n,\n * data: '0x',\n * authorization,\n * paymasterContext: { sponsorshipPolicyId },\n * });\n */\nexport function buildDelegationUserOp(\n params: BuildDelegationUserOpParams,\n): PartialUserOperation {\n return buildPartialUserOperation({\n sender: params.userAddress,\n nonce: params.aaNonce,\n operations: [\n {\n // Self-call with no data — triggers EIP-7702 delegation without\n // executing any inner logic. The BatchExecutor.execute([]) call with\n // an empty array would revert, so we target the EOA itself (which\n // forwards to BatchExecutor that then no-ops on empty input).\n target: params.userAddress,\n value: 0n,\n data: \"0x\",\n },\n ],\n gasLimits: {\n callGasLimit: params.gasLimits?.callGasLimit ?? 50_000n,\n verificationGasLimit: params.gasLimits?.verificationGasLimit ?? 150_000n,\n preVerificationGas: params.gasLimits?.preVerificationGas ?? 50_000n,\n },\n });\n}\n\n/**\n * Fetch the AA nonce for a user EOA from the EntryPoint v0.7.\n *\n * Convenience helper so callers don't need to import the EntryPoint ABI\n * themselves when building the delegation UserOp.\n *\n * @param client - viem PublicClient\n * @param userAddress - EOA to query\n * @returns bigint nonce (0n on first-ever UserOp)\n */\nexport async function getAaNonce(\n client: PublicClient,\n userAddress: Address,\n): Promise<bigint> {\n const NONCE_ABI = [\n {\n inputs: [\n { name: \"sender\", type: \"address\" },\n { name: \"key\", type: \"uint192\" },\n ],\n name: \"getNonce\",\n outputs: [{ name: \"nonce\", type: \"uint256\" }],\n stateMutability: \"view\",\n type: \"function\",\n },\n ] as const;\n\n return client.readContract({\n address: ENTRY_POINT_V08,\n abi: NONCE_ABI,\n functionName: \"getNonce\",\n args: [userAddress, 0n],\n });\n}\n","import { concat, keccak256, toRlp, type Address, type Hex } from \"viem\";\n\n/**\n * EIP-7702 authorization tuple hash.\n *\n * keccak256(0x05 || rlp([chain_id, address, nonce]))\n *\n * The user signs this hash with a **raw secp256k1 sign** (no EIP-191 prefix).\n * On mobile, pass the result to the wallet's raw signing primitive.\n *\n * The resulting 65-byte signature is sent to `POST /delegate/submit` as `authSig`.\n *\n * Reference: EIP-7702 §3 — Authorization tuple\n */\nexport function computeAuthorizationHash(\n chainId: number,\n address: Address,\n nonce: bigint,\n): Hex {\n const rlpEncoded = toRlp([\n toMinimalHex(BigInt(chainId)),\n address,\n toMinimalHex(nonce),\n ]);\n return keccak256(concat([\"0x05\", rlpEncoded]));\n}\n\n/**\n * Check whether an EOA code string carries an EIP-7702 delegation to `target`.\n *\n * Sync alternative to `isDelegatedTo()` for callers that already have the\n * code from a prior `eth_getCode` call and don't want another RPC round-trip.\n *\n * Delegation designator format: 0xef0100 (3 bytes) || address (20 bytes)\n */\nexport function isDelegatedToTarget(\n code: string | null | undefined,\n target: Address,\n): boolean {\n if (!code || code === \"0x\") return false;\n const expected = `0xef0100${target.slice(2).toLowerCase()}`;\n return code.toLowerCase() === expected;\n}\n\n/** Minimal-length big-endian hex for RLP integer encoding (no leading zeros). */\nfunction toMinimalHex(n: bigint): Hex {\n if (n === 0n) return \"0x\";\n const hex = n.toString(16);\n return `0x${hex.length % 2 === 0 ? hex : \"0\" + hex}` as Hex;\n}\n","import type { Address, Hex } from \"viem\";\n\n/**\n * EIP-7702 authorization tuple in the JSON-RPC wire format that bundlers\n * (Pimlico, Stackup, etc.) accept on `eth_sendUserOperation`. All\n * numeric fields are 0x-prefixed hex strings.\n *\n * Constructed from the user's secp256k1 signature over\n * `keccak256(0x05 || rlp([chainId, address, nonce]))` — see\n * `computeAuthorizationHash`.\n */\nexport interface Eip7702AuthorizationJsonRpc {\n chainId: Hex;\n address: Address;\n nonce: Hex;\n r: Hex;\n s: Hex;\n yParity: Hex;\n}\n\n/**\n * Split a serialized 65-byte ECDSA signature (`r || s || v`) into the\n * `{ r, s, yParity }` triple required by EIP-7702 authorization tuples\n * and EIP-1559+ transactions.\n *\n * Accepts the legacy `v ∈ {27, 28}` form and converts to `yParity ∈ {0, 1}`\n * automatically. Already-normalized `v ∈ {0, 1}` passes through unchanged.\n *\n * @throws when the signature is not 65 bytes (130 hex chars).\n */\nexport function splitAuthorizationSig(authSig: Hex | string): {\n r: Hex;\n s: Hex;\n yParity: 0 | 1;\n} {\n const raw = authSig.startsWith(\"0x\")\n ? authSig.slice(2)\n : authSig;\n if (raw.length !== 130) {\n throw new Error(\n `splitAuthorizationSig: expected 65-byte signature (130 hex chars), got ${raw.length}`,\n );\n }\n const r = `0x${raw.slice(0, 64)}` as Hex;\n const s = `0x${raw.slice(64, 128)}` as Hex;\n const v = parseInt(raw.slice(128, 130), 16);\n const yParity: 0 | 1 = v === 27 ? 0 : v === 28 ? 1 : (v as 0 | 1);\n if (yParity !== 0 && yParity !== 1) {\n throw new Error(\n `splitAuthorizationSig: invalid recovery byte ${v} (expected 0/1/27/28)`,\n );\n }\n return { r, s, yParity };\n}\n\n/**\n * Build the JSON-RPC EIP-7702 authorization tuple from raw fields and a\n * 65-byte secp256k1 signature.\n *\n * The `authSig` is the user's signature over `computeAuthorizationHash(\n * chainId, address, nonce )` — typically obtained via\n * `wallet.signAuthorization(...)` on the client.\n */\nexport function buildEip7702Authorization(params: {\n chainId: number;\n address: Address;\n nonce: bigint;\n authSig: Hex | string;\n}): Eip7702AuthorizationJsonRpc {\n const { r, s, yParity } = splitAuthorizationSig(params.authSig);\n return {\n chainId: `0x${params.chainId.toString(16)}` as Hex,\n address: params.address,\n nonce: `0x${params.nonce.toString(16)}` as Hex,\n r,\n s,\n yParity: `0x${yParity}` as Hex,\n };\n}\n","import type { Address } from \"viem\";\n\n/**\n * Per-chain deployed contract addresses.\n *\n * Base mainnet (8453): live deployment. Fee on mint happens via\n * `MintFeeWrapper` (single global instance, multi-PT).\n *\n * Base Sepolia (84532): entire row is placeholder — not in active use.\n *\n * ## What lives where\n *\n * batchExecutor — EIP-7702 delegation target (Pimlico Simple7702Account)\n * usdt — Stablecoin used by Uniswap pools (6 decimals)\n * usdc — Stablecoin used as the perp-deposit fee token and\n * Orderly settlement asset (6 decimals). Optional —\n * may be undefined on chains without a canonical USDC.\n * issuerRegistry — registry of all issuers on this chain\n * mintingOracle — per-token mint cap enforcer\n * mintFeeWrapper — mint-time fee splitter (single global instance)\n * chainlinkEthUsd — ETH/USD price feed used by FeeManager\n * orderlyRelay — Orderly perp-deposit Relay\n * pafiFeeRecipient — PAFI-controlled fee recipient (sponsored gas reimbursement)\n * universalRouter — PAFI v3 fork UniversalRouter\n * permit2 — PAFI fork Permit2 (paired with the PAFI UR above)\n *\n * PointToken addresses are per-issuer and resolved from the issuer's\n * config (env / DB / PointTokenFactory readout). PointTokenFactory and\n * the PointToken implementation live in separate exports below.\n *\n * Fee logic lives in `MintFeeWrapper` on the mint path; swaps are\n * hook-free (`hooks = address(0)`).\n */\nexport interface ContractAddresses {\n batchExecutor: Address;\n usdt: Address;\n /**\n * Canonical USDC (6 decimals) on this chain — the fee token used by\n * the perp-deposit scenario (Orderly settlement asset) and accepted\n * by sponsor-relayer's stable fee path. Optional because not every\n * chain has a recognised canonical USDC.\n */\n usdc?: Address;\n issuerRegistry: Address;\n mintingOracle: Address;\n /**\n * Single-instance MintFeeWrapper that skims a fee on every sig-gated\n * mint and distributes across the registered recipient list for the\n * target PointToken. Issuers route mints through this by passing\n * `mintFeeWrapperAddress` to `prepareMint`.\n */\n mintFeeWrapper: Address;\n /** Chainlink ETH/USD price feed — used by FeeManager to convert gas cost to USDT. */\n chainlinkEthUsd: Address;\n /**\n * Chainlink USDC/USD price feed — paired with the canonical USDC.\n * Optional because not every chain ships a USDC oracle; FeeManager\n * falls back to 1:1 (USDC pegged) when undefined.\n */\n chainlinkUsdcUsd?: Address;\n /**\n * Orderly perp-deposit Relay — holds an ETH reserve to cover the\n * LayerZero msg.value, charges a USDC token-fee reimbursement.\n * Lets perp deposits ride the ERC-4337 sponsored gas path without\n * the user holding ETH for msg.value.\n */\n orderlyRelay: Address;\n /**\n * PAFI fee recipient — receives PT gas-reimbursement transfers from\n * the user on the sponsored path of every scenario (mint, burn,\n * swap, perp deposit). This is PAFI-controlled, NOT issuer-controlled,\n * because PAFI pays the ERC-4337 gas via the paymaster — issuers\n * shouldn't be able to redirect this fee to themselves.\n */\n pafiFeeRecipient: Address;\n /** UniversalRouter — swap entry point for the swap handler. */\n universalRouter: Address;\n /**\n * PAFI fork of Uniswap's Permit2. Acts as the unified token-approval\n * authority for UniversalRouter swaps and any other PAFI-paired flow\n * that needs gasless allowances. Differs from Uniswap canonical\n * Permit2 (`0x000000000022D473...`) — must use the PAFI fork because\n * the fork UR was deployed against this Permit2 instance. Keep in\n * sync with `PERMIT2_ADDRESS` top-level export.\n */\n permit2: Address;\n // ──────────────────────────────────────────────────────────────────\n // V2 dual-bucket additions (deploy 2026-06-12)\n //\n // The V2 deploy introduces a layered registry architecture:\n // - `tokenRegistry` indexes PointToken metadata + active state\n // - `vaultRegistry` tracks ERC-4626 SettlementVaults per PointToken\n // (VAULT bucket — not yet exposed in the SDK writer surface)\n // - `vaultFactory` deploys SettlementVault clones\n // - `pointModuleCore` is the top-level coordinator that the rest\n // of the system reads from for cross-registry lookups\n // ──────────────────────────────────────────────────────────────────\n /** V2: TokenRegistry — per-PointToken metadata + active flag. */\n tokenRegistry: Address;\n /**\n * V2: VaultRegistry — per-PointToken SettlementVault registration.\n * Read-only from the SDK's perspective today (issuer-equity-only\n * launch); becomes write-side relevant when the LP vault flow lands.\n */\n vaultRegistry: Address;\n /** V2: VaultFactory — deploys SettlementVault UUPS-beacon clones. */\n vaultFactory: Address;\n /**\n * V2: PointModuleCore — top-level coordinator. Holds references to\n * the IssuerRegistry, TokenRegistry, VaultRegistry, MintingOracle so\n * consumers can resolve the full registry set from one address.\n */\n pointModuleCore: Address;\n}\n\nconst PLACEHOLDER_DEAD = (suffix: string): Address =>\n `0x000000000000000000000000000000000000${suffix.toLowerCase().padStart(4, \"0\")}` as Address;\n\nexport const CONTRACT_ADDRESSES: Record<number, ContractAddresses> = {\n // ──────────────────────────────────────────────────────────────────\n // Base mainnet (8453) — V2 dual-bucket deploy 2026-06-12\n //\n // Replaces the v1.6 single-bucket deploy (2026-05-07). Periphery\n // addresses (batchExecutor / chainlinkEthUsd / orderlyRelay /\n // universalRouter / permit2 / pafiFeeRecipient) are inherited from\n // v1.6 — they were not redeployed for V2. PointToken impl + factory\n // were rotated as part of the dual-bucket schema change.\n // ──────────────────────────────────────────────────────────────────\n 8453: {\n // ── Periphery (inherited from v1.6, unchanged) ────────────────\n batchExecutor: \"0xe6Cae83BdE06E4c305530e199D7217f42808555B\",\n chainlinkEthUsd: \"0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70\",\n orderlyRelay: \"0xDA082DAce1522c185aeB5A713FcA6fa6B6E99e7f\",\n pafiFeeRecipient: \"0xa3F71eadEd101513a0151007590020dCFD7C495e\",\n // PAFI fork UniversalRouter — DIFFERS from Uniswap canonical Base UR\n // (`0x6fF5693b...`). The PAFI fork has its own factory + poolInitCodeHash,\n // so only this UR can route through PAFI v3 fork pools. Keep in sync with\n // `UNIVERSAL_ROUTER_ADDRESSES` in `chains.ts`.\n universalRouter: \"0x008887C992A5bDC24097E717Bfb71CE89483c5A2\",\n // PAFI fork Permit2 — DIFFERS from Uniswap canonical Permit2\n // (`0x000000000022D473...`). PAFI's UR was deployed against this\n // Permit2, so swaps signed against the canonical Permit2 fail\n // signature verification. Keep in sync with `PERMIT2_ADDRESS`\n // top-level export.\n permit2: \"0xEB450d21ae68D3303Cf5775A54Cc84EE7c3fC8eC\",\n // ── Stablecoins ───────────────────────────────────────────────\n // USDT inherited from v1.6 (MockUSDT — no new V2 USDT deploy).\n usdt: \"0x3F7e71B150e97316Bb9f363A32c19CcD36ac2382\",\n // V2 USDC — PAFI ecosystem USDC (custom deploy, NOT canonical Base\n // USDC `0x833589fC...`). Paired with the USDC/USD Chainlink feed below.\n usdc: \"0xf0Fa9eB05fd3a373d3c54775Ff0ed55Dc9cc2D3E\",\n chainlinkUsdcUsd: \"0xEE86BfD4E2B3A1e71a1b45f750791D67e735e4a7\",\n // ── V2 core registries (replaces v1.6) ────────────────────────\n issuerRegistry: \"0x3e82647b0f716f80e65d311354E2C4F0DcFd6997\",\n mintingOracle: \"0x19F5Ea31D42E957F66B672c83293588f190f9fEB\",\n mintFeeWrapper: \"0x72B8FB182A72741d2F85Bd78B5cAAC8b98c5A493\",\n tokenRegistry: \"0x7092b329a5c9a823EE0b629D391330D29E109d97\",\n vaultRegistry: \"0x8D97352cEaA6d6C1Ee2ddFd327F1487357650712\",\n vaultFactory: \"0xDaD6b27Be7d89A6776b85D0672e0B645318DE63b\",\n pointModuleCore: \"0x3995fE8ED9dc563C2d7268fCA86d08cc1A2C1c9c\",\n },\n // Base Sepolia — not in active use; placeholders kept so the map\n // compiles for tooling that enumerates chains.\n 84532: {\n batchExecutor: PLACEHOLDER_DEAD(\"de01\"),\n usdt: PLACEHOLDER_DEAD(\"dead\"),\n issuerRegistry: PLACEHOLDER_DEAD(\"dead\"),\n mintingOracle: PLACEHOLDER_DEAD(\"dead\"),\n mintFeeWrapper: PLACEHOLDER_DEAD(\"dead\"),\n chainlinkEthUsd: PLACEHOLDER_DEAD(\"de02\"),\n orderlyRelay: PLACEHOLDER_DEAD(\"de03\"),\n pafiFeeRecipient: PLACEHOLDER_DEAD(\"de04\"),\n universalRouter: PLACEHOLDER_DEAD(\"de05\"),\n permit2: PLACEHOLDER_DEAD(\"de06\"),\n tokenRegistry: PLACEHOLDER_DEAD(\"de07\"),\n vaultRegistry: PLACEHOLDER_DEAD(\"de08\"),\n vaultFactory: PLACEHOLDER_DEAD(\"de09\"),\n pointModuleCore: PLACEHOLDER_DEAD(\"de0a\"),\n },\n};\n\n/**\n * PointTokenFactory address — separate from `ContractAddresses` because\n * it's only used at provisioning time (issuer onboard + token creation),\n * not in the hot mint/burn path.\n */\nexport const POINT_TOKEN_FACTORY_ADDRESSES: Record<number, Address> = {\n // V2 dual-bucket factory (2026-06-12). Replaces v1.6 factory\n // `0xA08274458b43E7D6F4ff61ddFe8A9852c6531085`.\n 8453: \"0x34f9F84841A77A19040686396b8B64522A2da4c8\",\n 84532: PLACEHOLDER_DEAD(\"dead\"),\n};\n\n/**\n * PointToken implementation address — the clone target used by\n * PointTokenFactory. V2 switched from EIP-1167 minimal proxy to a\n * UUPS-beacon pattern (see `POINT_TOKEN_BEACON_ADDRESSES`); each clone\n * delegates through the beacon to this impl. Exposed for observability\n * / proxy verification; consumers should never call this directly.\n */\nexport const POINT_TOKEN_IMPL_ADDRESSES: Record<number, Address> = {\n // V2 dual-bucket impl (2026-06-12). Replaces v1.6 impl\n // `0xc41c3F8A0380c7760Ee1209d6d19C4b81dE994e4` (single-bucket).\n 8453: \"0x067d2d82F7cfbAf9bBA1f493167cC6043eD5d7dd\",\n 84532: PLACEHOLDER_DEAD(\"dead\"),\n};\n\n/**\n * V2: PointToken UUPS beacon — clones point at this beacon; the beacon\n * delegates to the impl. Upgrading the impl is done by re-pointing the\n * beacon. Exposed for upgrade audits and observability; runtime SDK\n * paths never reference it directly.\n */\nexport const POINT_TOKEN_BEACON_ADDRESSES: Record<number, Address> = {\n 8453: \"0xdC479E294FD12658FDC68B9400c2073De291acf3\",\n 84532: PLACEHOLDER_DEAD(\"dead\"),\n};\n\n/**\n * V2: SettlementVault UUPS beacon — same pattern as the PointToken\n * beacon, for the per-PointToken ERC-4626 LP vault deployed by\n * VaultFactory. Exposed for upgrade audits.\n */\nexport const VAULT_BEACON_ADDRESSES: Record<number, Address> = {\n 8453: \"0x4E583d64bfcCD86dFf1f68b792D5a1Cd169f22eB\",\n 84532: PLACEHOLDER_DEAD(\"dead\"),\n};\n\n/**\n * Lookup helper — throws if the chain isn't in the map so callers fail\n * loudly on misconfiguration instead of silently using `undefined`.\n */\nexport function getContractAddresses(chainId: number): ContractAddresses {\n const addrs = CONTRACT_ADDRESSES[chainId];\n if (!addrs) {\n throw new Error(\n `getContractAddresses: no addresses for chainId ${chainId}. ` +\n `Supported: ${Object.keys(CONTRACT_ADDRESSES).join(\", \")}`,\n );\n }\n return addrs;\n}\n","import type {\n Address,\n Hex,\n PublicClient,\n WalletClient,\n TransactionReceipt,\n} from \"viem\";\nimport { getContractAddresses } from \"../contracts/real/addresses\";\nimport { parseEip7702DelegatedAddress } from \"./checkDelegation\";\n\n/**\n * Privy-style EIP-7702 authorization signer. Matches the shape returned\n * by `useSign7702Authorization()` from `@privy-io/react-auth` /\n * `@privy-io/expo`. Pass it via `params.signAuthorization` and the\n * helper takes care of the rest.\n *\n * The signer MUST be the user's Privy embedded wallet — external\n * wallets (MetaMask, WalletConnect, …) cannot produce raw secp256k1\n * EIP-7702 authorizations.\n */\nexport type SignAuthorizationFn = (args: {\n contractAddress: Address;\n chainId: number;\n nonce: number;\n}) => Promise<{\n contractAddress?: Address;\n address?: Address;\n chainId: number;\n nonce: number;\n r: Hex | string;\n s: Hex | string;\n // Privy returns `v` as bigint, viem types it as bigint, some SDKs as\n // string/number. Accept all and ignore — we use yParity directly.\n v?: bigint | string | number;\n // Privy returns 0 | 1 (number); some SDKs return '0x0' / '0x1' (hex\n // string). Accept both shapes — `delegateDirect` normalizes\n // internally.\n yParity: number | string;\n}>;\n\n/**\n * Authorization tuple in the shape `walletClient.sendTransaction`\n * expects on its `authorizationList` field. Mirrors viem's\n * `SignedAuthorization` so callers can also build it manually.\n */\nexport interface SignedAuthorization {\n contractAddress: Address;\n chainId: number;\n nonce: number;\n r: Hex;\n s: Hex;\n yParity: 0 | 1;\n}\n\nexport interface DelegateDirectParams {\n /** User EOA — must equal `walletClient.account.address`. */\n userAddress: Address;\n chainId: number;\n /** viem PublicClient — used for `getCode` + `getTransactionCount`. */\n publicClient: PublicClient;\n /** viem WalletClient (or any sender that exposes `sendTransaction`). */\n walletClient: WalletClient;\n /**\n * Privy hook that produces the EIP-7702 authorization signature.\n * Pass `useSign7702Authorization().signAuthorization` directly.\n */\n signAuthorization: SignAuthorizationFn;\n /**\n * Override the impl the EOA delegates to. Defaults to\n * `getContractAddresses(chainId).batchExecutor` (Pimlico\n * Simple7702Account on Base mainnet — the canonical PAFI delegate\n * target).\n */\n contractAddress?: Address;\n /**\n * When the user already has a 7702 delegation pointing at the\n * expected impl, skip the tx and return early. Default `true`.\n * Set `false` to force re-delegate even if status check passes.\n */\n skipIfAlreadyDelegated?: boolean;\n /**\n * Wait for the transaction receipt before returning. Default\n * `true` — caller usually wants to know \"delegation completed\"\n * before proceeding to claim/redeem flows.\n */\n waitForReceipt?: boolean;\n /** Optional onWarning hook for non-fatal warnings (logger surface). */\n onWarning?: (msg: string) => void;\n}\n\nexport interface DelegateDirectResult {\n /** `'sent'` when a tx was broadcast; `'already-delegated'` when skipped. */\n status: \"sent\" | \"already-delegated\";\n /** Transaction hash. `undefined` on `already-delegated`. */\n txHash?: Hex;\n /** Receipt — present when `waitForReceipt` AND status is `'sent'`. */\n receipt?: TransactionReceipt;\n /** EIP-7702 authorization tuple actually sent. */\n authorization: SignedAuthorization;\n /** Impl address user delegated to. */\n delegatedTo: Address;\n}\n\n/**\n * One-shot helper for the FE-direct EIP-7702 delegation path —\n * **no AA, no paymaster, no PAFI sponsor-relayer**. The user EOA\n * pays gas in ETH and broadcasts a single type-4 transaction with the\n * authorization attached.\n *\n * Use this when:\n * - The FE already has a Privy embedded wallet ready and the user\n * has a small ETH balance for gas (~$0.01–0.10 on Base).\n * - You don't want to depend on `permissionless` / Pimlico bundlers\n * / sponsor-relayer for the one-time delegation step.\n * - You're testing or running a self-hosted dev environment without\n * the full PAFI infra.\n *\n * Flow:\n * 1. Read on-chain code; short-circuit if already delegated to the\n * expected impl (`skipIfAlreadyDelegated`).\n * 2. Read EOA tx nonce (pending).\n * 3. Call `signAuthorization` (Privy hook) → r/s/yParity.\n * 4. Send EIP-7702 type-4 tx with `authorizationList: [auth]` and\n * `to: userAddress, data: '0x'` (no-op self-call; the work is\n * bundling the authorization).\n * 5. Wait for receipt (optional).\n *\n * Caller's `signAuthorization` MUST be wired to the user's Privy\n * embedded wallet — external wallets (MetaMask, …) do NOT support\n * raw secp256k1 EIP-7702 sign.\n *\n * @example\n * ```ts\n * import { useSign7702Authorization, useWallets } from \"@privy-io/react-auth\";\n * import { delegateDirect } from \"@pafi-dev/core\";\n * import { createWalletClient, custom } from \"viem\";\n * import { base } from \"viem/chains\";\n *\n * function DelegateButton() {\n * const { wallets } = useWallets();\n * const { signAuthorization } = useSign7702Authorization();\n * const wallet = wallets.find(w => w.walletClientType === \"privy\"); // embedded\n *\n * async function handleClick() {\n * const provider = await wallet.getEthereumProvider();\n * const walletClient = createWalletClient({\n * account: wallet.address,\n * chain: base,\n * transport: custom(provider),\n * });\n *\n * const result = await delegateDirect({\n * userAddress: wallet.address as `0x${string}`,\n * chainId: 8453,\n * publicClient,\n * walletClient,\n * signAuthorization,\n * });\n *\n * if (result.status === \"already-delegated\") {\n * console.log(\"Already delegated to\", result.delegatedTo);\n * } else {\n * console.log(\"Delegated! tx:\", result.txHash);\n * }\n * }\n * }\n * ```\n */\nexport async function delegateDirect(\n params: DelegateDirectParams,\n): Promise<DelegateDirectResult> {\n const target =\n params.contractAddress ??\n (getContractAddresses(params.chainId).batchExecutor as Address);\n\n // 1. Short-circuit if already delegated to the expected impl.\n if (params.skipIfAlreadyDelegated !== false) {\n const code = await params.publicClient.getCode({\n address: params.userAddress,\n });\n const current = parseEip7702DelegatedAddress(code);\n if (current && current.toLowerCase() === target.toLowerCase()) {\n // Build a no-op authorization placeholder for the response —\n // we didn't actually sign one because we skipped.\n return {\n status: \"already-delegated\",\n delegatedTo: current,\n authorization: {\n contractAddress: target,\n chainId: params.chainId,\n nonce: 0,\n r: \"0x\" as Hex,\n s: \"0x\" as Hex,\n yParity: 0,\n },\n };\n }\n }\n\n // 2. Read EOA tx nonce (pending — covers any in-flight sends).\n const nonce = await params.publicClient.getTransactionCount({\n address: params.userAddress,\n blockTag: \"pending\",\n });\n\n // 3. Sign authorization via Privy hook.\n const raw = await params.signAuthorization({\n contractAddress: target,\n chainId: params.chainId,\n nonce,\n });\n\n // Normalize to viem's SignedAuthorization shape. Privy returns\n // `yParity` as either `0/1` (number) or `'0x0'/'0x1'` (hex string)\n // depending on SDK version — handle both.\n const yParityRaw = raw.yParity;\n const yParityNum =\n typeof yParityRaw === \"number\"\n ? yParityRaw\n : String(yParityRaw) === \"1\" || String(yParityRaw) === \"0x1\"\n ? 1\n : 0;\n const yParity: 0 | 1 = yParityNum === 1 ? 1 : 0;\n\n const authorization: SignedAuthorization = {\n contractAddress: target,\n chainId: params.chainId,\n nonce,\n r: normalizeHex32(raw.r),\n s: normalizeHex32(raw.s),\n yParity,\n };\n\n // 4. Send EIP-7702 type-4 tx. viem auto-encodes when\n // `authorizationList` is supplied. Self-call (data: '0x') is a\n // no-op — the work is the authorization itself.\n const account = params.walletClient.account;\n if (!account) {\n throw new Error(\n \"delegateDirect: walletClient has no account attached — cannot send tx\",\n );\n }\n\n // Cast to bypass viem's strict generic chain inference; runtime\n // shape matches `EIP7702Transaction` regardless of declared chain.\n const txHash = (await (\n params.walletClient as WalletClient & {\n sendTransaction: (args: unknown) => Promise<Hex>;\n }\n ).sendTransaction({\n account,\n chain: params.walletClient.chain,\n to: params.userAddress,\n value: 0n,\n data: \"0x\" as Hex,\n authorizationList: [authorization],\n })) as Hex;\n\n // 5. Wait for receipt (optional).\n const waitForReceipt = params.waitForReceipt !== false;\n let receipt: TransactionReceipt | undefined;\n if (waitForReceipt) {\n try {\n receipt = await params.publicClient.waitForTransactionReceipt({\n hash: txHash,\n });\n } catch (err) {\n params.onWarning?.(\n `delegateDirect: tx ${txHash} sent but receipt fetch failed: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n }\n\n return {\n status: \"sent\",\n txHash,\n receipt,\n authorization,\n delegatedTo: target,\n };\n}\n\n/**\n * Normalize a hex r/s component to exactly `0x` + 64 hex chars\n * (32 bytes). Privy occasionally returns less-than-32-byte values\n * when the leading byte is zero; viem's `authorizationList` expects\n * full-width hex.\n */\nfunction normalizeHex32(value: Hex | string | undefined): Hex {\n if (!value) return \"0x\" as Hex;\n const stripped = value.replace(/^0x/i, \"\");\n return (\"0x\" + stripped.padStart(64, \"0\")) as Hex;\n}\n","import type { Address, Hex, PublicClient } from \"viem\";\nimport { isDelegatedTo } from \"./checkDelegation\";\nimport type { Eip7702AuthorizationJsonRpc } from \"./eip7702Authorization\";\nimport type { SignAuthorizationFn } from \"./delegateDirect\";\n\n/**\n * In-flight de-dup cache. Keyed by `${chainId}:${account-lower}`.\n *\n * Concurrent callers (e.g. user double-clicks \"Mint\", or React re-renders\n * fire two simultaneous queries) would otherwise each `signAuthorization()`\n * + each consume the same nonce, with the second tx silently failing\n * \"nonce already used\". The cache returns the same in-flight promise so\n * exactly ONE signature is produced per (chain, account) burst.\n *\n * Entries auto-evict when the promise resolves or rejects — no GC pressure,\n * no stale-state risk across user actions.\n */\nconst inFlight = new Map<\n string,\n Promise<{ authorization: Eip7702AuthorizationJsonRpc } | undefined>\n>();\n\nfunction cacheKey(chainId: number, account: Address): string {\n return `${chainId}:${account.toLowerCase()}`;\n}\n\nexport interface AttachDelegationIfNeededParams {\n /**\n * Public client connected to the chain on which the delegation lives.\n * The helper uses it to (1) read on-chain code via `eth_getCode` and\n * (2) read the EOA's tx nonce — both reads must hit the SAME chain\n * that `chainId` declares, otherwise Pimlico's bundler will reject the\n * authorization with a chain-mismatch error.\n */\n rpc: PublicClient;\n\n /**\n * The user's EOA — the wallet that will become a temporary smart account\n * via EIP-7702 SetCode. MUST match the `sender` of the UserOp the caller\n * is about to submit; otherwise the bundler's recovered-signer check\n * fails (\"recovered signer does not match userOperation sender\").\n */\n account: Address;\n\n /**\n * The implementation address the EOA should delegate to — typically the\n * Pimlico Simple7702Account / BatchExecutor on the target chain (Base\n * mainnet: `0xe6Cae83BdE06E4c305530e199D7217f42808555B`).\n *\n * The helper compares the on-chain code at `account` against this address.\n * Mismatch → sign a new authorization to switch the delegate. Match →\n * return `undefined` (caller passes nothing — extra auth would be wasted\n * gas + the bundler may reject redundant authorizations).\n */\n expectedDelegate: Address;\n\n /**\n * Chain id baked into the EIP-7702 authorization tuple. Must equal the\n * chain `rpc` connects to.\n */\n chainId: number;\n\n /**\n * Privy-shaped authorization signer. Pass `useSign7702Authorization().signAuthorization`\n * directly on web, or the Expo SDK's equivalent on mobile.\n *\n * The helper feeds it `(contractAddress, chainId, nonce)` — the signer\n * MUST sign with the same private key that controls `account`, otherwise\n * the recovered signer mismatch above triggers.\n */\n signAuthorization: SignAuthorizationFn;\n}\n\n/**\n * Sign an EIP-7702 authorization for `account` ONLY when the EOA does not\n * already delegate to `expectedDelegate`. Returns the authorization in the\n * shape that permissionless / viem expect on\n * `smartClient.sendTransaction({ authorization, ... })`. Pimlico bundles\n * the SetCode + UserOp execution into a SINGLE bundler tx — \"atomic\n * activation\" — so the user never sees a separate \"delegate\" step on their\n * first action.\n *\n * Returns `undefined` when the EOA already points at `expectedDelegate`.\n * Caller spreads the return value into the tx params:\n *\n * ```ts\n * const authPart = await attachDelegationIfNeeded({ ... });\n * await smartClient.sendTransaction({\n * to: pointToken,\n * data: encodeMintCalldata(amount),\n * ...(authPart ?? {}), // attaches { authorization: ... } only when needed\n * });\n * ```\n *\n * Cost: 1 `eth_getCode` read every time + 1 `eth_getTransactionCount` +\n * 1 signature (only the first time per EOA). Subsequent calls collapse to\n * a single RPC read and return `undefined` — cheap to call before every\n * UserOp.\n *\n * Implementation notes:\n * - Comparison is case-insensitive (`isDelegatedTo` lowercases both\n * sides) so passing checksum-cased addresses is safe.\n * - Nonce is fetched freshly per call to avoid replaying an old nonce\n * when the FE held the helper params across multiple actions.\n * - The signed tuple is passed through `buildEip7702Authorization` to\n * normalise `r` / `s` / `yParity` into the wire shape Pimlico accepts\n * (handles Privy's bigint `v` and string-hex `yParity` variants).\n */\nexport async function attachDelegationIfNeeded(\n params: AttachDelegationIfNeededParams,\n): Promise<{ authorization: Eip7702AuthorizationJsonRpc } | undefined> {\n const key = cacheKey(params.chainId, params.account);\n\n // De-dup concurrent callers: if one is already mid-flight for the same\n // (chain, account), join its promise instead of starting a parallel\n // signature that would race on the same nonce.\n const existing = inFlight.get(key);\n if (existing) return existing;\n\n const promise = doAttach(params).finally(() => {\n // Drop the entry as soon as the work settles so a NEW call (e.g. user\n // retried after a network blip) re-runs the check + sign. The cache is\n // a burst-coalescer, not a long-lived memoizer.\n inFlight.delete(key);\n });\n inFlight.set(key, promise);\n return promise;\n}\n\n/**\n * Inner implementation — runs exactly once per (chain, account) burst.\n * Split out from the public function so the in-flight wrapper stays clean.\n */\nasync function doAttach(\n params: AttachDelegationIfNeededParams,\n): Promise<{ authorization: Eip7702AuthorizationJsonRpc } | undefined> {\n const { rpc, account, expectedDelegate, chainId, signAuthorization } = params;\n\n if (await isDelegatedTo(rpc, account, expectedDelegate)) {\n return undefined;\n }\n\n const nonce = await rpc.getTransactionCount({ address: account });\n const rawAuth = await signAuthorization({\n contractAddress: expectedDelegate,\n chainId,\n nonce,\n });\n\n // Normalise to the JSON-RPC wire shape Pimlico expects on\n // `userOp.eip7702Auth`. Privy returns `r` / `s` / `yParity` separately\n // (and `yParity` as either number or '0x0' / '0x1' string), while\n // viem / permissionless need 0x-prefixed lower-case hex.\n //\n // We don't reuse `buildEip7702Authorization` here because that helper\n // expects a single concatenated `authSig` blob, which Privy doesn't\n // provide — Privy's API already splits the signature for us.\n const authorization: Eip7702AuthorizationJsonRpc = {\n chainId: toHexQuantity(rawAuth.chainId),\n address: (rawAuth.contractAddress ?? rawAuth.address ?? expectedDelegate),\n nonce: toHexQuantity(BigInt(rawAuth.nonce)),\n r: rawAuth.r as Hex,\n s: rawAuth.s as Hex,\n yParity:\n typeof rawAuth.yParity === \"string\"\n ? (rawAuth.yParity.startsWith(\"0x\")\n ? (rawAuth.yParity as Hex)\n : (`0x${rawAuth.yParity}` as Hex))\n : (`0x${rawAuth.yParity}` as Hex),\n };\n\n return { authorization };\n}\n\n/** Number/bigint → '0x'-prefixed hex quantity (no leading zero padding). */\nfunction toHexQuantity(n: number | bigint): Hex {\n return `0x${BigInt(n).toString(16)}` as Hex;\n}\n","import { http } from \"viem\";\nimport type { HttpTransport } from \"viem\";\n\n/**\n * Parameters for `createPafiProxyTransport`.\n */\nexport interface PafiProxyTransportParams {\n /**\n * Full URL of the PAFI sponsor-relayer Pimlico proxy endpoint.\n * @example \"https://sponsor-relayer.pacificfinance.org/pimlico\"\n * @example \"http://localhost:4000/pimlico\"\n */\n proxyUrl: string;\n\n /**\n * Called on every request to get the current Privy identity token.\n * Use a getter (or a ref's `.current`) so the transport always sends\n * the latest token without needing to be rebuilt when it rotates.\n *\n * @example () => identityTokenRef.current\n * @example () => privyHook.identityToken\n */\n getIdentityToken: () => string | null | undefined;\n\n /**\n * Issuer ID sent as `X-Issuer-Id` header.\n * sponsor-relayer uses this to look up per-issuer rate limits and policy.\n * Obtain from PAFI team during onboarding.\n */\n issuerId: string;\n}\n\n/**\n * Create a viem `HttpTransport` that proxies all Pimlico JSON-RPC calls\n * through the PAFI sponsor-relayer (`POST /pimlico`).\n *\n * The transport:\n * - Sets `Authorization: Bearer <identityToken>` on every request\n * - Sets `X-Issuer-Id: <issuerId>` on every request\n * - Reads the identity token lazily via `getIdentityToken()` so it is\n * always fresh without rebuilding the SmartAccountClient on each rotation\n *\n * Pass the returned transport to both `createPimlicoClient` and\n * `createSmartAccountClient` so ALL Pimlico RPC calls (paymaster + bundler)\n * go through the proxy with auth headers attached.\n *\n * @example\n * ```ts\n * import { createPafiProxyTransport, BATCH_EXECUTOR_ADDRESS_BASE_MAINNET } from '@pafi-dev/core';\n * import { createSmartAccountClient } from 'permissionless';\n * import { createPimlicoClient } from 'permissionless/clients/pimlico';\n * import { useIdentityToken } from '@privy-io/react-auth';\n *\n * const { identityToken } = useIdentityToken();\n * const identityTokenRef = useRef<string | null>(null);\n * useEffect(() => { identityTokenRef.current = identityToken; }, [identityToken]);\n *\n * const proxyTransport = createPafiProxyTransport({\n * proxyUrl: process.env.NEXT_PUBLIC_PIMLICO_PROXY_URL!,\n * getIdentityToken: () => identityTokenRef.current,\n * issuerId: process.env.NEXT_PUBLIC_ISSUER_ID!,\n * });\n *\n * const pimlicoClient = createPimlicoClient({ chain: base, transport: proxyTransport });\n * const smartClient = createSmartAccountClient({\n * client: publicClient,\n * chain: base,\n * account,\n * paymaster: pimlicoClient,\n * bundlerTransport: proxyTransport,\n * });\n * ```\n */\nexport function createPafiProxyTransport(\n params: PafiProxyTransportParams,\n): HttpTransport {\n const { proxyUrl, getIdentityToken, issuerId } = params;\n\n return http(proxyUrl, {\n fetchOptions: {},\n // fetchFn intercepts every fetch call the viem http transport makes,\n // injecting the auth headers before the request leaves the browser.\n fetchFn: (input: RequestInfo | URL, init?: RequestInit) => {\n const headers = new Headers(init?.headers);\n const token = getIdentityToken();\n if (token) {\n headers.set(\"authorization\", `Bearer ${token}`);\n }\n headers.set(\"x-issuer-id\", issuerId);\n return fetch(input, { ...init, headers });\n },\n });\n}\n","import type { Hex } from \"viem\";\n\n/**\n * Minimal interface for any smart-account client capable of submitting\n * transactions. Deliberately duck-typed so the SDK does not need a hard\n * dependency on permissionless — any client with this shape works.\n */\nexport interface SmartAccountSender {\n sendTransaction(params: Record<string, unknown>): Promise<Hex>;\n}\n\n/**\n * HTTP statuses returned by the PAFI sponsor-relayer / Pimlico proxy\n * that mean \"paymaster declined sponsorship, retrying without it is a\n * sensible UX.\" 401/403/429/503 only — NOT 4xx/5xx generally (a\n * generic 500 could be anything).\n */\nconst PAYMASTER_HTTP_STATUSES = new Set([401, 403, 429, 503]);\n\n/**\n * Word-boundary patterns matching paymaster-layer failures. Use\n * regex (not substring) so arbitrary error text containing the\n * digits \"503\" / \"403\" / etc. (transit IDs, addresses, log lines)\n * doesn't false-positive.\n *\n * - `\\bAA3[1-4]\\b` — ERC-4337 paymaster validation codes (AA31..AA34)\n * - `\\bpaymaster\\b` — Pimlico / sponsor-relayer plain text\n * - `\\bsponsorship\\b` — Pimlico declines\n * - `\\bpm_\\w+` — JSON-RPC method (pm_getPaymasterData, pm_sponsorUserOperation)\n * - `\\brate ?limit\\b` — generic rate limiter\n * - `\\bunauthorized\\b` — auth failure (also caught by HTTP status check below)\n */\nconst PAYMASTER_PATTERNS: RegExp[] = [\n /\\bAA3[1-4]\\b/i,\n /\\bpaymaster\\b/i,\n /\\bsponsorship\\b/i,\n /\\bpm_\\w+/i,\n /\\brate ?limit\\b/i,\n /\\bunauthorized\\b/i,\n];\n\ninterface MaybeHttpError {\n status?: number;\n statusCode?: number;\n response?: { status?: number };\n cause?: { status?: number };\n message?: string;\n}\n\n/**\n * Returns true when `err` originates from the **paymaster layer** rather\n * than the contract or bundler layer. Covers:\n *\n * - ERC-4337 AA3x validation errors (AA31–AA34, word-boundary match)\n * - Pimlico sponsorship rejections (`pm_*` methods, \"sponsorship\" / \"paymaster\")\n * - PAFI proxy HTTP errors — checked via typed `status` / `statusCode`\n * field when present, NOT substring match\n * - Generic \"rate limit\" / \"unauthorized\" strings\n *\n * Use this to decide whether retrying without a paymaster makes sense.\n * Contract reverts (AA23, AA24, AA25) and bundler errors are NOT matched.\n */\nexport function isPaymasterError(err: unknown): boolean {\n if (err == null || typeof err !== \"object\") return false;\n const e = err as MaybeHttpError;\n\n // 1. Typed HTTP status check — strict integer match, no substring drift.\n const status =\n e.status ?? e.statusCode ?? e.response?.status ?? e.cause?.status;\n if (typeof status === \"number\" && PAYMASTER_HTTP_STATUSES.has(status)) {\n return true;\n }\n\n // 2. Word-boundary regex against the error message. Avoids false\n // positives from arbitrary strings containing the digits \"503\"\n // or substrings like \"403\" inside a hex address.\n const msg = e.message ?? String(err);\n return PAYMASTER_PATTERNS.some((re) => re.test(msg));\n}\n\nexport interface SendWithPaymasterFallbackParams {\n /** Smart-account client with paymaster configured — tried first. */\n primaryClient: SmartAccountSender;\n /**\n * Smart-account client without paymaster — used when `primaryClient`\n * throws a paymaster error. User pays gas from their ETH balance.\n * Pass `null` or `undefined` to disable fallback (error re-thrown as-is).\n */\n fallbackClient: SmartAccountSender | null | undefined;\n /** Transaction params forwarded verbatim to `sendTransaction`. */\n txParams: Record<string, unknown>;\n /**\n * Optional alternate `txParams` used **only on the fallback attempt**.\n * Lets callers strip operator-fee transfers from the calldata when the\n * paymaster refuses — there's no point charging a \"PAFI gas\n * reimbursement\" fee in PT/USDC if PAFI didn't actually sponsor the\n * gas. When omitted, the same `txParams` is reused for both attempts.\n */\n txParamsFallback?: Record<string, unknown>;\n /**\n * Called just before the fallback attempt so the caller can log or\n * update UI. Receives the error message from the failed primary attempt.\n */\n onFallback?: (errorMessage: string) => void;\n}\n\n/**\n * Submit a UserOp with paymaster sponsorship, falling back to user-paid gas\n * if the paymaster refuses.\n *\n * Flow:\n * 1. Call `primaryClient.sendTransaction(txParams)`.\n * 2. If it throws and `isPaymasterError` returns true, call `onFallback` then\n * retry with `fallbackClient` (no paymaster — user pays gas).\n * 3. All other errors (contract revert, bundler rejection, network) are\n * re-thrown immediately without a retry.\n *\n * @example\n * ```ts\n * import { sendWithPaymasterFallback } from '@pafi-dev/core';\n *\n * const txHash = await sendWithPaymasterFallback({\n * primaryClient: smartClientWithPaymaster,\n * fallbackClient: smartClientNoPaymaster,\n * txParams: { calls, paymasterContext: { sponsorshipPolicyId } },\n * onFallback: (msg) => addLog(`Paymaster refused (${msg}) — you will pay gas.`),\n * });\n * ```\n */\nexport async function sendWithPaymasterFallback(\n params: SendWithPaymasterFallbackParams,\n): Promise<Hex> {\n const { primaryClient, fallbackClient, txParams, txParamsFallback, onFallback } = params;\n try {\n return await primaryClient.sendTransaction(txParams);\n } catch (err) {\n if (isPaymasterError(err) && fallbackClient) {\n const msg = (err as any)?.message ?? String(err);\n onFallback?.(msg);\n return await fallbackClient.sendTransaction(txParamsFallback ?? txParams);\n }\n throw err;\n }\n}\n","import { parseAbi } from \"viem\";\nimport type { Address, PublicClient } from \"viem\";\nimport { PAFI_SUBGRAPH_URL } from \"../subgraph/pools\";\nimport { getContractAddresses } from \"../contracts/real/addresses\";\nimport { OracleStaleError } from \"../errors\";\n\n/**\n * Operator-fee quoter — pure on-chain + subgraph reads, no issuer\n * backend involvement.\n *\n * Computes the PT amount the user must transfer to PAFI's fee\n * recipient to reimburse the ERC-4337 gas cost of a sponsored UserOp:\n *\n * nativeWei = gasUnits × gasPrice\n * premium = nativeWei × premiumBps / 10_000\n * feeInUsdt = premium × ethPriceUsd / 10^(18+8-6) // Chainlink\n * feeInPt = feeInUsdt × ptPerUsdt_18dec / 10^(18-6) // V4 pool spot\n *\n * Used by the SDK Direct path so callers don't need to hit any\n * issuer-specific endpoint to figure out the fee. The sponsor-relayer\n * `FeeValidatorService` runs the same quote server-side with a 5%\n * tolerance window — minor drift between client and server is\n * accepted.\n *\n * @example\n * ```ts\n * import { quoteOperatorFeePt } from \"@pafi-dev/core\";\n *\n * const fee = await quoteOperatorFeePt({\n * provider: publicClient,\n * chainId: 8453,\n * pointTokenAddress: POINT_TOKEN,\n * });\n *\n * await trading.handleSwap({\n * ...,\n * gasFeePt: fee,\n * feeRecipient: getContractAddresses(8453).pafiFeeRecipient,\n * });\n * ```\n */\n\nconst CHAINLINK_ABI = parseAbi([\n \"function latestRoundData() external view returns (uint80, int256, uint256, uint256, uint80)\",\n]);\n\n// Base ETH/USD heartbeat — feed updates hourly or on 0.5% deviation.\nconst CHAINLINK_MAX_AGE_S = 3_600n;\n\nconst POOL_PRICE_QUERY = `\n query GetPoolPrice($id: ID!) {\n pafiToken(id: $id) {\n pool {\n token0 { id }\n token1 { id }\n token0Price\n token1Price\n }\n }\n }\n`;\n\ninterface GraphQLPriceResponse {\n data?: {\n pafiToken: {\n pool: {\n token0: { id: string };\n token1: { id: string };\n token0Price: string;\n token1Price: string;\n } | null;\n } | null;\n };\n errors?: { message: string }[];\n}\n\n/**\n * Per-scenario gas budgets (callGasLimit + verificationGas + preVerify)\n * used by `quoteOperatorFee*`. Defaults are calibrated against the\n * actual UserOps the issuer + trading SDKs build:\n *\n * mint — `RelayService.prepareMint` callGasLimit 300k → 500k margin\n * burn — `RelayService.prepareBurn` 300k → 500k margin\n * swap — `buildSwapUserOp` callGasLimit 700k (UR.execute heavy)\n * perp-deposit — `buildPerpDepositViaRelay` 800k (LayerZero msg)\n * delegate — minimal no-op self-call, 200k margin\n *\n * Pass an explicit `gasUnits` to override.\n */\nexport type FeeScenario =\n | \"mint\"\n | \"burn\"\n | \"swap\"\n | \"perp-deposit\"\n | \"delegate\"\n | \"erc20-transfer\";\n\nexport const SCENARIO_GAS_UNITS: Record<FeeScenario, bigint> = {\n mint: 500_000n,\n burn: 500_000n,\n swap: 700_000n,\n \"perp-deposit\": 800_000n,\n delegate: 200_000n,\n // 2-call batch: 1 ERC-20 fee transfer + 1 ERC-20 transfer + 7702\n // delegation overhead. ~70-90k empirical; 200k matches `delegate`\n // budget and gives headroom for premium variance.\n \"erc20-transfer\": 200_000n,\n};\n\nexport interface QuoteOperatorFeePtConfig {\n provider: PublicClient;\n chainId: number;\n pointTokenAddress: Address;\n /**\n * Scenario tag — picks default `gasUnits` from `SCENARIO_GAS_UNITS`.\n * Ignored when `gasUnits` is explicit. Default `\"mint\"` for back-\n * compat (matches the legacy 500_000 constant).\n */\n scenario?: FeeScenario;\n /**\n * ERC-4337 gas units the UserOp will consume on chain. Defaults to\n * `SCENARIO_GAS_UNITS[scenario]`, falling back to 500_000n. Pass a\n * tighter number when you have a Pimlico estimate to feed in.\n */\n gasUnits?: bigint;\n /**\n * Premium applied on top of raw gas cost in basis points. Default\n * 12_000 (= 1.2×, 20% buffer to cover oracle drift + paymaster\n * overhead). Same default the issuer SDK's `FeeManager` uses.\n */\n premiumBps?: number;\n /** Chainlink ETH/USD feed override. Defaults to chain's address. */\n chainlinkFeedAddress?: Address;\n /** Subgraph URL override. Defaults to `PAFI_SUBGRAPH_URL`. */\n subgraphUrl?: string;\n /** USDT token decimals. Default 6 (USDC/USDT on Base/Ethereum). */\n usdtDecimals?: number;\n /**\n * Opt-in fallback prices used when Chainlink / subgraph is\n * unavailable. Default `false` — throw `OracleStaleError`. Set\n * `true` AND configure `fallbackEthPriceUsd` / `fallbackPtPriceUsdt`\n * to absorb oracle outages instead of surfacing them.\n *\n * When fallback fires, callers SHOULD observe via `onFallback` so\n * production can flag the under-priced quote (e.g. show \"fee priced\n * against stale oracle\" banner in FE; alert on-call).\n */\n allowStaleFallback?: boolean;\n /** Fallback ETH price (USD) when Chainlink is unreachable. Only used when `allowStaleFallback: true`. Default 3000. */\n fallbackEthPriceUsd?: number;\n /** Fallback PT price (USDT per 1 PT) when subgraph is unreachable. Only used when `allowStaleFallback: true`. Default 0.1. */\n fallbackPtPriceUsdt?: number;\n /**\n * Observability hook fired when the quoter falls back to stale\n * prices. Callers SHOULD wire this to their alert pipeline (Sentry,\n * Datadog, PagerDuty) so under-priced quotes don't go unnoticed.\n * When omitted, falls back to `console.warn` (kept for back-compat\n * but invisible in most prod log shippers).\n */\n onFallback?: (info: FallbackInfo) => void;\n fetchImpl?: typeof fetch;\n}\n\n/**\n * Detail surfaced via `onFallback` when the quoter substitutes a\n * stale-fallback price. Callers can format this into a UI banner or\n * log line.\n */\nexport interface FallbackInfo {\n /** Which oracle failed. */\n source: \"chainlink\" | \"subgraph\";\n /** Underlying reason from the oracle call (e.g. RPC error message). */\n reason: string;\n /** The fallback value that was used (in source-native units). */\n fallbackValue: number;\n}\n\n/**\n * Subset of `QuoteOperatorFeePtConfig` that doesn't need the V4 pool\n * step — used by `quoteOperatorFeeUsdt` (FE cashout-preview / `/gas-fee`\n * replacement). No `pointTokenAddress`, no `subgraphUrl`, no PT-side\n * fallback.\n */\nexport interface QuoteOperatorFeeUsdtConfig {\n provider: PublicClient;\n chainId: number;\n /** See `QuoteOperatorFeePtConfig.scenario`. v0.7.4. */\n scenario?: FeeScenario;\n gasUnits?: bigint;\n premiumBps?: number;\n chainlinkFeedAddress?: Address;\n usdtDecimals?: number;\n /**\n * Opt-in fallback price when Chainlink is unavailable. Default\n * `false` — throw `OracleStaleError`.\n */\n allowStaleFallback?: boolean;\n fallbackEthPriceUsd?: number;\n /** See QuoteOperatorFeePtConfig.onFallback. */\n onFallback?: (info: FallbackInfo) => void;\n}\n\nconst DEFAULT_GAS_UNITS = 500_000n;\nconst DEFAULT_PREMIUM_BPS = 12_000;\nconst DEFAULT_USDT_DECIMALS = 6;\n\n/**\n * Operator fee quoted in USDT raw units (default 6 decimals). Returns\n * the same value the legacy `GET /gas-fee` endpoint produced —\n * `withPremium_wei × ethPrice / 10^(18 + 8 - usdtDecimals)`. Does NOT\n * convert to PT, so no V4 subgraph call needed; pure on-chain\n * Chainlink read + RPC `getGasPrice`.\n *\n * Use this on the FE for the cashout preview screen (or anywhere a\n * USDT-denominated estimate is wanted) instead of round-tripping the\n * issuer backend's `/gas-fee` endpoint.\n *\n * @example\n * ```ts\n * import { quoteOperatorFeeUsdt } from \"@pafi-dev/core\";\n * const gasFeeUsdt = await quoteOperatorFeeUsdt({\n * provider, chainId: 8453,\n * });\n * // gasFeeUsdt is a bigint in 6-decimal USDT raw units\n * ```\n */\nexport async function quoteOperatorFeeUsdt(\n config: QuoteOperatorFeeUsdtConfig,\n): Promise<bigint> {\n const {\n provider,\n chainId,\n scenario = \"mint\",\n gasUnits = SCENARIO_GAS_UNITS[scenario] ?? DEFAULT_GAS_UNITS,\n premiumBps = DEFAULT_PREMIUM_BPS,\n usdtDecimals = DEFAULT_USDT_DECIMALS,\n allowStaleFallback = false,\n fallbackEthPriceUsd = 3_000,\n } = config;\n\n const chainlinkFeedAddress =\n config.chainlinkFeedAddress ??\n getContractAddresses(chainId).chainlinkEthUsd;\n\n const gasPrice = await provider.getGasPrice();\n const nativeCost = gasPrice * gasUnits;\n const withPremium = (nativeCost * BigInt(premiumBps)) / 10_000n;\n\n const ethPrice8dec = await getEthPrice8dec(\n provider,\n chainlinkFeedAddress,\n allowStaleFallback ? fallbackEthPriceUsd : null,\n config.onFallback,\n );\n\n // feeUsdt_<usdtDec> = withPremium_wei × ethPrice_8dec / 10^(18 + 8 - usdtDec)\n const denomExp = 18 + 8 - usdtDecimals;\n return (withPremium * ethPrice8dec) / 10n ** BigInt(denomExp);\n}\n\nexport async function quoteOperatorFeePt(\n config: QuoteOperatorFeePtConfig,\n): Promise<bigint> {\n const {\n provider,\n chainId,\n pointTokenAddress,\n scenario = \"mint\",\n gasUnits = SCENARIO_GAS_UNITS[scenario] ?? DEFAULT_GAS_UNITS,\n premiumBps = DEFAULT_PREMIUM_BPS,\n subgraphUrl = PAFI_SUBGRAPH_URL,\n usdtDecimals = DEFAULT_USDT_DECIMALS,\n allowStaleFallback = false,\n fallbackEthPriceUsd = 3_000,\n fallbackPtPriceUsdt = 0.1,\n fetchImpl = globalThis.fetch,\n } = config;\n\n const chainlinkFeedAddress =\n config.chainlinkFeedAddress ??\n getContractAddresses(chainId).chainlinkEthUsd;\n\n const gasPrice = await provider.getGasPrice();\n const nativeCost = gasPrice * gasUnits;\n const withPremium = (nativeCost * BigInt(premiumBps)) / 10_000n;\n\n const [ethPrice8dec, ptPerUsdt18dec] = await Promise.all([\n getEthPrice8dec(\n provider,\n chainlinkFeedAddress,\n allowStaleFallback ? fallbackEthPriceUsd : null,\n config.onFallback,\n ),\n getPtPerUsdt18dec(\n fetchImpl,\n subgraphUrl,\n pointTokenAddress,\n allowStaleFallback ? fallbackPtPriceUsdt : null,\n config.onFallback,\n ),\n ]);\n\n // feeInUsdt_<usdtDec> = withPremium_wei × ethPrice_8dec / 10^(18+8-usdtDec)\n // feeInPt_18dec = feeInUsdt × ptPerUsdt_18dec / 10^(18-usdtDec)\n // combined = withPremium × ethPrice × ptPerUsdt / 10^(18+8-usdtDec + 18-usdtDec)\n // = / 10^(44 - 2·usdtDec)\n // For usdtDecimals=6: 10^(44-12) = 10^32. But ptPerUsdt is already 18-scaled\n // so we subtract another 18: 10^(32-18) = 10^14? No — let me redo cleanly.\n //\n // Working in fractional units to keep it readable:\n // eth_human = withPremium / 10^18\n // usdt_human = eth_human × ethPriceFloat\n // pt_human = usdt_human × ptPerUsdtFloat\n // pt_18dec = pt_human × 10^18\n //\n // Substituting bigint scales:\n // ethPrice_8dec = ethPriceFloat × 10^8\n // ptPerUsdt_18dec = ptPerUsdtFloat × 10^18 (PT raw per 1 *human* USDT)\n //\n // pt_18dec = (withPremium / 10^18) × (ethPrice_8dec / 10^8)\n // × (ptPerUsdt_18dec / 10^18) × 10^18\n // = withPremium × ethPrice_8dec × ptPerUsdt_18dec / 10^26\n return (withPremium * ethPrice8dec * ptPerUsdt18dec) / 10n ** 26n;\n}\n\n/**\n * Router quoter for the `erc20-transfer` sponsored scenario — picks\n * the correct underlying quoter based on the token being transferred:\n *\n * USDC / USDT → `quoteOperatorFeeUsdt` (Chainlink-based, 6-dec)\n * active PT → `quoteOperatorFeePt` (Chainlink + V3 pool quote)\n *\n * Fee is denominated in the SAME token the user is sending — no extra\n * approval / swap step needed. Caller is responsible for verifying the\n * token IS in the sponsored allowlist before calling; this function\n * just routes by address equality.\n *\n * Returns the fee in the token's native units (6-dec for USDC/USDT,\n * 18-dec for PT). The sponsor-relayer reproduces the same quote\n * server-side with a 5% slack window.\n *\n * @example\n * ```ts\n * const fee = await quoteOperatorFeeForTransfer({\n * provider, chainId: 8453,\n * tokenAddress: USDC_ADDRESS,\n * });\n * ```\n */\nexport interface QuoteOperatorFeeForTransferConfig\n extends Omit<QuoteOperatorFeeUsdtConfig, \"scenario\"> {\n /** The ERC-20 the user is transferring. Drives both quote logic + fee currency. */\n tokenAddress: Address;\n /**\n * Subgraph URL override — only consumed when `tokenAddress` resolves\n * to a PT (USDC/USDT paths don't hit the subgraph).\n */\n subgraphUrl?: string;\n /** PT fallback (only used when `tokenAddress` is a PT and subgraph fails). */\n fallbackPtPriceUsdt?: number;\n fetchImpl?: typeof fetch;\n}\n\nexport async function quoteOperatorFeeForTransfer(\n config: QuoteOperatorFeeForTransferConfig,\n): Promise<bigint> {\n const { provider, chainId, tokenAddress } = config;\n const { usdc, usdt } = getContractAddresses(chainId);\n const tokenLower = tokenAddress.toLowerCase();\n\n const isStable =\n (usdc && tokenLower === usdc.toLowerCase()) ||\n tokenLower === usdt.toLowerCase();\n\n if (isStable) {\n return quoteOperatorFeeUsdt({\n provider,\n chainId,\n scenario: \"erc20-transfer\",\n gasUnits: config.gasUnits,\n premiumBps: config.premiumBps,\n chainlinkFeedAddress: config.chainlinkFeedAddress,\n usdtDecimals: config.usdtDecimals,\n allowStaleFallback: config.allowStaleFallback,\n fallbackEthPriceUsd: config.fallbackEthPriceUsd,\n onFallback: config.onFallback,\n });\n }\n\n // Non-stable → assume PT. Caller validates allowlist upstream.\n return quoteOperatorFeePt({\n provider,\n chainId,\n pointTokenAddress: tokenAddress,\n scenario: \"erc20-transfer\",\n gasUnits: config.gasUnits,\n premiumBps: config.premiumBps,\n chainlinkFeedAddress: config.chainlinkFeedAddress,\n subgraphUrl: config.subgraphUrl,\n usdtDecimals: config.usdtDecimals,\n allowStaleFallback: config.allowStaleFallback,\n fallbackEthPriceUsd: config.fallbackEthPriceUsd,\n fallbackPtPriceUsdt: config.fallbackPtPriceUsdt,\n onFallback: config.onFallback,\n fetchImpl: config.fetchImpl,\n });\n}\n\n/**\n * Fetch ETH/USD from Chainlink. When the feed is unavailable or stale,\n * either:\n * - fallback is `null` → throw `OracleStaleError` (default, v0.7.1+).\n * - fallback is a number → return `Math.round(fallback * 1e8)` after\n * `console.warn`. Caller opted in via `allowStaleFallback: true`.\n */\nasync function getEthPrice8dec(\n provider: PublicClient,\n feed: Address,\n fallback: number | null,\n onFallback?: (info: FallbackInfo) => void,\n): Promise<bigint> {\n try {\n const result = await provider.readContract({\n address: feed,\n abi: CHAINLINK_ABI,\n functionName: \"latestRoundData\",\n });\n const answer = result[1];\n const updatedAt = result[3];\n\n if (answer <= 0n) throw new Error(\"Chainlink: non-positive price\");\n\n const ageS = BigInt(Math.floor(Date.now() / 1000)) - updatedAt;\n if (ageS > CHAINLINK_MAX_AGE_S) {\n throw new Error(`Chainlink: price stale by ${ageS}s`);\n }\n\n return answer;\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n if (fallback === null) {\n throw new OracleStaleError(\"chainlink\", reason);\n }\n if (onFallback) {\n onFallback({ source: \"chainlink\", reason, fallbackValue: fallback });\n } else {\n // Back-compat: default to console.warn when caller hasn't wired\n // an observability hook. Production callers SHOULD pass\n // `onFallback` to surface the fallback in their alert pipeline.\n // eslint-disable-next-line no-console\n console.warn(\n \"[quoteOperatorFee] Chainlink unavailable, using opt-in fallback:\",\n reason,\n );\n }\n return BigInt(Math.round(fallback * 1e8));\n }\n}\n\n/**\n * Fetch PT/USDT price from PAFI subgraph. When subgraph is unavailable\n * either:\n * - `fallbackPtPriceUsdt` is `null` → throw `OracleStaleError`\n * (default, v0.7.1+).\n * - is a number → fall back to `1 / fallback` after `console.warn`.\n */\nasync function getPtPerUsdt18dec(\n fetchImpl: typeof fetch,\n subgraphUrl: string,\n pointTokenAddress: Address,\n fallbackPtPriceUsdt: number | null,\n onFallback?: (info: FallbackInfo) => void,\n): Promise<bigint> {\n try {\n const response = await fetchImpl(subgraphUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n query: POOL_PRICE_QUERY,\n variables: { id: pointTokenAddress.toLowerCase() },\n }),\n });\n\n if (!response.ok) throw new Error(`subgraph HTTP ${response.status}`);\n\n const json = (await response.json()) as GraphQLPriceResponse;\n if (json.errors?.length) {\n throw new Error(json.errors.map((e) => e.message).join(\"; \"));\n }\n\n const pool = json.data?.pafiToken?.pool;\n if (!pool) throw new Error(\"pafiToken or pool not found\");\n\n // Subgraph V4 returns HUMAN-readable price ratios (decimals already\n // applied). For pool {token0=USDT, token1=PT}:\n // token0Price = USDT_human per 1 PT_human (e.g. \"2.0015\")\n // token1Price = PT_human per 1 USDT_human (e.g. \"0.4996\")\n //\n // We want `ptPerUsdt_18dec` = PT_raw per 1 USDT_human, i.e. the price\n // in PT-raw units per one human USDT. That's:\n // PT_human_per_USDT_human × 10^18 = PT_raw_per_USDT_human\n //\n // So always pick the price string that's \"PT per USDT in human\":\n // isPtToken0 → token0Price is \"PT/USDT_other\" but we have USDT here\n // (PT is token0, token1 is USDT) → token0Price is\n // USDT_human per 1 PT_human → invert\n // !isPtToken0 → token1Price is PT_human per 1 USDT_human → use as-is\n //\n // (Older comment claiming subgraph returns raw ratio + the\n // 10^24 / raw formula was wrong — it returned values off by ~10^11\n // for the typical 18/6 decimals split, making operator fee in PT\n // effectively zero.)\n const isPtToken0 =\n pool.token0.id.toLowerCase() === pointTokenAddress.toLowerCase();\n\n let ptPerUsdtHumanStr: string;\n if (isPtToken0) {\n // token0Price = USDT_human per 1 PT_human → invert to PT/USDT\n const usdtPerPtHuman = Number(pool.token0Price);\n if (!Number.isFinite(usdtPerPtHuman) || usdtPerPtHuman <= 0) {\n throw new Error(`invalid pool token0Price from subgraph: ${pool.token0Price}`);\n }\n ptPerUsdtHumanStr = (1 / usdtPerPtHuman).toFixed(18);\n } else {\n // token1Price = PT_human per 1 USDT_human → use directly\n ptPerUsdtHumanStr = pool.token1Price;\n if (!ptPerUsdtHumanStr || Number(ptPerUsdtHumanStr) <= 0) {\n throw new Error(`invalid pool token1Price from subgraph: ${ptPerUsdtHumanStr}`);\n }\n }\n\n const ptPerUsdt18dec = parseBigDecimalTo18(ptPerUsdtHumanStr);\n if (ptPerUsdt18dec === 0n) {\n throw new Error(`pool price parsed to zero: ${ptPerUsdtHumanStr}`);\n }\n return ptPerUsdt18dec;\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n if (fallbackPtPriceUsdt === null) {\n throw new OracleStaleError(\"subgraph\", reason);\n }\n if (onFallback) {\n onFallback({\n source: \"subgraph\",\n reason,\n fallbackValue: fallbackPtPriceUsdt,\n });\n } else {\n // eslint-disable-next-line no-console\n console.warn(\n \"[quoteOperatorFeePt] subgraph unavailable, using opt-in fallback:\",\n reason,\n );\n }\n const ptPerUsdtHuman = 1 / fallbackPtPriceUsdt;\n return parseBigDecimalTo18(ptPerUsdtHuman.toFixed(18));\n }\n}\n\nfunction parseBigDecimalTo18(s: string): bigint {\n const SCALE = 18;\n const [whole = \"0\", frac = \"\"] = s.split(\".\");\n const padded = (frac + \"0\".repeat(SCALE)).slice(0, SCALE);\n return BigInt(whole + padded);\n}\n","import { isAddress } from \"viem\";\nimport type { Address } from \"viem\";\nimport type { PoolKey } from \"../types\";\n\n/**\n * PAFI-hosted subgraph endpoint — single source of truth across all\n * SDK packages.\n *\n * Indexes IssuerRegistry, PointTokenFactory, MintingOracle, and\n * MintFeeWrapper. Schema entities: `MintFeeWrapper`, `MintFeeConfig`,\n * `FeeRecipient`, `MintWithFeeEvent`, `FeeDistribution`.\n */\n/**\n * Default subgraph endpoint. Points at `pafi-subgraph-v1` — the deployment\n * that indexes the V2 contract bucket (IssuerRegistry 0x3e8264…,\n * PointTokenFactory 0x34f9F8…, TokenRegistry 0x7092b3…, MintingOracle\n * 0x19F5Ea…, MintFeeWrapper 0x72B8FB…). Despite the \"v1\" name in the URL\n * this is the freshest subgraph — naming is a deploy-order artifact, not\n * a contract-version indicator.\n *\n * Previous deployments (`pafi-subgraph-v4`, `v5`) tracked the v1.6\n * contract bucket and have been retired alongside the v2 cutover.\n *\n * Override per-call via `subgraphUrl` parameter when you need to point at\n * a fork or a private graph node for staging.\n */\nexport const PAFI_SUBGRAPH_URL =\n \"https://graph-base-mainnet.pacificfinance.org/subgraphs/name/pafi-subgraph-v1\";\n\n/**\n * Pool query — requests `feeTier`, `token0`, `token1` (the V3-required\n * fields). `tickSpacing` and `hooks` were V4-only; the parser ignores\n * them if the subgraph still returns them, so this is forward- and\n * backward-compatible while the subgraph team finishes their own\n * schema migration.\n */\nconst POOL_QUERY = `\n query GetPoolForPointToken($id: ID!) {\n pafiToken(id: $id) {\n id\n pool {\n id\n feeTier\n token0 { id }\n token1 { id }\n }\n }\n }\n`;\n\ninterface GraphQLResponse {\n data?: {\n pafiToken: {\n id: string;\n pool: {\n id: string;\n feeTier: string;\n /** V4-era field; ignored if present, allowed to be absent. */\n tickSpacing?: string | null;\n /** V4-era field; ignored if present, allowed to be absent. */\n hooks?: string | null;\n token0: { id: string };\n token1: { id: string };\n } | null;\n } | null;\n };\n errors?: { message: string }[];\n}\n\nfunction sortTokens(a: Address, b: Address): [Address, Address] {\n return a.toLowerCase() < b.toLowerCase() ? [a, b] : [b, a];\n}\n\n/**\n * Fetch the Uniswap V3 pool(s) for a PAFI PointToken from the subgraph.\n *\n * Browser and Node compatible — uses globalThis.fetch. Returns an empty\n * array (never throws) when the token has no pool yet or the subgraph is\n * unreachable, so callers can show \"quote unavailable\" without crashing.\n *\n * Tolerates subgraph responses still carrying the V4-era `tickSpacing`\n * and `hooks` fields — those are silently ignored. Subgraph migration\n * can land independently of the SDK migration.\n *\n * @param chainId - Chain ID (reserved for multi-subgraph routing; currently unused)\n * @param pointTokenAddress - The PointToken contract address\n * @param subgraphUrl - Override the default PAFI subgraph URL\n */\nexport async function fetchPafiPools(\n _chainId: number,\n pointTokenAddress: Address,\n subgraphUrl: string = PAFI_SUBGRAPH_URL,\n): Promise<PoolKey[]> {\n let response: Response;\n try {\n response = await fetch(subgraphUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n query: POOL_QUERY,\n variables: { id: pointTokenAddress.toLowerCase() },\n }),\n });\n } catch (err) {\n console.warn(\"[fetchPafiPools] subgraph unreachable:\", (err as Error).message);\n return [];\n }\n\n if (!response.ok) {\n console.warn(`[fetchPafiPools] subgraph returned ${response.status}`);\n return [];\n }\n\n let json: GraphQLResponse;\n try {\n json = (await response.json()) as GraphQLResponse;\n } catch (err) {\n console.warn(\n \"[fetchPafiPools] subgraph returned non-JSON:\",\n (err as Error).message,\n );\n return [];\n }\n if (json.errors && json.errors.length > 0) {\n console.warn(\n \"[fetchPafiPools] subgraph errors:\",\n json.errors.map((e) => e.message).join(\"; \"),\n );\n return [];\n }\n\n const pool = json.data?.pafiToken?.pool;\n if (!pool) return [];\n\n if (\n !isAddress(pool.token0.id) ||\n !isAddress(pool.token1.id) ||\n !Number.isFinite(Number(pool.feeTier))\n ) {\n console.error(\"[fetchPafiPools] invalid pool data in subgraph response — skipping\");\n return [];\n }\n\n const [token0, token1] = sortTokens(\n pool.token0.id as Address,\n pool.token1.id as Address,\n );\n\n return [{\n token0,\n token1,\n fee: Number(pool.feeTier),\n }];\n}\n","import { parseAbi } from \"viem\";\n\n/**\n * Real `PointToken` ABI — matches the V2 dual-bucket PointToken on\n * Base. Each issuer's clone is per-deploy, created via\n * `PointTokenFactory.createToken()`. ABI sourced from the Solidity\n * contract shipped by the SC team (Pacific-Finance-Lab/PAFI-Point-module-sc).\n *\n * ## V2 Source enum\n *\n * Every mint and burn now requires a `Source` argument:\n * 0 = EQUITY — issuer's declared capital\n * 1 = VAULT — LP collateral in SettlementVault\n *\n * SDK helpers + sponsor-relayer hardcode / enforce `source = EQUITY`\n * until the VAULT writer surface lands.\n *\n * ## Mint — two paths\n *\n * Path 1 — Direct (whitelisted minter):\n * mint(address to, uint256 amount, uint8 source)\n * - msg.sender must be in minters[]\n * - Used by off-chain services that hold a minter key\n *\n * Path 2 — Sig-gated (sponsored flow):\n * mint(address user, uint256 amount, uint8 source, uint256 deadline, bytes minterSig)\n * - msg.sender MUST equal `user` (the receiver)\n * - minterSig is an EIP-712 MintForRequest signature from a whitelisted minter\n * - Consumes `mintRequestNonces[user]`\n * - Canonical PAFI path: issuer backend signs, user submits via\n * EIP-7702 + PAFI sponsor-relayer\n *\n * ## Burn — mirror structure\n *\n * Path 1: burn(address from, uint256 amount, uint8 source) — whitelisted burner\n * Path 2: burn(address from, uint256 amount, uint8 source, uint256 deadline, bytes burnerSig)\n * — msg.sender must equal `from`, burnerSig from whitelisted burner,\n * consumes `burnRequestNonces[from]`\n *\n * Either burn path emits `Transfer(from → 0x0)` so BurnIndexer semantics\n * don't depend on which path was taken. Bucket-specific underflow\n * surfaces as `BucketUnderflow(source, amount, available)`.\n *\n * ## EIP-712 type hashes (V2 — from contract source)\n *\n * MintForRequest(address user,address receiver,uint256 amount,uint8 source,uint256 nonce,uint256 deadline)\n * BurnRequest(address from,uint256 amount,uint8 source,uint256 nonce,uint256 deadline)\n *\n * `user` drives the `mintRequestNonces[user]` stream; `receiver ==\n * msg.sender` of `mint()` (= user for direct, = MintFeeWrapper for\n * wrapper-mediated mints).\n *\n * Domain: EIP-712 standard — name = ERC-20 token name, version = \"1\",\n * chainId, verifyingContract = this PointToken address.\n */\n/**\n * Single-signature ABI for the sig-gated mint (the canonical PAFI\n * sponsored path). Split out from `POINT_TOKEN_ABI` because viem's\n * type-level overload resolution on `encodeFunctionData({abi, functionName:\"mint\", args:[...]})`\n * cannot unambiguously pick between the 3-arg direct path and the\n * 5-arg sig-gated path when both share the `mint` name. Callers that\n * need to ENCODE the sig-gated mint should use this ABI; ABI reads\n * (e.g. balance views) keep `POINT_TOKEN_ABI`.\n */\nexport const POINT_TOKEN_MINT_SIG_ABI = parseAbi([\n \"function mint(address user, uint256 amount, uint8 source, uint256 deadline, bytes minterSig) external\",\n]);\n\n/** Sig-gated burn ABI — see `POINT_TOKEN_MINT_SIG_ABI` rationale. */\nexport const POINT_TOKEN_BURN_SIG_ABI = parseAbi([\n \"function burn(address from, uint256 amount, uint8 source, uint256 deadline, bytes burnerSig) external\",\n]);\n\nexport const POINT_TOKEN_ABI = parseAbi([\n // --- Mint paths (V2 dual-bucket: +uint8 source) ---\n \"function mint(address to, uint256 amount, uint8 source) external\",\n \"function mint(address user, uint256 amount, uint8 source, uint256 deadline, bytes minterSig) external\",\n\n // --- Burn paths (V2 dual-bucket: +uint8 source) ---\n \"function burn(address from, uint256 amount, uint8 source) external\",\n \"function burn(address from, uint256 amount, uint8 source, uint256 deadline, bytes burnerSig) external\",\n\n // --- V2 per-bucket supply views ---\n \"function equitySupply() external view returns (uint256)\",\n \"function vaultSupply() external view returns (uint256)\",\n\n // --- Authorization reads ---\n \"function minters(address account) external view returns (bool)\",\n \"function burners(address account) external view returns (bool)\",\n\n // --- Authorization writes (owner-only; used by provisioning scripts) ---\n \"function addMinter(address minter) external\",\n \"function removeMinter(address minter) external\",\n \"function addBurner(address burner) external\",\n \"function removeBurner(address burner) external\",\n\n // --- Nonces (sig-gated paths) ---\n \"function mintRequestNonces(address receiver) external view returns (uint256)\",\n \"function burnRequestNonces(address from) external view returns (uint256)\",\n\n // --- Oracle ---\n \"function mintingOracle() external view returns (address)\",\n \"function setMintingOracle(address _mintingOracle) external\",\n\n // --- Issuer ---\n \"function issuer() external view returns (address)\",\n\n // --- Standard ERC-20 reads ---\n \"function balanceOf(address account) external view returns (uint256)\",\n \"function totalSupply() external view returns (uint256)\",\n \"function name() external view returns (string)\",\n \"function symbol() external view returns (string)\",\n \"function decimals() external view returns (uint8)\",\n\n // --- EIP-712 domain (for off-chain sig verification) ---\n \"function DOMAIN_SEPARATOR() external view returns (bytes32)\",\n \"function eip712Domain() external view returns (bytes1 fields, string name, string version, uint256 chainId, address verifyingContract, bytes32 salt, uint256[] extensions)\",\n\n // --- Events ---\n // PointIndexer filters Transfer(from=0x0) for mints\n // BurnIndexer filters Transfer(to=0x0) for burns\n \"event Transfer(address indexed from, address indexed to, uint256 value)\",\n \"event MinterAdded(address indexed minter)\",\n \"event MinterRemoved(address indexed minter)\",\n \"event BurnerAdded(address indexed burner)\",\n \"event BurnerRemoved(address indexed burner)\",\n \"event MintingOracleUpdated(address indexed mintingOracle)\",\n] as const);\n","import { getContractAddresses } from \"./addresses\";\n\n// Derived from addresses.ts — single source of truth.\n// Update addresses.ts when SC team redeploys; these follow automatically.\nexport const BATCH_EXECUTOR_ADDRESS_BASE_MAINNET = getContractAddresses(8453).batchExecutor;\nexport const BATCH_EXECUTOR_ADDRESS_BASE_SEPOLIA = getContractAddresses(84532).batchExecutor;\n\n// The ABI itself is fully real — re-exported from userop/ for\n// convenience so consumers don't need two imports.\nexport { BATCH_EXECUTOR_ABI } from \"../../userop/batchExecute\";\n","/**\n * PAFI HTTP service URLs by chainId. Mirrors the `getContractAddresses`\n * pattern: SDK ships with the URL for each supported chain so issuers\n * don't pass URLs as env vars / constructor args.\n *\n * When PAFI changes URLs (rebrand, re-host, environment migration),\n * bump the SDK version and re-publish. Issuers pin a version, get the\n * URLs that match.\n *\n * No env override is provided on purpose — different environments use\n * different SDK versions (e.g. an `@pafi-dev/issuer-dev` channel for\n * staging). This keeps issuer integration to chainId + creds only.\n */\nexport interface PafiServiceUrls {\n /**\n * sponsor-relayer base URL (paymaster proxy). PafiBackendClient\n * appends `/paymaster/sponsor`, `/bundler/receipt`, etc.\n */\n sponsorRelayer: string;\n\n /**\n * issuer-api base URL. SettlementClient appends\n * `/issuers/:issuerId/redemption-policy`.\n */\n issuerApi: string;\n}\n\n/**\n * Per-chain URL map. URLs follow the pattern\n * `https://<host>/api/<service>` so Kong gateway can route by path.\n *\n * NOTE: placeholder URLs — Phi will swap in real prod/staging hosts\n * once the deploy targets are finalized. SDK version bump captures\n * the change.\n */\nexport const PAFI_SERVICE_URLS: Record<number, PafiServiceUrls> = {\n // Base mainnet\n 8453: {\n sponsorRelayer: \"https://api-dev.pacificfinance.org/api/sponsor\",\n issuerApi: \"https://api-dev.pacificfinance.org/api/issuer\",\n },\n // Base sepolia\n 84532: {\n sponsorRelayer: \"https://api-dev.pacificfinance.org/api/sponsor\",\n issuerApi: \"https://api-dev.pacificfinance.org/api/issuer\",\n },\n};\n\nexport function getPafiServiceUrls(chainId: number): PafiServiceUrls {\n const urls = PAFI_SERVICE_URLS[chainId];\n if (!urls) {\n throw new Error(\n `getPafiServiceUrls: no PAFI service URLs for chainId ${chainId}. ` +\n `Supported: ${Object.keys(PAFI_SERVICE_URLS).join(\", \")}`,\n );\n }\n return urls;\n}\n","import type {\n ModalOpenOptions,\n PafiWebModalAdapter,\n PafiWebModalHandle,\n} from \"./types\";\n\nconst DEFAULT_WIDTH = 900;\nconst DEFAULT_HEIGHT = 700;\nconst DEFAULT_NAME = \"pafi-web\";\n\n/**\n * Web browser popup adapter. Opens the given URL in a centered\n * `window.open()` popup, polls for close, and optionally forwards\n * `postMessage` events back to the caller.\n *\n * Runtime requirement: `window.open` must exist. Throws if called\n * under Node / SSR / React Native — use `setPafiWebModalAdapter()` to\n * provide a platform-specific adapter in those environments.\n *\n * ## Popup blocking\n *\n * Browsers block `window.open()` unless it happens inside a user\n * gesture (click handler). Callers MUST wire this into a direct click\n * handler — wrapping it in a `setTimeout` or async await before the\n * open call will trigger the blocker.\n */\nexport function openWebPopup(\n url: string,\n options: ModalOpenOptions = {},\n): PafiWebModalHandle {\n if (typeof window === \"undefined\" || typeof window.open !== \"function\") {\n throw new Error(\n \"openWebPopup: `window.open` is not available in this runtime. \" +\n \"Register a platform adapter via setPafiWebModalAdapter() instead.\",\n );\n }\n\n const width = options.width ?? DEFAULT_WIDTH;\n const height = options.height ?? DEFAULT_HEIGHT;\n const name = options.windowName ?? DEFAULT_NAME;\n\n // Center on the screen — works in all major browsers. Falls back\n // gracefully if `screen.availWidth`/`screenX` are unavailable.\n const screenW = window.screen?.availWidth ?? window.innerWidth;\n const screenH = window.screen?.availHeight ?? window.innerHeight;\n const left = Math.max(0, Math.floor((screenW - width) / 2));\n const top = Math.max(0, Math.floor((screenH - height) / 2));\n\n const features = [\n `width=${width}`,\n `height=${height}`,\n `left=${left}`,\n `top=${top}`,\n \"resizable=yes\",\n \"scrollbars=yes\",\n \"noopener=no\", // we need opener.postMessage to work\n ].join(\",\");\n\n const popup = window.open(url, name, features);\n if (!popup) {\n throw new Error(\n \"openWebPopup: popup was blocked. Ensure this call runs inside a user gesture (click handler).\",\n );\n }\n\n // Set up state + listeners --------------------------------------------\n\n let closed = false;\n let pollId: ReturnType<typeof setInterval> | null = null;\n let messageListener: ((e: MessageEvent) => void) | null = null;\n\n const dispose = (): void => {\n if (closed) return;\n closed = true;\n if (pollId !== null) {\n clearInterval(pollId);\n pollId = null;\n }\n if (messageListener) {\n window.removeEventListener(\"message\", messageListener);\n messageListener = null;\n }\n options.onClose?.();\n };\n\n // Poll every 500ms for popup close — there's no `close` event.\n // Stops when `popup.closed` flips to true OR our handle is closed.\n pollId = setInterval(() => {\n if (popup.closed) {\n dispose();\n }\n }, 500);\n\n // Wire postMessage filtering by origin — secure-by-default. An\n // empty `allowedOrigins` would silently reject every message (UI\n // hangs forever waiting for a callback that never arrives), so we\n // throw at construction to surface misconfiguration at startup.\n if (options.onMessage) {\n const allowed = options.allowedOrigins ?? [];\n if (allowed.length === 0) {\n throw new Error(\n \"openPafiWebModal: `allowedOrigins` is empty/missing while `onMessage` \" +\n \"is supplied. The popup-message listener would silently reject every \" +\n \"message — caller must explicitly whitelist PAFI Web's host (e.g. \" +\n \"`['https://app.pacificfinance.org']`) before any payload reaches the \" +\n \"callback. To accept any origin (insecure), pass an explicit list \" +\n \"containing '*' or omit `onMessage` entirely.\",\n );\n }\n const onMessage = options.onMessage;\n messageListener = (event: MessageEvent): void => {\n if (event.source !== popup) return;\n // Allow caller-side wildcard explicitly opt-in; never silently allow.\n if (!allowed.includes(\"*\") && !allowed.includes(event.origin)) return;\n onMessage(event.data, event.origin);\n };\n window.addEventListener(\"message\", messageListener);\n }\n\n // Handle object -------------------------------------------------------\n\n return {\n get isOpen(): boolean {\n return !closed && !popup.closed;\n },\n close(): void {\n if (closed) return;\n try {\n popup.close();\n } catch {\n // Some browsers block close() unless the popup was opened by\n // the same document. Dispose our listeners anyway.\n }\n dispose();\n },\n focus(): void {\n if (closed || popup.closed) return;\n try {\n popup.focus();\n } catch {\n // No-op on failure — focus is best-effort.\n }\n },\n postMessage(data: unknown): void {\n if (closed || popup.closed) return;\n try {\n // targetOrigin '*' is fine here because the caller owns the\n // data being sent. For security on the receiving side, the\n // PAFI Web page should check event.origin itself.\n popup.postMessage(data, \"*\");\n } catch {\n // No-op.\n }\n },\n };\n}\n\n/**\n * The web popup packaged as a {@link PafiWebModalAdapter} so callers\n * can register it explicitly (e.g. in a test harness).\n */\nexport const webPopupAdapter: PafiWebModalAdapter = {\n open(url, options) {\n return openWebPopup(url, options);\n },\n};\n","import { openWebPopup } from \"./webPopup\";\nimport type {\n ModalOpenOptions,\n PafiWebModalAdapter,\n PafiWebModalHandle,\n} from \"./types\";\n\nexport type {\n ModalOpenOptions,\n PafiWebModalAdapter,\n PafiWebModalHandle,\n} from \"./types\";\nexport { openWebPopup, webPopupAdapter } from \"./webPopup\";\n\n/**\n * Module-level adapter registry — allows platform consumers (React\n * Native, Electron, Tauri, etc.) to plug in their own handoff\n * implementation without forking the SDK.\n *\n * Callers set this once at app boot; `openPafiWebModal()` below uses\n * whatever is registered, or falls back to the web popup adapter\n * when `window.open` is available.\n */\nlet registeredAdapter: PafiWebModalAdapter | null = null;\n\n/**\n * Register the adapter used by `openPafiWebModal()`. Typically called\n * once during app initialization — mobile apps register a\n * SFSafariViewController / Chrome Custom Tabs adapter, desktop apps\n * can leave it unset (web popup is the default).\n *\n * Pass `null` to unregister and fall back to the default.\n */\nexport function setPafiWebModalAdapter(\n adapter: PafiWebModalAdapter | null,\n): void {\n registeredAdapter = adapter;\n}\n\n/**\n * Return the currently registered adapter, or `null` when none is set.\n * Useful for tests that want to snapshot-and-restore the adapter.\n */\nexport function getPafiWebModalAdapter(): PafiWebModalAdapter | null {\n return registeredAdapter;\n}\n\n/**\n * Open PAFI Web in the host platform's appropriate UX:\n *\n * - Browser (window.open): centered popup, 900×700 by default\n * - React Native (with adapter registered): SFSafariViewController\n * / Chrome Custom Tabs via `react-native-inappbrowser-reborn` or\n * `expo-web-browser`\n * - Desktop (with adapter registered): custom BrowserWindow / new tab\n *\n * Resolution order:\n * 1. If an adapter was registered via `setPafiWebModalAdapter()`, use it.\n * 2. Else if `window.open` is available, use the built-in web popup.\n * 3. Else throw with a clear error pointing at the adapter registry.\n *\n * @example\n * ```ts\n * // User clicks \"Trade on PAFI\" button\n * button.addEventListener('click', async () => {\n * const modal = await openPafiWebModal('https://app.pacificfinance.org', {\n * allowedOrigins: ['https://app.pacificfinance.org'],\n * onMessage: (data, origin) => {\n * if (typeof data === 'object' && data && 'txHash' in data) {\n * console.log('Swap confirmed:', data.txHash);\n * modal.close();\n * }\n * },\n * onClose: () => {\n * console.log('User closed modal');\n * },\n * });\n * });\n * ```\n */\nexport async function openPafiWebModal(\n url: string,\n options: ModalOpenOptions = {},\n): Promise<PafiWebModalHandle> {\n if (!url || typeof url !== \"string\") {\n throw new Error(\"openPafiWebModal: `url` is required\");\n }\n\n if (registeredAdapter) {\n return Promise.resolve(registeredAdapter.open(url, options));\n }\n\n if (typeof window !== \"undefined\" && typeof window.open === \"function\") {\n return openWebPopup(url, options);\n }\n\n throw new Error(\n \"openPafiWebModal: no adapter registered and `window.open` is unavailable. \" +\n \"Call `setPafiWebModalAdapter()` with a platform-specific adapter (e.g. SFSafariViewController on iOS) during app boot.\",\n );\n}\n"]}
|