@pafi-dev/issuer 0.12.5 → 0.12.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +13 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +13 -9
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/policy/defaultPolicy.ts","../src/auth/memorySessionStore.ts","../src/auth/nonceManager.ts","../src/auth/loginVerifier.ts","../src/auth/errors.ts","../src/auth/jwtMiddleware.ts","../src/auth/rateLimiter.ts","../src/relay/types.ts","../src/relay/relayService.ts","../src/relay/feeManager.ts","../src/indexer/types.ts","../src/indexer/pointIndexer.ts","../src/indexer/burnIndexer.ts","../src/api/handlers.ts","../src/api/handlers/ptRedeemHandler.ts","../src/api/statusHandlers.ts","../src/api/mobileHandlers.ts","../src/userop-store/serialize.ts","../src/userop-store/memoryStore.ts","../src/userop-store/prepareUserOp.ts","../src/pafi-backend/types.ts","../src/pafi-backend/helpers.ts","../src/api/handlers/ptClaimHandler.ts","../src/issuer-state/types.ts","../src/api/handlers/perpDepositHandler.ts","../src/api/delegateHandler.ts","../src/api/issuerApiAdapter.ts","../src/pools/subgraphPoolsProvider.ts","../src/pools/subgraphNativeUsdtQuoter.ts","../src/pools/nativePtQuoter.ts","../src/balance/balanceAggregator.ts","../src/pafi-backend/client.ts","../src/config.ts","../src/redemption/evaluator.ts","../src/redemption/settlementClient.ts","../src/redemption/defaults.ts","../src/redemption/policyProvider.ts","../src/redemption/service.ts","../src/issuer-state/validator.ts","../src/redemption/memoryHistoryStore.ts","../src/index.ts"],"sourcesContent":["import type { Address, PublicClient } from \"viem\";\nimport type { IPointLedger } from \"../ledger/types\";\nimport type { IPolicyEngine, PolicyDecision, PolicyEvalRequest } from \"./types\";\n\n/**\n * Options for constructing a DefaultPolicyEngine.\n *\n * `mintingOracleAddress` and `provider` are optional — if omitted, the\n * engine skips the on-chain cap check (useful for dev/testing).\n *\n * `verifyMintCap` is injectable so tests can simulate cap rejections\n * without needing a real contract.\n */\nexport interface DefaultPolicyEngineOptions {\n ledger: IPointLedger;\n provider?: PublicClient;\n mintingOracleAddress?: Address;\n /**\n * Override the on-chain cap check. Defaults to `@pafi/core`'s\n * `verifyMintCap`, which reverts if the issuer's declared supply would\n * be exceeded. A rejected check should throw; returning normally is pass.\n */\n verifyMintCap?: (\n client: PublicClient,\n oracle: Address,\n issuer: Address,\n amount: bigint,\n ) => Promise<void>;\n /**\n * Resolve a point-token address to the issuer address for the cap check.\n * Required iff `mintingOracleAddress + provider` are supplied.\n */\n resolveIssuer?: (pointToken: Address) => Promise<Address>;\n}\n\n/**\n * Default policy engine — performs two checks:\n *\n * 1. **Off-chain balance** — the user must have at least `amount` of\n * unlocked point balance in the issuer ledger.\n * 2. **On-chain cap** — (optional) calls the MintingOracle via\n * `verifyMintCap` to confirm that minting this amount would not exceed\n * the issuer's declared total supply.\n *\n * Issuers extend this class (or implement `IPolicyEngine` directly) to add\n * KYC, volume caps, claim budgets, or anti-abuse rules.\n */\nexport class DefaultPolicyEngine implements IPolicyEngine {\n private readonly ledger: IPointLedger;\n private readonly provider?: PublicClient;\n private readonly mintingOracleAddress?: Address;\n private readonly verifyMintCap?: DefaultPolicyEngineOptions[\"verifyMintCap\"];\n private readonly resolveIssuer?: DefaultPolicyEngineOptions[\"resolveIssuer\"];\n\n constructor(opts: DefaultPolicyEngineOptions) {\n this.ledger = opts.ledger;\n if (opts.provider) this.provider = opts.provider;\n if (opts.mintingOracleAddress) {\n this.mintingOracleAddress = opts.mintingOracleAddress;\n }\n if (opts.verifyMintCap) this.verifyMintCap = opts.verifyMintCap;\n if (opts.resolveIssuer) this.resolveIssuer = opts.resolveIssuer;\n\n if (!opts.mintingOracleAddress || !opts.provider || !opts.verifyMintCap || !opts.resolveIssuer) {\n if (process.env.NODE_ENV === 'production') {\n throw new Error(\n '[PAFI] DefaultPolicyEngine: on-chain MintingOracle cap check is required in production. ' +\n 'Configure mintingOracleAddress, provider, verifyMintCap, and resolveIssuer.',\n );\n }\n }\n }\n\n async evaluate(request: PolicyEvalRequest): Promise<PolicyDecision> {\n if (request.amount <= 0n) {\n return { approved: false, reason: \"Amount must be positive\" };\n }\n\n // 1. Off-chain balance check — scoped to the requested token so\n // multi-token issuers don't leak balance across tokens.\n const available = await this.ledger.getBalance(\n request.userAddress,\n request.pointTokenAddress,\n );\n if (available < request.amount) {\n return {\n approved: false,\n reason: `Insufficient balance (available=${available}, requested=${request.amount})`,\n };\n }\n\n // 2. On-chain cap check — only runs if fully configured\n if (\n this.mintingOracleAddress &&\n this.provider &&\n this.verifyMintCap &&\n this.resolveIssuer\n ) {\n try {\n const issuer = await this.resolveIssuer(request.pointTokenAddress);\n await this.verifyMintCap(\n this.provider,\n this.mintingOracleAddress,\n issuer,\n request.amount,\n );\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n approved: false,\n reason: `Minting cap check failed: ${msg}`,\n };\n }\n }\n\n return { approved: true };\n }\n}\n","import { randomBytes } from \"node:crypto\";\nimport { getAddress } from \"viem\";\nimport type { Address } from \"viem\";\nimport type { ISessionStore, Session } from \"./types\";\n\nexport interface MemorySessionStoreOptions {\n /**\n * How long a login nonce is valid before auto-expiry. Defaults to 5 min,\n * matching typical SIWE recommendations.\n */\n nonceTtlMs?: number;\n /** Clock override for tests. */\n now?: () => number;\n /**\n * Bypass the production-safety guard. By default, instantiating\n * `MemorySessionStore` when `process.env.NODE_ENV === \"production\"`\n * THROWS — multi-pod K8s deploys do not share Map state, so sessions\n * are not revocable across replicas, and tokens remain valid on pod\n * B after `logout` on pod A. Only set this `true` for single-pod\n * deployments where you've consciously accepted the trade-off.\n */\n dangerouslyAllowMemoryStoreInProduction?: boolean;\n}\n\nconst DEFAULT_NONCE_TTL_MS = 5 * 60 * 1000;\n\n/**\n * In-memory `ISessionStore` — Map-backed nonces and sessions with lazy\n * expiry sweeps on every read/write.\n *\n * Not safe across processes or restarts; intended for dev/test only.\n */\nexport class MemorySessionStore implements ISessionStore {\n private nonces = new Map<string, number>(); // nonce → expiresAt\n private sessions = new Map<string, Session>(); // tokenId → session\n private readonly nonceTtlMs: number;\n private readonly now: () => number;\n\n constructor(opts: MemorySessionStoreOptions = {}) {\n if (\n process.env.NODE_ENV === \"production\" &&\n !opts.dangerouslyAllowMemoryStoreInProduction\n ) {\n throw new Error(\n \"[PAFI] MemorySessionStore refuses to start in production \" +\n \"(NODE_ENV=production). Multi-pod K8s deploys do not share Map \" +\n \"state, so sessions are not revocable across replicas — `logout` \" +\n \"on pod A leaves the token valid on pod B until expiry. Use a \" +\n \"Redis-backed session store (see RedisSessionStoreService in \" +\n \"gg56-backend or implement your own ISessionStore). To bypass \" +\n \"for a single-pod deploy, pass \" +\n \"`dangerouslyAllowMemoryStoreInProduction: true`.\",\n );\n }\n this.nonceTtlMs = opts.nonceTtlMs ?? DEFAULT_NONCE_TTL_MS;\n this.now = opts.now ?? (() => Date.now());\n }\n\n // -------------------------------------------------------------------------\n // Nonces\n // -------------------------------------------------------------------------\n\n async createNonce(): Promise<string> {\n this.purgeExpiredNonces();\n const nonce = randomBytes(16).toString(\"hex\");\n this.nonces.set(nonce, this.now() + this.nonceTtlMs);\n return nonce;\n }\n\n async consumeNonce(nonce: string): Promise<boolean> {\n this.purgeExpiredNonces();\n const expiresAt = this.nonces.get(nonce);\n if (expiresAt === undefined) return false;\n // One-time use — always delete regardless of expiry result.\n this.nonces.delete(nonce);\n return expiresAt > this.now();\n }\n\n // -------------------------------------------------------------------------\n // Sessions\n // -------------------------------------------------------------------------\n\n async createSession(session: Session): Promise<void> {\n this.purgeExpiredSessions();\n const normalized: Session = {\n ...session,\n userAddress: getAddress(session.userAddress),\n };\n this.sessions.set(session.tokenId, normalized);\n }\n\n async getSession(tokenId: string): Promise<Session | null> {\n this.purgeExpiredSessions();\n const session = this.sessions.get(tokenId);\n if (!session) return null;\n if (session.expiresAt.getTime() <= this.now()) {\n this.sessions.delete(tokenId);\n return null;\n }\n return { ...session };\n }\n\n async revokeSession(tokenId: string): Promise<void> {\n this.sessions.delete(tokenId);\n }\n\n async revokeAllSessions(userAddress: Address): Promise<void> {\n const key = getAddress(userAddress);\n for (const [tokenId, session] of this.sessions.entries()) {\n if (session.userAddress === key) {\n this.sessions.delete(tokenId);\n }\n }\n }\n\n // -------------------------------------------------------------------------\n // Housekeeping\n // -------------------------------------------------------------------------\n\n private purgeExpiredNonces(): void {\n const now = this.now();\n for (const [nonce, expiresAt] of this.nonces.entries()) {\n if (expiresAt <= now) this.nonces.delete(nonce);\n }\n }\n\n private purgeExpiredSessions(): void {\n const now = this.now();\n for (const [tokenId, session] of this.sessions.entries()) {\n if (session.expiresAt.getTime() <= now) this.sessions.delete(tokenId);\n }\n }\n}\n","import type { ISessionStore } from \"./types\";\n\n/**\n * Thin wrapper around an `ISessionStore` exposing nonce generation and\n * single-use consumption as its own named surface. AuthService uses it\n * internally, and issuers can also use it directly for other flows that\n * need replay protection (e.g. a CSRF-style challenge).\n */\nexport class NonceManager {\n constructor(private readonly store: ISessionStore) {}\n\n /** Generate a fresh login nonce. The store is responsible for TTL. */\n async generate(): Promise<string> {\n return this.store.createNonce();\n }\n\n /**\n * Atomically validate + consume a nonce. Returns `true` iff the nonce\n * was known and unexpired (and is now removed from the store).\n */\n async consume(nonce: string): Promise<boolean> {\n return this.store.consumeNonce(nonce);\n }\n}\n","import { randomBytes } from \"node:crypto\";\nimport { SignJWT, jwtVerify, errors as joseErrors } from \"jose\";\nimport { getAddress } from \"viem\";\nimport type { Address, Hex } from \"viem\";\nimport { parseLoginMessage, verifyLoginMessage } from \"@pafi-dev/core\";\nimport { AuthError } from \"./errors\";\nimport { NonceManager } from \"./nonceManager\";\nimport type { ISessionStore, Session } from \"./types\";\n\n/**\n * Validate that the JWT signing secret has enough entropy that HS256\n * brute-force is infeasible. A length-only check (`secret.length >= 32`)\n * is insufficient — trivially weak secrets like `\"a\".repeat(32)` or\n * `\"password12345password12345password\"` pass length but have\n * near-zero search space against an attacker.\n *\n * Heuristic (conservative — rejects only obvious weak secrets, never\n * false-positives on real `crypto.randomBytes(32).toString(\"hex\")`):\n *\n * 1. Length ≥ 32 chars.\n * 2. ≥ 16 unique characters (a 32-byte random hex always has 16+).\n * 3. Shannon entropy ≥ 3.5 bits/char (random hex ≈ 4.0).\n * 4. Reject simple repeating patterns (e.g. \"abcabcabc...\").\n *\n * Recommended secret generation:\n * `node -e \"console.log(crypto.randomBytes(32).toString('hex'))\"`\n *\n * Throws on any failure with a descriptive message.\n */\nexport function assertJwtSecretStrength(secret: string | undefined): void {\n if (!secret) {\n throw new Error(\n \"AuthService: jwtSecret is required. Generate via \" +\n \"`node -e \\\"console.log(require('crypto').randomBytes(32).toString('hex'))\\\"`\",\n );\n }\n if (secret.length < 32) {\n throw new Error(\n `AuthService: jwtSecret too short (${secret.length} chars; need ≥ 32). ` +\n \"HS256 brute-force becomes feasible below this threshold.\",\n );\n }\n const uniqueChars = new Set(secret).size;\n if (uniqueChars < 16) {\n throw new Error(\n `AuthService: jwtSecret has only ${uniqueChars} unique characters; need ≥ 16. ` +\n \"A trivially weak secret (e.g. \\\"aaaaa...\\\") will pass length but offer almost no entropy. \" +\n \"Use `crypto.randomBytes(32).toString('hex')`.\",\n );\n }\n const entropy = shannonEntropyBitsPerChar(secret);\n if (entropy < 3.5) {\n throw new Error(\n `AuthService: jwtSecret entropy too low (${entropy.toFixed(2)} bits/char; need ≥ 3.5). ` +\n \"Use `crypto.randomBytes(32).toString('hex')` (≈ 4.0 bits/char).\",\n );\n }\n // Catch repeating patterns that pass uniqueness + entropy but are\n // structurally weak (e.g. \"abcdefgh\".repeat(8) has 8 unique chars but\n // repeats 8 times — already caught by uniqueChars < 16, but defensive).\n for (let period = 1; period <= secret.length / 4; period++) {\n if (secret.length % period !== 0) continue;\n const head = secret.slice(0, period);\n if (secret === head.repeat(secret.length / period)) {\n throw new Error(\n `AuthService: jwtSecret is a repeating pattern of period ${period}. ` +\n \"Use `crypto.randomBytes(32).toString('hex')`.\",\n );\n }\n }\n}\n\nfunction shannonEntropyBitsPerChar(s: string): number {\n const counts = new Map<string, number>();\n for (const c of s) counts.set(c, (counts.get(c) ?? 0) + 1);\n const len = s.length;\n let h = 0;\n for (const n of counts.values()) {\n const p = n / len;\n h -= p * Math.log2(p);\n }\n return h;\n}\n\n/**\n * Decode the `jti` claim from an expired JWT WITHOUT signature verification.\n * Used solely by `logout()` to revoke the session of a token that's already\n * past `exp` — signature must already have been validated upstream when the\n * token was issued; the only attack surface here is a forged token leaking\n * a valid `jti` to revoke someone else's session, which is impossible because\n * `jti` values are 16-byte secrets generated by `randomBytes`.\n *\n * Returns `{ jti: undefined }` on any parse failure.\n */\nfunction decodeExpiredJwtJti(token: string): { jti?: string } {\n try {\n const parts = token.split(\".\");\n if (parts.length !== 3) return {};\n const payloadB64 = parts[1];\n if (!payloadB64) return {};\n // Re-pad base64url\n const padded =\n payloadB64.replace(/-/g, \"+\").replace(/_/g, \"/\") +\n \"===\".slice((payloadB64.length + 3) % 4);\n const json = Buffer.from(padded, \"base64\").toString(\"utf-8\");\n const claims = JSON.parse(json) as { jti?: unknown };\n return typeof claims.jti === \"string\" ? { jti: claims.jti } : {};\n } catch {\n return {};\n }\n}\n\nexport interface AuthServiceConfig {\n sessionStore: ISessionStore;\n /** HMAC secret for signing JWTs (HS256). At least 32 bytes recommended. */\n jwtSecret: string;\n /**\n * JWT lifetime passed to `jose`. Accepts any of `jose`'s time strings\n * (`\"24h\"`, `\"7d\"`, `\"45m\"`, …). Defaults to `\"24h\"`.\n */\n jwtExpiresIn?: string;\n /**\n * Expected domain in the login message, e.g. `\"app.example.com\"`. The\n * SIWE spec requires the frontend to build the message with this exact\n * domain; the backend rejects any mismatch.\n */\n domain: string;\n /** Expected chain id. */\n chainId: number;\n /**\n * Optional `iss` (issuer) claim baked into JWTs and validated on\n * verify. Recommended for multi-issuer deployments to prevent\n * cross-validation if a JWT secret is accidentally shared. Examples:\n * `\"gg56-issuer\"`, `\"issuer.gg56.com\"`. When unset, no `iss` is\n * written or required (back-compat).\n */\n issuer?: string;\n /**\n * Optional `aud` (audience) claim baked into JWTs and validated on\n * verify. Recommended to bind tokens to a specific consumer, e.g.\n * `\"gg56-mobile\"` or `\"gg56-web\"`. When unset, no `aud` is written\n * or required (back-compat).\n */\n audience?: string;\n /** Clock override for tests. */\n now?: () => Date;\n}\n\nexport interface LoginResult {\n token: string;\n userAddress: Address;\n tokenId: string;\n expiresAt: Date;\n}\n\nexport interface AuthContext {\n userAddress: Address;\n chainId: number;\n tokenId: string;\n}\n\ninterface JwtPayload {\n userAddress: Address;\n chainId: number;\n}\n\nconst DEFAULT_EXPIRES_IN = \"24h\";\n\n/**\n * Wallet-based authentication service implementing the EIP-4361 (Sign-In\n * With Ethereum) login flow with server-issued JWTs.\n *\n * Flow:\n * 1. `getNonce()` — client fetches a nonce, stores it locally\n * 2. Client constructs a login message with the nonce + signs with wallet\n * 3. `login(message, signature)` — server validates fields + signature,\n * consumes the nonce, persists a session, returns a JWT\n * 4. Protected endpoints call `verifyToken(token)` to resolve the user\n * context; the session is re-checked on every call, so `logout()`\n * revokes access immediately.\n */\nexport class AuthService {\n private readonly sessionStore: ISessionStore;\n private readonly jwtSecret: Uint8Array;\n private readonly jwtExpiresIn: string;\n private readonly domain: string;\n private readonly chainId: number;\n private readonly issuer?: string;\n private readonly audience?: string;\n private readonly nonceManager: NonceManager;\n private readonly now: () => Date;\n\n constructor(config: AuthServiceConfig) {\n assertJwtSecretStrength(config.jwtSecret);\n this.sessionStore = config.sessionStore;\n this.jwtSecret = new TextEncoder().encode(config.jwtSecret);\n this.jwtExpiresIn = config.jwtExpiresIn ?? DEFAULT_EXPIRES_IN;\n this.domain = config.domain;\n this.chainId = config.chainId;\n this.issuer = config.issuer;\n this.audience = config.audience;\n this.nonceManager = new NonceManager(config.sessionStore);\n this.now = config.now ?? (() => new Date());\n }\n\n // -------------------------------------------------------------------------\n // Public API\n // -------------------------------------------------------------------------\n\n /** Generate a fresh login nonce. */\n async getNonce(): Promise<string> {\n return this.nonceManager.generate();\n }\n\n /**\n * Verify a signed login message and issue a JWT on success.\n * Throws an `AuthError` on any validation failure.\n */\n async login(message: string, signature: Hex): Promise<LoginResult> {\n // 1. Parse the message\n let parsed;\n try {\n parsed = parseLoginMessage(message);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new AuthError(\"INVALID_MESSAGE\", `Could not parse login message: ${msg}`);\n }\n\n // 2. Field checks (before doing any crypto work)\n if (parsed.expirationTime == null) {\n throw new AuthError(\n \"INVALID_MESSAGE\",\n \"login message must include expirationTime\",\n );\n }\n if (parsed.domain !== this.domain) {\n throw new AuthError(\n \"DOMAIN_MISMATCH\",\n `Expected domain \"${this.domain}\", got \"${parsed.domain}\"`,\n );\n }\n if (parsed.chainId !== this.chainId) {\n throw new AuthError(\n \"CHAIN_MISMATCH\",\n `Expected chainId ${this.chainId}, got ${parsed.chainId}`,\n );\n }\n\n const now = this.now();\n if (parsed.notBefore && parsed.notBefore.getTime() > now.getTime()) {\n throw new AuthError(\n \"MESSAGE_NOT_YET_VALID\",\n \"Login message is not yet valid\",\n );\n }\n if (\n parsed.expirationTime &&\n parsed.expirationTime.getTime() <= now.getTime()\n ) {\n throw new AuthError(\"MESSAGE_EXPIRED\", \"Login message has expired\");\n }\n\n // 3. Signature verification\n const verifyResult = await verifyLoginMessage(message, signature);\n if (!verifyResult.valid) {\n throw new AuthError(\n \"SIGNATURE_INVALID\",\n \"Signature does not match the address in the login message\",\n );\n }\n\n // 4. Consume the nonce (one-time use). Order matters: do this AFTER\n // signature verification so a failed signature does not burn a\n // nonce the user can still legitimately spend on a retry.\n const nonceOk = await this.nonceManager.consume(parsed.nonce);\n if (!nonceOk) {\n throw new AuthError(\n \"NONCE_INVALID\",\n \"Nonce is unknown, expired, or already used\",\n );\n }\n\n // 5. Create session + mint JWT\n const userAddress = getAddress(verifyResult.address);\n const tokenId = randomBytes(16).toString(\"hex\");\n const issuedAt = now;\n const expiresAt = parseExpiry(issuedAt, this.jwtExpiresIn);\n\n const session: Session = {\n tokenId,\n userAddress,\n chainId: this.chainId,\n issuedAt,\n expiresAt,\n };\n await this.sessionStore.createSession(session);\n\n let signer = new SignJWT({\n userAddress,\n chainId: this.chainId,\n } satisfies JwtPayload)\n .setProtectedHeader({ alg: \"HS256\" })\n .setJti(tokenId)\n .setIssuedAt(Math.floor(issuedAt.getTime() / 1000))\n .setExpirationTime(Math.floor(expiresAt.getTime() / 1000));\n if (this.issuer) signer = signer.setIssuer(this.issuer);\n if (this.audience) signer = signer.setAudience(this.audience);\n const token = await signer.sign(this.jwtSecret);\n\n return { token, userAddress, tokenId, expiresAt };\n }\n\n /** Revoke the session backing the given JWT (logout). */\n async logout(token: string): Promise<void> {\n // STEP 1 — verify the JWT signature. We tolerate expiry (logout\n // immediately after expiry should still revoke the session-store\n // entry), but signature failure / malformed tokens MUST throw —\n // accepting them would let attackers issue 200 responses against\n // forged tokens and hide signature errors from monitoring.\n let payload;\n try {\n const result = await jwtVerify(token, this.jwtSecret, {\n clockTolerance: 60, // allow logout right after expiry\n ...(this.issuer ? { issuer: this.issuer } : {}),\n ...(this.audience ? { audience: this.audience } : {}),\n });\n payload = result.payload;\n } catch (err) {\n // Expired tokens: still allow revocation (decode the jti without\n // verification — `jose` v5 rejects expired tokens via JWTExpired\n // even with clockTolerance if the gap is large).\n if (err instanceof joseErrors.JWTExpired) {\n const decoded = decodeExpiredJwtJti(token);\n if (decoded.jti) {\n try {\n await this.sessionStore.revokeSession(decoded.jti);\n } catch (storeErr) {\n // Storage error during revocation — log only; logout is\n // idempotent so a missing session row is fine.\n this.logSessionStoreError(storeErr);\n }\n }\n return;\n }\n // ANY other JWT failure (signature verification, malformed,\n // missing claims) → throw. Forged tokens MUST NOT receive a 200.\n if (\n err instanceof joseErrors.JWSSignatureVerificationFailed ||\n err instanceof joseErrors.JWSInvalid ||\n err instanceof joseErrors.JWTInvalid\n ) {\n throw new AuthError(\"TOKEN_INVALID\", \"JWT verification failed\");\n }\n // Unknown class — be conservative and reject.\n throw new AuthError(\n \"TOKEN_INVALID\",\n `JWT verification failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n // STEP 2 — verified payload, now revoke the session entry. Storage\n // errors here are swallowed (logout idempotent).\n if (payload.jti) {\n try {\n await this.sessionStore.revokeSession(payload.jti);\n } catch (storeErr) {\n this.logSessionStoreError(storeErr);\n }\n }\n }\n\n private logSessionStoreError(err: unknown): void {\n const msg = err instanceof Error ? err.message : String(err);\n // \"not found\" is benign — session already revoked or expired.\n if (msg.includes(\"not found\")) return;\n console.error(\"[PAFI] AuthService logout: session store error\", err);\n }\n\n /**\n * Verify a JWT and return the authenticated user context. Throws an\n * `AuthError` if the token is missing, malformed, expired, revoked, or\n * signed by a different key.\n */\n async verifyToken(token: string): Promise<AuthContext> {\n let payload;\n try {\n const result = await jwtVerify(token, this.jwtSecret, {\n ...(this.issuer ? { issuer: this.issuer } : {}),\n ...(this.audience ? { audience: this.audience } : {}),\n });\n payload = result.payload;\n } catch (err) {\n if (err instanceof joseErrors.JWTExpired) {\n throw new AuthError(\"TOKEN_EXPIRED\", \"JWT has expired\");\n }\n if (\n err instanceof joseErrors.JWTInvalid ||\n err instanceof joseErrors.JWSInvalid ||\n err instanceof joseErrors.JWSSignatureVerificationFailed\n ) {\n throw new AuthError(\"TOKEN_INVALID\", \"JWT is invalid\");\n }\n throw new AuthError(\"TOKEN_INVALID\", \"JWT verification failed\");\n }\n\n const tokenId = payload.jti;\n if (!tokenId) {\n throw new AuthError(\"TOKEN_INVALID\", \"JWT is missing jti claim\");\n }\n\n const session = await this.sessionStore.getSession(tokenId);\n if (!session) {\n throw new AuthError(\n \"SESSION_REVOKED\",\n \"Session is no longer active (revoked or expired)\",\n );\n }\n\n const userAddress = (payload as unknown as JwtPayload).userAddress;\n const chainId = (payload as unknown as JwtPayload).chainId;\n if (!userAddress || typeof chainId !== \"number\") {\n throw new AuthError(\"TOKEN_INVALID\", \"JWT payload is malformed\");\n }\n\n return {\n userAddress: getAddress(userAddress),\n chainId,\n tokenId,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Parse a duration string in the minimal form `<number><unit>` where unit is\n * one of `s`, `m`, `h`, `d`. Matches the subset of `jose`'s time-string\n * format that AuthService needs to compute the absolute expiry.\n */\nfunction parseExpiry(from: Date, expiresIn: string): Date {\n const match = expiresIn.match(/^(\\d+)\\s*(s|m|h|d)$/i);\n if (!match) {\n throw new Error(\n `AuthService: unsupported jwtExpiresIn \"${expiresIn}\" — use e.g. \"24h\"`,\n );\n }\n const n = Number(match[1]);\n const unit = match[2]!.toLowerCase();\n const multiplier =\n unit === \"s\" ? 1000 : unit === \"m\" ? 60_000 : unit === \"h\" ? 3_600_000 : 86_400_000;\n return new Date(from.getTime() + n * multiplier);\n}\n","import { PafiSdkError, type SdkErrorHttpStatus } from \"../errors\";\n\n/**\n * Error codes surfaced by the auth subsystem. Issuers can pattern-match on\n * `err.code` to translate into framework-specific HTTP responses.\n */\nexport type AuthErrorCode =\n | \"INVALID_MESSAGE\" // Could not parse the login message\n | \"DOMAIN_MISMATCH\" // Message domain does not match expected\n | \"CHAIN_MISMATCH\" // Message chainId does not match expected\n | \"NONCE_INVALID\" // Unknown, expired, or already-consumed nonce\n | \"MESSAGE_EXPIRED\" // Login message past its expirationTime\n | \"MESSAGE_NOT_YET_VALID\" // Login message before its notBefore\n | \"SIGNATURE_INVALID\" // Signature did not recover to the claimed address\n | \"MISSING_TOKEN\" // No Authorization header\n | \"MALFORMED_TOKEN\" // Header present but not \"Bearer <token>\"\n | \"TOKEN_INVALID\" // JWT verification failed (bad sig, tampered)\n | \"TOKEN_EXPIRED\" // JWT past its exp claim\n | \"SESSION_REVOKED\"; // JWT ok but backing session is gone\n\n/**\n * Map auth error codes → HTTP status the issuer's controller should\n * surface. `forbidden` for any signature / token integrity failure\n * (caller proved nothing); `unprocessable` for content/format errors\n * (caller's payload didn't validate); `not_found` for missing session.\n */\nfunction statusForCode(code: AuthErrorCode): SdkErrorHttpStatus {\n switch (code) {\n case \"INVALID_MESSAGE\":\n case \"DOMAIN_MISMATCH\":\n case \"CHAIN_MISMATCH\":\n case \"MESSAGE_EXPIRED\":\n case \"MESSAGE_NOT_YET_VALID\":\n case \"MALFORMED_TOKEN\":\n return \"unprocessable\";\n case \"NONCE_INVALID\":\n case \"SIGNATURE_INVALID\":\n case \"MISSING_TOKEN\":\n case \"TOKEN_INVALID\":\n case \"TOKEN_EXPIRED\":\n return \"forbidden\";\n case \"SESSION_REVOKED\":\n return \"not_found\";\n }\n}\n\n/**\n * Extends `PafiSdkError` so issuer controllers can route auth failures\n * through the same `createSdkErrorMapper` as everything else (no\n * separate `instanceof AuthError` check needed).\n */\nexport class AuthError extends PafiSdkError {\n readonly code: AuthErrorCode;\n readonly httpStatus: SdkErrorHttpStatus;\n\n constructor(code: AuthErrorCode, message: string) {\n super(message);\n this.code = code;\n this.httpStatus = statusForCode(code);\n }\n}\n","import { AuthError } from \"./errors\";\nimport type { AuthContext, AuthService } from \"./loginVerifier\";\n\n/**\n * Extracts a Bearer token from an `Authorization` header value and verifies\n * it via the supplied `AuthService`. Framework-agnostic — issuers wrap this\n * in their framework's middleware pattern (Express, Fastify, Hono, …).\n *\n * Throws an `AuthError` if the header is missing, malformed, or the token\n * fails verification. Callers should translate the `err.code` into an\n * appropriate HTTP status (401 for auth failures, 500 for anything else).\n */\nexport async function authenticateRequest(\n authHeader: string | undefined,\n authService: AuthService,\n): Promise<AuthContext> {\n if (!authHeader) {\n throw new AuthError(\n \"MISSING_TOKEN\",\n \"Authorization header is required\",\n );\n }\n\n const match = authHeader.match(/^Bearer\\s+(\\S+)\\s*$/i);\n if (!match) {\n throw new AuthError(\n \"MALFORMED_TOKEN\",\n \"Authorization header must be in the form 'Bearer <token>'\",\n );\n }\n\n const token = match[1]!;\n return authService.verifyToken(token);\n}\n","/**\n * Per-key rate limiting for unauthenticated public endpoints.\n *\n * `/auth/nonce` and `/auth/login` are unauthenticated and CPU-bound\n * (SIWE message parsing + ECDSA `recover`), so without rate limiting\n * an attacker can saturate the issuer at low cost. Worse, on\n * `/auth/nonce` they can flood the session store with single-use\n * nonces; with `MemorySessionStore` this is a pure DoS.\n *\n * Issuers wire a `IRateLimiter` impl in `IssuerApiHandlersConfig` —\n * `MemoryRateLimiter` is the default reference impl (single-pod, dev).\n * Production deployments use a Redis-backed impl that shares state\n * across replicas (similar pattern to `ISessionStore`).\n *\n * The \"key\" is up to the caller — typically the client IP, but the\n * issuer's HTTP layer chooses (could be userAddress on POST /login).\n */\n\nexport interface IRateLimiter {\n /**\n * Check + increment a counter under `key`. Returns `{ allowed: false,\n * retryAfterMs }` when the caller has exceeded its quota for this\n * window. Default config is action-specific (see\n * `RateLimiterConfig`).\n *\n * Idempotent in the sense that successive calls with the same `key`\n * within the same window count toward the limit; a separate window\n * starts after `windowMs` elapses.\n */\n consume(\n key: string,\n action: RateLimitAction,\n ): Promise<{ allowed: boolean; retryAfterMs?: number }>;\n}\n\nexport type RateLimitAction = \"auth_nonce\" | \"auth_login\";\n\nexport interface RateLimiterConfig {\n /**\n * Per-key max requests per `windowMs`. Defaults:\n * - auth_nonce: 1 per 2s (30/min)\n * - auth_login: 5 per minute\n *\n * Issuers can tune per their threat model.\n */\n limits?: Partial<\n Record<RateLimitAction, { max: number; windowMs: number }>\n >;\n /** Clock override for tests. */\n now?: () => number;\n}\n\nconst DEFAULT_LIMITS: Record<\n RateLimitAction,\n { max: number; windowMs: number }\n> = {\n auth_nonce: { max: 30, windowMs: 60_000 }, // 30 nonces/min ≈ 1 per 2s\n auth_login: { max: 5, windowMs: 60_000 }, // 5 logins/min\n};\n\n/**\n * In-memory `IRateLimiter` — Map of `{key, action}` → count + window\n * start. Lazy expiry on read. Single-process: NOT safe for multi-pod\n * deployments. Use a Redis impl in production.\n */\nexport class MemoryRateLimiter implements IRateLimiter {\n private buckets = new Map<\n string,\n { count: number; windowStartedAt: number }\n >();\n private readonly limits: Record<\n RateLimitAction,\n { max: number; windowMs: number }\n >;\n private readonly now: () => number;\n\n constructor(config: RateLimiterConfig = {}) {\n if (\n process.env.NODE_ENV === \"production\" &&\n !process.env.PAFI_ALLOW_MEMORY_RATE_LIMITER_IN_PROD\n ) {\n // eslint-disable-next-line no-console\n console.warn(\n \"[PAFI] MemoryRateLimiter not safe for multi-pod K8s deploys — \" +\n \"rate counters are NOT shared across replicas, allowing \" +\n \"round-robin bypass. Use a Redis-backed IRateLimiter in production.\",\n );\n }\n this.limits = {\n ...DEFAULT_LIMITS,\n ...(config.limits ?? {}),\n } as typeof DEFAULT_LIMITS;\n this.now = config.now ?? (() => Date.now());\n }\n\n async consume(\n key: string,\n action: RateLimitAction,\n ): Promise<{ allowed: boolean; retryAfterMs?: number }> {\n const limit = this.limits[action];\n if (!limit) return { allowed: true };\n\n const bucketKey = `${action}:${key}`;\n const now = this.now();\n const bucket = this.buckets.get(bucketKey);\n\n // Fresh bucket OR window expired → reset.\n if (!bucket || now - bucket.windowStartedAt >= limit.windowMs) {\n this.buckets.set(bucketKey, { count: 1, windowStartedAt: now });\n return { allowed: true };\n }\n\n if (bucket.count < limit.max) {\n bucket.count += 1;\n return { allowed: true };\n }\n\n const retryAfterMs = Math.max(\n 0,\n bucket.windowStartedAt + limit.windowMs - now,\n );\n return { allowed: false, retryAfterMs };\n }\n\n /**\n * Test helper — clear all buckets. Not part of `IRateLimiter`; only\n * exposed on the in-memory impl for unit tests.\n */\n reset(): void {\n this.buckets.clear();\n }\n}\n\n/**\n * Convenience: a no-op `IRateLimiter` that lets every request through.\n * Useful as a default in `IssuerApiHandlersConfig` when the issuer\n * hasn't wired a rate limiter yet (back-compat) — emits a one-time\n * warning at construction so consumers notice.\n */\nexport class NoopRateLimiter implements IRateLimiter {\n private warned = false;\n\n async consume(): Promise<{ allowed: boolean }> {\n if (!this.warned && process.env.NODE_ENV === \"production\") {\n // eslint-disable-next-line no-console\n console.warn(\n \"[PAFI] NoopRateLimiter active — `/auth/nonce` and `/auth/login` \" +\n \"are NOT throttled. Wire a `MemoryRateLimiter` (dev) or Redis-backed \" +\n \"impl (prod) via `IssuerApiHandlersConfig.rateLimiter`.\",\n );\n this.warned = true;\n }\n return { allowed: true };\n }\n}\n","import { PafiSdkError } from \"../errors\";\n\nexport type RelayErrorCode = \"ENCODE_FAILED\"; // calldata encoding / signing threw\n\nexport class RelayError extends PafiSdkError {\n readonly httpStatus = \"unprocessable\" as const;\n readonly code: RelayErrorCode;\n\n constructor(code: RelayErrorCode, message: string, cause?: unknown) {\n super(message);\n this.code = code;\n if (cause !== undefined) {\n // Attach via assignment — `Error.cause` is ES2022 standard but the\n // base class doesn't take it via constructor. Cast to unknown so\n // TS doesn't complain about widening readonly.\n (this as { cause?: unknown }).cause = cause;\n }\n }\n}\n","import {\n encodeFunctionData,\n erc20Abi,\n type Address,\n type Hex,\n type WalletClient,\n} from \"viem\";\nimport type { PublicClient } from \"viem\";\nimport {\n POINT_TOKEN_V2_ABI,\n mintFeeWrapperAbi,\n buildPartialUserOperation,\n signMintRequest,\n getContractAddresses,\n quoteOperatorFeePt,\n type Operation,\n type PartialUserOperation,\n type PointTokenDomainConfig,\n type BurnRequest,\n} from \"@pafi-dev/core\";\nimport { RelayError } from \"./types\";\n\n/**\n * Optional config that lets `RelayService` auto-quote the operator fee\n * + auto-resolve the recipient when callers don't pass `feeAmount` /\n * `feeRecipient`. When both `provider` + `chainId` are set, callers can\n * omit the fee params entirely; the service will:\n *\n * 1. Resolve `feeRecipient = getContractAddresses(chainId).pafiFeeRecipient`\n * 2. If `feeAmount` is undefined → run `quoteOperatorFeePt(...)` against\n * Chainlink + V4 subgraph to compute the PT amount.\n *\n * When unset, the legacy \"caller passes feeAmount + feeRecipient or no\n * fee\" behavior applies, so existing integrations keep working.\n */\nexport interface RelayServiceConfig {\n provider?: PublicClient;\n chainId?: number;\n}\n\n/**\n * Builds unsigned `PartialUserOperation` payloads for the v1.4 sponsored\n * flow. The service is stateless and HTTP-client-free:\n *\n * - `prepareMint` signs a `MintRequest` EIP-712 with the caller-supplied\n * issuer signer wallet, then encodes `PointToken.mint(to, amount,\n * deadline, minterSig)` into a UserOp the frontend submits via\n * EIP-7702 + Paymaster.\n * - `prepareBurn` mirrors the above on the burn side using a\n * pre-signed `BurnRequest` + `PointToken.burn(from, amount,\n * deadline, burnerSig)`.\n *\n * There is no broadcasting, no operator wallet, no simulation — those\n * concerns moved to the Bundler + Paymaster in v1.4.\n */\nexport class RelayService {\n private readonly provider: PublicClient | undefined;\n private readonly chainId: number | undefined;\n\n constructor(config: RelayServiceConfig = {}) {\n this.provider = config.provider;\n this.chainId = config.chainId;\n }\n\n /**\n * Resolve the fee recipient + amount applied to the next UserOp:\n *\n * - If caller passed an explicit `feeRecipient`, use it (testing\n * only — sponsor-relayer's L1 will reject any non-canonical\n * recipient with `INSUFFICIENT_FEE`). Otherwise, default to\n * `getContractAddresses(chainId).pafiFeeRecipient` when the\n * service has a `chainId` configured.\n * - If caller passed `feeAmount`, use it. Otherwise, when the\n * service has both `provider` + `chainId`, auto-quote via\n * `quoteOperatorFeePt`.\n * - When the service is unconfigured AND caller passed nothing,\n * return `{ feeAmount: 0n, feeRecipient: undefined }` — legacy\n * \"no fee\" behavior, caller must opt in for the gas-reimbursement\n * transfer to be added to the batch.\n */\n private async resolveFee(params: {\n feeAmount?: bigint;\n feeRecipient?: Address;\n pointTokenAddress: Address;\n }): Promise<{ feeAmount: bigint; feeRecipient: Address | undefined }> {\n const feeRecipient =\n params.feeRecipient ??\n (this.chainId !== undefined\n ? getContractAddresses(this.chainId).pafiFeeRecipient\n : undefined);\n\n if (params.feeAmount !== undefined) {\n return { feeAmount: params.feeAmount, feeRecipient };\n }\n\n if (this.provider && this.chainId !== undefined) {\n // Enable stale fallback: if the subgraph has no pool for this PT yet\n // (e.g. brand-new clone before any liquidity is added) the quoter\n // would otherwise throw `OracleStaleError(\"subgraph\", ...)` which\n // surfaces as \"pafiToken or pool not found\" and blocks every mint\n // until ops deploys + seeds the V4 pool. With fallback enabled the\n // quoter uses the default 0.1 USDT/PT price (and the onWarning\n // hook on `IssuerApiAdapter` logs the fallback for ops visibility).\n const feeAmount = await quoteOperatorFeePt({\n provider: this.provider,\n chainId: this.chainId,\n pointTokenAddress: params.pointTokenAddress,\n allowStaleFallback: true,\n onFallback: (info) => {\n // eslint-disable-next-line no-console\n console.warn(\n `[RelayService] operatorFeeQuoter fallback (${info.source}): ${info.reason} → using ${info.fallbackValue}`,\n );\n },\n });\n return { feeAmount, feeRecipient };\n }\n\n return { feeAmount: 0n, feeRecipient };\n }\n\n /**\n * Build an unsigned UserOp for Scenario 1 (Mint) — sig-gated\n * `PointToken.mint(to, amount, deadline, minterSig)`.\n */\n async prepareMint(params: PrepareMintParams): Promise<PartialUserOperation> {\n if (!params.batchExecutorAddress) {\n throw new RelayError(\n \"ENCODE_FAILED\",\n \"prepareMint: batchExecutorAddress required\",\n );\n }\n if (!params.userAddress) {\n throw new RelayError(\"ENCODE_FAILED\", \"prepareMint: userAddress required\");\n }\n if (!params.pointTokenAddress) {\n throw new RelayError(\n \"ENCODE_FAILED\",\n \"prepareMint: pointTokenAddress required\",\n );\n }\n if (params.amount <= 0n) {\n throw new RelayError(\"ENCODE_FAILED\", \"prepareMint: amount must be positive\");\n }\n if (!params.issuerSignerWallet) {\n throw new RelayError(\n \"ENCODE_FAILED\",\n \"prepareMint: issuerSignerWallet required (for MintRequest EIP-712 signature)\",\n );\n }\n if (params.deadline <= 0n) {\n throw new RelayError(\"ENCODE_FAILED\", \"prepareMint: deadline must be positive\");\n }\n const nowSecs = BigInt(Math.floor(Date.now() / 1000));\n if (params.deadline <= nowSecs) {\n throw new RelayError(\"ENCODE_FAILED\", \"prepareMint: deadline is in the past\");\n }\n const MAX_DEADLINE_WINDOW = 3600n; // 1 hour\n if (params.deadline > nowSecs + MAX_DEADLINE_WINDOW) {\n throw new RelayError(\n \"ENCODE_FAILED\",\n \"prepareMint: deadline exceeds maximum allowed window (1 hour)\",\n );\n }\n\n // 1. Sign MintForRequest with the issuer minter signer.\n //\n // v1.6 typehash: MintForRequest(user, receiver, amount, nonce, deadline)\n // - `user` = the off-chain spender (drives nonce stream).\n // - `receiver` = on-chain msg.sender of `PointToken.mint(...)`.\n //\n // Direct path: receiver = user (user calls PointToken.mint themselves)\n // Wrapper-mediated path: receiver = wrapper, user = end-user\n //\n // The `mintFeeWrapperAddress` param flips between the two; when unset\n // the path defaults to direct mint for backward compat with deployments\n // that haven't onboarded the wrapper yet.\n const useWrapper = params.mintFeeWrapperAddress !== undefined;\n const receiverForSig: Address = useWrapper\n ? params.mintFeeWrapperAddress!\n : params.userAddress;\n\n let minterSig: Hex;\n try {\n const sig = await signMintRequest(\n params.issuerSignerWallet,\n params.domain,\n {\n user: params.userAddress,\n receiver: receiverForSig,\n amount: params.amount,\n nonce: params.mintRequestNonce,\n deadline: params.deadline,\n },\n );\n minterSig = sig.serialized;\n } catch (err) {\n throw new RelayError(\n \"ENCODE_FAILED\",\n `prepareMint: failed to sign MintForRequest: ${errorMessage(err)}`,\n err,\n );\n }\n\n // 2. Encode the mint call.\n // - Direct: PointToken.mint(user, amount, deadline, sig)\n // - Wrapper-mediated: MintFeeWrapper.mintWithFee(pointToken, user, gross, deadline, sig)\n let mintCallData: Hex;\n let mintTarget: Address;\n try {\n if (useWrapper) {\n mintCallData = encodeFunctionData({\n abi: mintFeeWrapperAbi,\n functionName: \"mintWithFee\",\n args: [\n params.pointTokenAddress,\n params.userAddress,\n params.amount,\n params.deadline,\n minterSig,\n ],\n });\n mintTarget = params.mintFeeWrapperAddress!;\n } else {\n mintCallData = encodeFunctionData({\n abi: POINT_TOKEN_V2_ABI,\n functionName: \"mint\",\n args: [params.userAddress, params.amount, params.deadline, minterSig],\n });\n mintTarget = params.pointTokenAddress;\n }\n } catch (err) {\n throw new RelayError(\n \"ENCODE_FAILED\",\n `prepareMint: failed to encode mint call: ${errorMessage(err)}`,\n err,\n );\n }\n\n const operations: Operation[] = [\n {\n target: mintTarget,\n value: 0n,\n data: mintCallData,\n },\n ];\n\n // 3. Operator fee transfer (PT reimbursement to PAFI for sponsored\n // gas). Resolved via `resolveFee` — auto-quotes when the service\n // was constructed with provider+chainId and the caller didn't pass\n // an explicit `feeAmount`. Pass `feeAmount: 0n` to opt out.\n const { feeAmount, feeRecipient } = await this.resolveFee({\n feeAmount: params.feeAmount,\n feeRecipient: params.feeRecipient,\n pointTokenAddress: params.pointTokenAddress,\n });\n if (feeAmount > 0n) {\n if (!feeRecipient) {\n throw new RelayError(\n \"ENCODE_FAILED\",\n \"prepareMint: feeRecipient could not be resolved — pass `feeRecipient` explicitly or construct RelayService with a `chainId`.\",\n );\n }\n if (feeRecipient === \"0x0000000000000000000000000000000000000000\") {\n throw new RelayError(\n \"ENCODE_FAILED\",\n \"prepareMint: feeRecipient must not be zero address\",\n );\n }\n operations.push({\n target: params.pointTokenAddress,\n value: 0n,\n data: encodeFunctionData({\n abi: erc20Abi,\n functionName: \"transfer\",\n args: [feeRecipient, feeAmount],\n }),\n });\n }\n\n return buildPartialUserOperation({\n sender: params.userAddress,\n nonce: params.aaNonce,\n operations,\n gasLimits: {\n callGasLimit: params.callGasLimit ?? 300_000n,\n verificationGasLimit: params.verificationGasLimit ?? 150_000n,\n preVerificationGas: params.preVerificationGas ?? 50_000n,\n },\n });\n }\n\n /**\n * Build an unsigned UserOp for Scenario 2 (Burn/Redeem) — sig-gated\n * `PointToken.burn(from, amount, deadline, burnerSig)`. Caller\n * provides a pre-signed `BurnRequest` + sig bytes (typically from\n * `PTRedeemHandler`).\n *\n * Direct burn (no sig) was dropped in v1.4 — every burn now goes\n * through the issuer-signed `BurnRequest` path.\n */\n async prepareBurn(params: PrepareBurnParams): Promise<PartialUserOperation> {\n if (!params.pointTokenAddress) {\n throw new RelayError(\"ENCODE_FAILED\", \"prepareBurn: pointTokenAddress required\");\n }\n if (!params.batchExecutorAddress) {\n throw new RelayError(\n \"ENCODE_FAILED\",\n \"prepareBurn: batchExecutorAddress required\",\n );\n }\n if (!params.burnRequest || !params.burnerSignature) {\n throw new RelayError(\n \"ENCODE_FAILED\",\n \"prepareBurn: burnRequest + burnerSignature required\",\n );\n }\n\n let burnCallData: Hex;\n try {\n burnCallData = encodeFunctionData({\n abi: POINT_TOKEN_V2_ABI,\n functionName: \"burn\",\n args: [\n params.burnRequest.from,\n params.burnRequest.amount,\n params.burnRequest.deadline,\n params.burnerSignature,\n ],\n });\n } catch (err) {\n throw new RelayError(\n \"ENCODE_FAILED\",\n `prepareBurn: failed to encode burn call: ${errorMessage(err)}`,\n err,\n );\n }\n\n // Burn ops: fee transfer (if any) FIRST, then burn.\n // Fee comes out of the user's `amount` PT before the burn, so a user\n // holding exactly `amount` can complete the flow. BurnRequest is\n // signed for `amount - fee` upstream.\n const operations: Operation[] = [];\n\n const { feeAmount, feeRecipient } = await this.resolveFee({\n feeAmount: params.feeAmount,\n feeRecipient: params.feeRecipient,\n pointTokenAddress: params.pointTokenAddress,\n });\n if (feeAmount > 0n) {\n if (!feeRecipient) {\n throw new RelayError(\n \"ENCODE_FAILED\",\n \"prepareBurn: feeRecipient could not be resolved — pass `feeRecipient` explicitly or construct RelayService with a `chainId`.\",\n );\n }\n if (feeRecipient === \"0x0000000000000000000000000000000000000000\") {\n throw new RelayError(\n \"ENCODE_FAILED\",\n \"prepareBurn: feeRecipient must not be zero address\",\n );\n }\n operations.push({\n target: params.pointTokenAddress,\n value: 0n,\n data: encodeFunctionData({\n abi: erc20Abi,\n functionName: \"transfer\",\n args: [feeRecipient, feeAmount],\n }),\n });\n }\n\n operations.push({\n target: params.pointTokenAddress,\n value: 0n,\n data: burnCallData,\n });\n\n return buildPartialUserOperation({\n sender: params.userAddress,\n nonce: params.aaNonce,\n operations,\n gasLimits: {\n callGasLimit: params.callGasLimit ?? 300_000n,\n verificationGasLimit: params.verificationGasLimit ?? 150_000n,\n preVerificationGas: params.preVerificationGas ?? 50_000n,\n },\n });\n }\n}\n\n// ===========================================================================\n// Parameter types\n// ===========================================================================\n\n/**\n * v1.4 — sig-gated `PointToken.mint(to, amount, deadline, minterSig)`.\n *\n * The issuer backend validates off-chain (balance, policy, KYC), signs\n * a `MintRequest` EIP-712 with its minter signer, and packages the\n * whole thing into a UserOp for the user to submit via EIP-7702 +\n * Paymaster. On confirmation, PointIndexer watches `Transfer(0x0, user,\n * amount)` and resolves the ledger lock.\n */\nexport interface PrepareMintParams {\n /** User EOA that will send the UserOp (via EIP-7702 delegation). */\n userAddress: Address;\n /** ERC-4337 account nonce. Caller fetches from EntryPoint v0.7. */\n aaNonce: bigint;\n /** BatchExecutor delegation target (chain-specific). */\n batchExecutorAddress: Address;\n /** PointToken contract — the call target + EIP-712 verifying contract. */\n pointTokenAddress: Address;\n /** PT amount to mint to `userAddress`. */\n amount: bigint;\n /**\n * Issuer minter signer wallet — signs the `MintRequest` EIP-712.\n * Must be added to `PointToken.minters[]` via `addMinter(signerAddr)`\n * at provisioning time. Typically HSM/KMS-backed in prod.\n */\n issuerSignerWallet: WalletClient;\n /** EIP-712 domain for MintRequest. */\n domain: PointTokenDomainConfig;\n /** Current `mintRequestNonces[userAddress]` — caller reads from contract. */\n mintRequestNonce: bigint;\n /** Unix timestamp after which the signature expires. */\n deadline: bigint;\n /**\n * Optional v1.6+ — when set, mint is routed through MintFeeWrapper:\n * - sig.receiver = wrapper address (vs `userAddress` for direct path)\n * - calldata target = wrapper.mintWithFee(pointToken, user, gross, ...)\n * - the wrapper then mints `amount` to itself, splits per recipient list,\n * and forwards net to the user.\n *\n * Leave undefined to keep the v1.5 direct-mint behavior (no fee skim).\n * Wrapper must be registered on the PointToken via\n * `IssuerRegistry.addIssuer` cascade (or owner-only `wrapper.registerToken`)\n * before this path works on-chain.\n */\n mintFeeWrapperAddress?: Address;\n /**\n * Optional — application-level fee transfer appended after mint.\n * Set both `feeAmount` and `feeRecipient` together.\n */\n feeAmount?: bigint;\n feeRecipient?: Address;\n /** Gas limits — defaults are conservative; caller can tighten. */\n callGasLimit?: bigint;\n verificationGasLimit?: bigint;\n preVerificationGas?: bigint;\n}\n\n/**\n * v1.4 — sig-gated burn only. Direct (no-sig) burn was dropped because\n * every burn now goes through `BurnRequest` EIP-712 signed by the\n * issuer burner. The `mode: 'burnWithSig'` discriminant is preserved\n * for backwards-compat with existing callers but is no longer\n * branched on internally.\n */\nexport interface PrepareBurnParams {\n userAddress: Address;\n aaNonce: bigint;\n pointTokenAddress: Address;\n batchExecutorAddress: Address;\n /** Discriminant kept for backwards compat. Always `'burnWithSig'`. */\n mode?: \"burnWithSig\";\n /** BurnRequest message the issuer burner signer signed. */\n burnRequest: BurnRequest;\n /** Serialized EIP-712 signature (bytes) over `burnRequest`. */\n burnerSignature: Hex;\n /**\n * Optional — application-level PT fee transfer appended after burn.\n * Used for gas reimbursement on the sponsored path. Set both\n * `feeAmount` and `feeRecipient` together. User must hold\n * `burnAmount + feeAmount` PT.\n */\n feeAmount?: bigint;\n feeRecipient?: Address;\n callGasLimit?: bigint;\n verificationGasLimit?: bigint;\n preVerificationGas?: bigint;\n}\n\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n","import type { PublicClient } from \"viem\";\n\nexport interface FeeManagerConfig {\n /** Provider used for gas price reads. */\n provider: PublicClient;\n /**\n * Typical gas used by a single sponsored UserOp. Default: 500_000.\n * The manager multiplies this by current gas price to get native\n * cost, then converts via the injected `quoteNativeToFee`.\n */\n gasUnits?: bigint;\n /**\n * Safety margin applied before charging the user, as basis points.\n * 12_000 = 120%. Default: 12_000.\n */\n gasPremiumBps?: number;\n /**\n * Quote function — given an amount of native wei, return the\n * equivalent amount in the fee currency (PT raw units in v1.4,\n * USDT 6-decimal in legacy v1.2 flows).\n *\n * Injected so the manager stays chain- and token-agnostic. Issuers\n * wire it to `@pafi-dev/core` V4 quoting, a subgraph query, or an\n * oracle feed.\n */\n quoteNativeToFee: (amountNative: bigint) => Promise<bigint>;\n}\n\nconst DEFAULT_GAS_UNITS = 500_000n;\nconst DEFAULT_PREMIUM_BPS = 12_000; // 120%\n\n/**\n * Computes how much fee to collect from the user to cover the gas cost\n * of a sponsored UserOp.\n *\n * ## v1.4 scope change\n *\n * The fee is now expressed in the **fee currency** chosen by the caller\n * (PT for mint/burn, USDT for swap/perp_deposit) — not hardcoded to USDT.\n *\n * **Operator rebalancing is gone.** In v1.4 the operator no longer holds\n * ETH directly — gas is paid by PAFI sponsor-relayer via the paymaster-proxy\n * (see [SPONSORED_PATH_FLOW.md]). The fee collected here is an\n * application-level ERC-20 transfer inside the same UserOp batch, not a\n * reimbursement to a wallet that needs topping up.\n *\n * `rebalanceIfNeeded()` and `swapUsdtToNative` were removed in 0.3.0.\n */\nexport class FeeManager {\n private readonly provider: PublicClient;\n private readonly gasUnits: bigint;\n private readonly gasPremiumBps: number;\n private readonly quoteNativeToFee: FeeManagerConfig[\"quoteNativeToFee\"];\n private cachedFee: bigint | null = null;\n private cacheExpiresAt = 0;\n private static readonly CACHE_TTL_MS = 10_000;\n\n constructor(config: FeeManagerConfig) {\n if (!config.provider) throw new Error(\"FeeManager: provider required\");\n if (!config.quoteNativeToFee)\n throw new Error(\"FeeManager: quoteNativeToFee required\");\n\n this.provider = config.provider;\n this.gasUnits = config.gasUnits ?? DEFAULT_GAS_UNITS;\n this.gasPremiumBps = config.gasPremiumBps ?? DEFAULT_PREMIUM_BPS;\n this.quoteNativeToFee = config.quoteNativeToFee;\n }\n\n /**\n * Estimate the fee (in the caller's fee currency) to charge for the\n * next sponsored UserOp:\n *\n * nativeCost = gasUnits × gasPrice\n * withPremium = nativeCost × premiumBps / 10_000\n * fee = quoteNativeToFee(withPremium)\n *\n * For backward compatibility with v0.2.x code that reads `gasFeeUsdt`\n * from the response, the name `estimateGasFee` is kept — but the\n * currency depends on how the caller wired `quoteNativeToFee`.\n */\n async estimateGasFee(): Promise<bigint> {\n const now = Date.now();\n if (this.cachedFee !== null && now < this.cacheExpiresAt) {\n return this.cachedFee;\n }\n const gasPrice = await this.provider.getGasPrice();\n const nativeCost = gasPrice * this.gasUnits;\n const withPremium = (nativeCost * BigInt(this.gasPremiumBps)) / 10_000n;\n const fee = await this.quoteNativeToFee(withPremium);\n this.cachedFee = fee;\n this.cacheExpiresAt = now + FeeManager.CACHE_TTL_MS;\n return fee;\n }\n}\n","import type { Address, Hex } from \"viem\";\n\n/** Decoded Transfer(from=0x0 → to) event used to finalize a mint. */\nexport interface MintEvent {\n /** Destination address (the user who received the minted points) */\n to: Address;\n /** Amount minted (in wei / base units) */\n amount: bigint;\n /** Block number the mint was included in */\n blockNumber: bigint;\n /** Transaction hash that emitted the event */\n txHash: Hex;\n /** Log index within the tx, for deterministic ordering */\n logIndex: number;\n}\n\n/** Decoded Transfer(from=user → 0x0) event used to finalize a burn-for-credit. */\nexport interface BurnEvent {\n /** The burner — user whose PT was burned. */\n from: Address;\n /** Amount burned. */\n amount: bigint;\n blockNumber: bigint;\n txHash: Hex;\n logIndex: number;\n}\n\n/**\n * Cursor persistence interface — the indexer reports the next block\n * number it is about to process so the caller can write it to Redis /\n * Postgres / a file. The SDK does not own persistence because every\n * issuer has their own storage stack.\n */\nexport interface IIndexerCursorStore {\n /** Return the last persisted cursor (`undefined` on first run). */\n load(): Promise<bigint | undefined>;\n /** Persist a new cursor value. Called after each successful batch. */\n save(blockNumber: bigint): Promise<void>;\n}\n\n/**\n * No-op cursor store. Useful when the caller wants to drive the cursor\n * entirely via `processBlockRange()` and doesn't need persistence.\n */\nexport class InMemoryCursorStore implements IIndexerCursorStore {\n private cursor: bigint | undefined;\n async load(): Promise<bigint | undefined> {\n return this.cursor;\n }\n async save(blockNumber: bigint): Promise<void> {\n this.cursor = blockNumber;\n }\n}\n","import { getAddress, parseAbiItem } from \"viem\";\nimport type { Address, Log, PublicClient } from \"viem\";\nimport type { IPointLedger, LockedMintRequest } from \"../ledger/types\";\nimport type { IIndexerCursorStore, MintEvent } from \"./types\";\nimport { InMemoryCursorStore } from \"./types\";\n\nexport interface PointIndexerConfig {\n provider: PublicClient;\n pointTokenAddress: Address;\n ledger: IPointLedger;\n /**\n * v1.6 — when set, indexer listens to `MintFeeWrapper.MintWithFee`\n * (filtered by this `pointToken`) instead of `PointToken.Transfer(0x0)`.\n *\n * Required for wrapper-mediated mints because the on-chain Transfer\n * destination is the wrapper itself (not the end user), so a naive\n * `Transfer(0x0 → user)` watch would leave PENDING locks unresolved.\n *\n * `MintWithFee(indexed pointToken, indexed to, grossAmount, netAmount,\n * feeAmount)` carries the actual recipient + the gross amount that\n * matches the off-chain lock.\n *\n * Pass `undefined` (or the dead-zero address) for direct-mint chains\n * without a wrapper — indexer falls back to legacy Transfer mode.\n */\n mintFeeWrapperAddress?: Address;\n /**\n * Block to start from on first run. Ignored if the cursor store already\n * has a value. Defaults to `0n`.\n */\n fromBlock?: bigint;\n /**\n * Persistent cursor store. Defaults to an `InMemoryCursorStore` which\n * only survives inside the current process.\n */\n cursorStore?: IIndexerCursorStore;\n /**\n * Reorg safety: only treat events as final after this many confirmations.\n * The indexer reads `latestBlock - confirmations` as its high-water mark.\n * Default: 3 (a conservative number for Base L2).\n */\n confirmations?: number;\n /**\n * How many blocks to scan per `getLogs` call. Keeps RPC pages bounded.\n * Default: 2000 (comfortably under most provider page limits).\n */\n batchSize?: number;\n /**\n * Polling interval (ms) used by `start()`. Default: 5000 (5s).\n */\n pollIntervalMs?: number;\n /** Clock override for tests. */\n now?: () => number;\n /**\n * Observability hook fired on EVERY tick error (RPC failure, ledger\n * write failure, etc). Issuers MUST wire this to their alert\n * pipeline (Sentry / Datadog / PagerDuty) — without it, the indexer\n * silently swallows errors via `console.error`, and indexer outage\n * means PENDING mint locks never resolve (user balances stuck).\n *\n * When omitted, falls back to `console.error` for back-compat.\n */\n onTickError?: (err: unknown) => void;\n}\n\nconst TRANSFER_EVENT = parseAbiItem(\n \"event Transfer(address indexed from, address indexed to, uint256 value)\",\n);\n\nconst MINT_WITH_FEE_EVENT = parseAbiItem(\n \"event MintWithFee(address indexed pointToken, address indexed to, uint256 grossAmount, uint256 netAmount, uint256 feeAmount)\",\n);\n\nconst ZERO_ADDRESS: Address = \"0x0000000000000000000000000000000000000000\";\nconst DEAD_ADDRESS: Address = \"0x000000000000000000000000000000000000dEaD\";\n\nconst DEFAULT_CONFIRMATIONS = 3;\nconst DEFAULT_BATCH_SIZE = 2000n;\nconst DEFAULT_POLL_INTERVAL_MS = 5000;\n\nfunction isNoWrapper(addr: Address | undefined): boolean {\n if (!addr) return true;\n const checksummed = getAddress(addr);\n return checksummed === ZERO_ADDRESS || checksummed === DEAD_ADDRESS;\n}\n\n/**\n * Watches mint events on PointToken and finalizes the issuer ledger when\n * a confirmed mint matches a PENDING lock.\n *\n * **Two modes** — selected at construction by `mintFeeWrapperAddress`:\n *\n * 1. **Wrapper mode (v1.6, recommended for mainnet)**\n * Listens to `MintFeeWrapper.MintWithFee(indexed pointToken, indexed to,\n * grossAmount, netAmount, feeAmount)` filtered by `pointToken`. The\n * `to` field is the actual end-user (not the wrapper), and `grossAmount`\n * matches the off-chain lock's amount.\n *\n * 2. **Direct mode (legacy / chains without wrapper)**\n * Listens to `PointToken.Transfer(from=0x0 → to)` — the original v1.5\n * behavior. Used when the wrapper address is undefined / zero / dead.\n *\n * Finalization strategy (per event):\n * 1. Find a PENDING locked mint request for `to` with matching amount.\n * 2. Call `ledger.deductBalance(to, amount, txHash)` — this is idempotent\n * in the default `MemoryPointLedger` because deducting also resolves\n * the matching lock.\n * 3. If no matching lock is found (e.g. a manual `PointToken.mint()` call\n * or a race where the lock already expired), log and skip — this\n * intentionally does NOT credit the ledger, because the gateway is\n * the only sanctioned way to spawn a mint.\n *\n * Reorg safety: events are only processed when they are at least\n * `confirmations` blocks deep. A new `getLogs` call on every poll picks\n * up finalized blocks from the persisted cursor.\n *\n * Pure-polling design (rather than `watchContractEvent`) keeps the SDK\n * compatible with HTTP-only providers and with RPC rotation.\n */\nexport class PointIndexer {\n private readonly provider: PublicClient;\n private readonly pointTokenAddress: Address;\n private readonly mintFeeWrapperAddress: Address | undefined;\n private readonly ledger: IPointLedger;\n private readonly cursorStore: IIndexerCursorStore;\n private readonly startBlock: bigint;\n private readonly confirmations: bigint;\n private readonly batchSize: bigint;\n private readonly pollIntervalMs: number;\n private readonly onTickError?: (err: unknown) => void;\n\n private running = false;\n private timer: ReturnType<typeof setTimeout> | undefined;\n\n constructor(config: PointIndexerConfig) {\n if (!config.provider) throw new Error(\"PointIndexer: provider required\");\n if (!config.pointTokenAddress)\n throw new Error(\"PointIndexer: pointTokenAddress required\");\n if (!config.ledger) throw new Error(\"PointIndexer: ledger required\");\n\n this.provider = config.provider;\n this.pointTokenAddress = getAddress(config.pointTokenAddress);\n this.mintFeeWrapperAddress = isNoWrapper(config.mintFeeWrapperAddress)\n ? undefined\n : getAddress(config.mintFeeWrapperAddress as Address);\n this.ledger = config.ledger;\n this.cursorStore = config.cursorStore ?? new InMemoryCursorStore();\n this.startBlock = config.fromBlock ?? 0n;\n this.confirmations = BigInt(config.confirmations ?? DEFAULT_CONFIRMATIONS);\n this.batchSize = BigInt(config.batchSize ?? Number(DEFAULT_BATCH_SIZE));\n this.pollIntervalMs = config.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n if (config.onTickError) this.onTickError = config.onTickError;\n }\n\n // -------------------------------------------------------------------------\n // Lifecycle\n // -------------------------------------------------------------------------\n\n /** Begin polling. Schedules `tick()` on a loop. */\n start(): void {\n if (this.running) return;\n this.running = true;\n // Kick off the first tick immediately instead of waiting for the\n // first interval. `tick()` has its own try/catch so it never\n // produces an unhandled rejection, but we attach a defensive\n // `.catch` here so any future refactor that removes the inner\n // catch still surfaces failures via `onTickError`.\n this.tick().catch((err) => this.handleTickError(err));\n }\n\n /** Stop polling. Safe to call multiple times. */\n stop(): void {\n this.running = false;\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = undefined;\n }\n }\n\n /**\n * Run one poll cycle: load cursor → scan [cursor, safeHead] in\n * `batchSize` chunks → persist new cursor. Swallows any error and\n * schedules the next tick. Visible for test harnesses via a public name.\n */\n async tick(): Promise<void> {\n if (!this.running) return;\n try {\n const latest = await this.provider.getBlockNumber();\n const safeHead = latest - this.confirmations;\n if (safeHead < 0n) {\n this.scheduleNext();\n return;\n }\n\n const stored = await this.cursorStore.load();\n const from = stored ?? this.startBlock;\n if (from > safeHead) {\n this.scheduleNext();\n return;\n }\n\n await this.processBlockRange(from, safeHead);\n } catch (err) {\n this.handleTickError(err);\n }\n this.scheduleNext();\n }\n\n private handleTickError(err: unknown): void {\n if (this.onTickError) {\n try {\n this.onTickError(err);\n } catch {\n // onTickError must never throw — fall back to console.error to\n // surface the meta-failure.\n // eslint-disable-next-line no-console\n console.error(\"[PAFI] PointIndexer onTickError threw:\", err);\n }\n } else {\n // eslint-disable-next-line no-console\n console.error(\"[PAFI] PointIndexer tick error:\", err);\n }\n }\n\n private scheduleNext(): void {\n if (!this.running) return;\n this.timer = setTimeout(\n () => this.tick().catch((err) => this.handleTickError(err)),\n this.pollIntervalMs,\n );\n }\n\n // -------------------------------------------------------------------------\n // Block scanning\n // -------------------------------------------------------------------------\n\n /**\n * Scan `[from, to]` inclusive for mint events in `batchSize` chunks.\n * Callers can use this directly to backfill a specific range without\n * engaging `start()`. On completion, the cursor is advanced to `to + 1`.\n */\n async processBlockRange(from: bigint, to: bigint): Promise<void> {\n if (from > to) return;\n\n let cursor = from;\n while (cursor <= to) {\n const chunkEnd =\n cursor + this.batchSize - 1n > to ? to : cursor + this.batchSize - 1n;\n\n const events = this.mintFeeWrapperAddress\n ? await this.fetchWrapperMintEvents(cursor, chunkEnd)\n : await this.fetchTransferMintEvents(cursor, chunkEnd);\n\n // Process in deterministic order so txHash matches the first lock we\n // pick when multiple mints land in the same block.\n events.sort((a, b) => {\n if (a.blockNumber !== b.blockNumber) {\n return a.blockNumber < b.blockNumber ? -1 : 1;\n }\n return a.logIndex - b.logIndex;\n });\n\n for (const evt of events) {\n await this.finalize(evt);\n }\n\n await this.cursorStore.save(chunkEnd + 1n);\n cursor = chunkEnd + 1n;\n }\n }\n\n // -------------------------------------------------------------------------\n // Event fetching — two modes (wrapper vs direct)\n // -------------------------------------------------------------------------\n\n /**\n * Wrapper mode (v1.6): listen for `MintWithFee` on the wrapper,\n * filtered to events for THIS pointToken only. The event's `to` field\n * is the actual end user, and `grossAmount` matches the lock amount.\n */\n private async fetchWrapperMintEvents(\n fromBlock: bigint,\n toBlock: bigint,\n ): Promise<MintEvent[]> {\n const logs = await this.provider.getLogs({\n address: this.mintFeeWrapperAddress!,\n event: MINT_WITH_FEE_EVENT,\n args: { pointToken: this.pointTokenAddress },\n fromBlock,\n toBlock,\n });\n\n const out: MintEvent[] = [];\n for (const log of logs) {\n const args = log.args;\n if (\n !args.pointToken ||\n !args.to ||\n args.grossAmount === undefined ||\n log.blockNumber === null ||\n log.transactionHash === null\n ) {\n continue;\n }\n // Defensive: provider may ignore the indexed filter.\n if (getAddress(args.pointToken) !== this.pointTokenAddress) continue;\n out.push({\n to: getAddress(args.to),\n amount: args.grossAmount,\n blockNumber: log.blockNumber,\n txHash: log.transactionHash,\n logIndex: log.logIndex ?? 0,\n });\n }\n return out;\n }\n\n /**\n * Direct mode (legacy / chains without wrapper): listen for\n * `Transfer(from=0x0 → to)` on the PointToken itself.\n */\n private async fetchTransferMintEvents(\n fromBlock: bigint,\n toBlock: bigint,\n ): Promise<MintEvent[]> {\n const logs = await this.provider.getLogs({\n address: this.pointTokenAddress,\n event: TRANSFER_EVENT,\n args: { from: ZERO_ADDRESS },\n fromBlock,\n toBlock,\n });\n\n return this.decodeTransferMintEvents(logs);\n }\n\n private decodeTransferMintEvents(\n logs: Log<bigint, number, false, typeof TRANSFER_EVENT>[],\n ): MintEvent[] {\n const out: MintEvent[] = [];\n for (const log of logs) {\n // Filter narrowed on `from=0x0` via getLogs `args`, but be defensive —\n // a provider may ignore the filter.\n const args = log.args;\n if (!args.from || !args.to || args.value === undefined) continue;\n if (getAddress(args.from) !== ZERO_ADDRESS) continue;\n if (log.blockNumber === null || log.transactionHash === null) continue;\n out.push({\n to: getAddress(args.to),\n amount: args.value,\n blockNumber: log.blockNumber,\n txHash: log.transactionHash,\n logIndex: log.logIndex ?? 0,\n });\n }\n return out;\n }\n\n // -------------------------------------------------------------------------\n // Finalization\n // -------------------------------------------------------------------------\n\n /**\n * Finalize a single mint event: match it to a PENDING lock in the\n * ledger, then call `deductBalance` (which also resolves the lock in\n * the default `MemoryPointLedger`).\n *\n * No-matching-lock is a valid state: it means either the lock already\n * expired, or the mint was authorized out-of-band (e.g. a direct\n * `PointToken.mint()` from an EOA minter for testing). In that case we\n * do NOT touch the ledger — crediting here would silently allow the\n * issuer to mint without going through the gateway.\n */\n private async finalize(evt: MintEvent): Promise<void> {\n const locks = await this.ledger.getLockedRequests(\n evt.to,\n this.pointTokenAddress,\n );\n const match = pickMatchingLock(locks, evt.amount);\n if (!match) return;\n\n try {\n await this.ledger.deductBalance(\n evt.to,\n evt.amount,\n evt.txHash,\n this.pointTokenAddress,\n );\n } catch {\n // If deduct fails (e.g. race with manual edit), leave the lock and\n // let the caller reconcile. We intentionally do not retry here —\n // the next indexer tick will re-encounter the same event and try\n // again until the cursor is advanced.\n return;\n }\n\n // Best-effort MINTED status update. Already resolved by the default\n // `MemoryPointLedger` via `deductBalance`, but custom ledgers may\n // need the explicit call.\n try {\n await this.ledger.updateMintStatus(match.lockId, \"MINTED\", evt.txHash);\n } catch {\n // Swallow — deductBalance already succeeded, which is the source\n // of truth for the user's balance.\n }\n }\n}\n\n/**\n * Pick the oldest PENDING lock whose amount matches the mint event. The\n * \"oldest\" tie-breaker makes behavior deterministic when a user has\n * multiple concurrent mints of the same size.\n */\nfunction pickMatchingLock(\n locks: LockedMintRequest[],\n amount: bigint,\n): LockedMintRequest | undefined {\n let best: LockedMintRequest | undefined;\n for (const lock of locks) {\n if (lock.status !== \"PENDING\") continue;\n if (lock.amount !== amount) continue;\n if (!best || lock.createdAt < best.createdAt) {\n best = lock;\n }\n }\n return best;\n}\n","import { getAddress, parseAbiItem } from \"viem\";\nimport type { Address, Log, PublicClient } from \"viem\";\nimport type { IPointLedger } from \"../ledger/types\";\nimport type { BurnEvent, IIndexerCursorStore } from \"./types\";\nimport { InMemoryCursorStore } from \"./types\";\n\nexport interface BurnIndexerConfig {\n provider: PublicClient;\n pointTokenAddress: Address;\n ledger: IPointLedger;\n /** Block to start from on first run. Ignored if cursor store has value. */\n fromBlock?: bigint;\n cursorStore?: IIndexerCursorStore;\n /**\n * Reorg safety — only treat events as final after this many\n * confirmations. Default: 3.\n */\n confirmations?: number;\n /** Blocks per getLogs call. Default: 2000. */\n batchSize?: number;\n /** Polling interval (ms). Default: 5000. */\n pollIntervalMs?: number;\n now?: () => number;\n /**\n * Map a burn event to the pending credit lockId that should be resolved.\n * Return `undefined` to skip this burn event (no credit granted).\n *\n * REQUIRED — there is no default implementation. Issuers with a Postgres\n * ledger typically JOIN on `(from, amount, status=PENDING)`. The in-memory\n * ledger uses a lookup by lockId supplied out-of-band from the claim flow.\n */\n matchLockId: (event: BurnEvent) => Promise<string | undefined>;\n /**\n * Observability hook — see PointIndexerConfig.onTickError for full\n * rationale. Critical for burn indexer too: silent failure means\n * pending credits never resolve, user reports \"I burned my PT but\n * never got my off-chain credit\".\n */\n onTickError?: (err: unknown) => void;\n}\n\nconst TRANSFER_EVENT = parseAbiItem(\n \"event Transfer(address indexed from, address indexed to, uint256 value)\",\n);\n\nconst ZERO_ADDRESS: Address = \"0x0000000000000000000000000000000000000000\";\n\nconst DEFAULT_CONFIRMATIONS = 3;\nconst DEFAULT_BATCH_SIZE = 2000n;\nconst DEFAULT_POLL_INTERVAL_MS = 5000;\n\n/**\n * Mirror of `PointIndexer` for the reverse direction — watches\n * `Transfer(user → 0x0)` events (ERC-20 burns) on the PointToken\n * contract and finalizes pending off-chain credits.\n *\n * Finalization flow:\n * 1. For each Burn event at `{from, amount, txHash}`:\n * 2. Call `ledger.resolveCreditByBurnTx(lockId, txHash)` where `lockId`\n * is resolved by the caller's `onMatchCredit` hook or a\n * ledger-specific lookup. The SDK does not prescribe the matching\n * strategy — issuers with a Postgres ledger can JOIN by\n * `(from, amount, status=PENDING)`; the in-memory ledger matches\n * by `lockId` supplied out-of-band.\n *\n * When no pending credit matches an observed Burn event, the indexer\n * logs + skips — **it never credits off-chain** from a Burn that was\n * not first reserved via `reservePendingCredit()`. This prevents\n * spurious credits from one-off admin burns or direct burn calls\n * outside the issuer SDK.\n */\nexport class BurnIndexer {\n private readonly provider: PublicClient;\n private readonly pointTokenAddress: Address;\n private readonly ledger: IPointLedger;\n private readonly cursorStore: IIndexerCursorStore;\n private readonly startBlock: bigint;\n private readonly confirmations: bigint;\n private readonly batchSize: bigint;\n private readonly pollIntervalMs: number;\n private readonly onTickError?: (err: unknown) => void;\n\n matchLockId: (event: BurnEvent) => Promise<string | undefined>;\n\n private running = false;\n private timer: ReturnType<typeof setTimeout> | undefined;\n\n constructor(config: BurnIndexerConfig) {\n if (!config.provider) throw new Error(\"BurnIndexer: provider required\");\n if (!config.pointTokenAddress)\n throw new Error(\"BurnIndexer: pointTokenAddress required\");\n if (!config.ledger) throw new Error(\"BurnIndexer: ledger required\");\n\n this.provider = config.provider;\n this.pointTokenAddress = config.pointTokenAddress;\n this.ledger = config.ledger;\n this.cursorStore = config.cursorStore ?? new InMemoryCursorStore();\n this.startBlock = config.fromBlock ?? 0n;\n this.confirmations = BigInt(\n config.confirmations ?? DEFAULT_CONFIRMATIONS,\n );\n this.batchSize = BigInt(config.batchSize ?? Number(DEFAULT_BATCH_SIZE));\n this.pollIntervalMs = config.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n if (config.onTickError) this.onTickError = config.onTickError;\n\n if (!config.matchLockId) {\n throw new Error(\n \"BurnIndexer: matchLockId is required. \" +\n \"Provide a function that maps a burn event to its pending credit lockId. \" +\n \"Without it, no on-chain burns will ever grant off-chain credits.\",\n );\n }\n this.matchLockId = config.matchLockId;\n }\n\n start(): void {\n if (this.running) return;\n this.running = true;\n // First-tick safety: any unhandled rejection (vs `void this.tick()`)\n // routes to onTickError instead of disappearing as an unhandled rejection.\n this.tick().catch((err) => this.handleTickError(err));\n }\n\n stop(): void {\n this.running = false;\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = undefined;\n }\n }\n\n async tick(): Promise<void> {\n if (!this.running) return;\n try {\n const latest = await this.provider.getBlockNumber();\n const safeHead = latest - this.confirmations;\n if (safeHead < 0n) {\n this.scheduleNext();\n return;\n }\n\n const stored = await this.cursorStore.load();\n const from = stored ?? this.startBlock;\n if (from > safeHead) {\n this.scheduleNext();\n return;\n }\n\n await this.processBlockRange(from, safeHead);\n } catch (err) {\n this.handleTickError(err);\n }\n this.scheduleNext();\n }\n\n private handleTickError(err: unknown): void {\n if (this.onTickError) {\n try {\n this.onTickError(err);\n } catch {\n // eslint-disable-next-line no-console\n console.error(\"[PAFI] BurnIndexer onTickError threw:\", err);\n }\n } else {\n // eslint-disable-next-line no-console\n console.error(\"[PAFI] BurnIndexer tick error:\", err);\n }\n }\n\n private scheduleNext(): void {\n if (!this.running) return;\n this.timer = setTimeout(\n () => this.tick().catch((err) => this.handleTickError(err)),\n this.pollIntervalMs,\n );\n }\n\n /**\n * Scan `[from, to]` inclusive for burn events. Callers can drive this\n * directly to backfill a specific range without `start()`. Cursor is\n * advanced to `to + 1` on completion.\n */\n async processBlockRange(from: bigint, to: bigint): Promise<void> {\n if (from > to) return;\n\n let cursor = from;\n while (cursor <= to) {\n const chunkEnd =\n cursor + this.batchSize - 1n > to ? to : cursor + this.batchSize - 1n;\n\n const logs = await this.provider.getLogs({\n address: this.pointTokenAddress,\n event: TRANSFER_EVENT,\n args: { to: ZERO_ADDRESS }, // filter: burn = transfer to zero\n fromBlock: cursor,\n toBlock: chunkEnd,\n });\n\n const events = this.decodeBurnEvents(logs);\n events.sort((a, b) => {\n if (a.blockNumber !== b.blockNumber) {\n return a.blockNumber < b.blockNumber ? -1 : 1;\n }\n return a.logIndex - b.logIndex;\n });\n\n for (const evt of events) {\n await this.finalize(evt);\n }\n\n await this.cursorStore.save(chunkEnd + 1n);\n cursor = chunkEnd + 1n;\n }\n }\n\n private decodeBurnEvents(\n logs: Log<bigint, number, false, typeof TRANSFER_EVENT>[],\n ): BurnEvent[] {\n const out: BurnEvent[] = [];\n for (const log of logs) {\n const args = log.args;\n if (!args.from || !args.to || args.value === undefined) continue;\n if (getAddress(args.to) !== ZERO_ADDRESS) continue;\n if (log.blockNumber === null || log.transactionHash === null) continue;\n out.push({\n from: getAddress(args.from),\n amount: args.value,\n blockNumber: log.blockNumber,\n txHash: log.transactionHash,\n logIndex: log.logIndex ?? 0,\n });\n }\n return out;\n }\n\n /**\n * Resolve a matching pending credit for this burn event and call\n * `ledger.resolveCreditByBurnTx(lockId, txHash)`. If no match found,\n * log + skip.\n */\n private async finalize(evt: BurnEvent): Promise<void> {\n const txHash = evt.txHash;\n const lockId = await this.matchLockId(evt);\n if (lockId === undefined) {\n console.warn(\n '[PAFI] BurnIndexer: matchLockId returned undefined for burn tx ' + txHash +\n '. This burn will NOT be credited. Implement matchLockId to map burn events to lock IDs.'\n );\n return;\n }\n\n if (!this.ledger.resolveCreditByBurnTx) {\n // Legacy ledger doesn't support reverse flow; skip.\n return;\n }\n\n try {\n await this.ledger.resolveCreditByBurnTx(lockId, evt.txHash);\n } catch (err) {\n console.error('[PAFI] BurnIndexer finalize error — credit may be lost:', err);\n }\n }\n}\n","import { getAddress } from \"viem\";\nimport type { Address, PublicClient } from \"viem\";\nimport {\n getMintFeeBps,\n getMintRequestNonce,\n getPointTokenBalance,\n getReceiverConsentNonce,\n isMinter,\n} from \"@pafi-dev/core\";\nimport type { AuthService } from \"../auth/loginVerifier\";\nimport {\n NoopRateLimiter,\n type IRateLimiter,\n} from \"../auth/rateLimiter\";\nimport type { IPointLedger } from \"../ledger/types\";\nimport type { FeeManager } from \"../relay/feeManager\";\nimport type { RedemptionService } from \"../redemption/service\";\nimport { ConfigurationError, ValidationError } from \"../errors\";\nimport type {\n ApiConfigResponse,\n ApiGasFeeResponse,\n ApiLoginRequest,\n ApiLoginResponse,\n ApiNonceResponse,\n ApiPoolsRequest,\n ApiPoolsResponse,\n ApiRedemptionEvaluateRequest,\n ApiRedemptionEvaluateResponse,\n ApiRedemptionPreviewRequest,\n ApiRedemptionPreviewResponse,\n ApiUserRequest,\n ApiUserResponse,\n PoolsProvider,\n} from \"./types\";\n\nexport interface IssuerApiHandlersConfig {\n // -- Auth ---------------------------------------------------------------\n authService: AuthService;\n\n // -- Data sources -------------------------------------------------------\n ledger: IPointLedger;\n /** Used by `handleUser` to read on-chain nonces and minter status. */\n provider: PublicClient;\n\n // -- Point token addresses ---------------------------------------------\n /**\n * Legacy single-token config. Prefer `pointTokenAddresses` for multi-token\n * issuers (0.2.0+).\n */\n pointTokenAddress?: Address;\n /**\n * All supported PointToken addresses. Handlers accept any address in this\n * list; others are rejected with \"unsupported pointToken\".\n */\n pointTokenAddresses?: Address[];\n\n // -- Chain config (surfaced verbatim by `handleConfig`) ----------------\n chainId: number;\n contracts: ApiConfigResponse[\"contracts\"];\n /**\n * Optional — URL that the Issuer App opens for PT→USDT swap or perp\n * deposit after a successful claim. Surfaced in `/config` response.\n * See [MOBILE_SDK_INTEGRATION.md] \"PAFI Web Handoff\".\n */\n pafiWebUrl?: string;\n /**\n * v1.6 — MintFeeWrapper address used to read per-PointToken fee\n * basis points for `/config`. When omitted, the response will not\n * include `mintFeeBpsByToken` and FE must assume zero fee. Wrapper\n * is shared across PointTokens; per-token recipients live inside it.\n */\n mintFeeWrapperAddress?: Address;\n\n // -- Optional / pluggable -----------------------------------------------\n /** Required by `handleGasFee`; omit to disable the endpoint. */\n feeManager?: FeeManager;\n /** Required by `handlePools`; omit to disable the endpoint. */\n poolsProvider?: PoolsProvider;\n /**\n * Required by `handleRedemptionPreview` / `handleRedemptionEvaluate`;\n * omit to disable both. Wired by createIssuerService when the top-level\n * `redemption` config is provided.\n */\n redemption?: RedemptionService;\n /**\n * Rate limiter for `/auth/nonce` and `/auth/login` (both unauthenticated +\n * CPU-bound). When omitted, defaults to a `NoopRateLimiter` that lets\n * every request through (with a one-time prod warning) — issuers MUST\n * wire a real impl in production.\n */\n rateLimiter?: IRateLimiter;\n}\n\n/**\n * Framework-agnostic HTTP handlers that match the endpoints a `PafiSDK`\n * frontend expects to call. Issuers wrap these in Express / Fastify /\n * Hono / whatever — the handlers take plain inputs and return plain\n * outputs, with `AuthError` surfaced as typed exceptions.\n *\n * Every protected handler takes a pre-verified `userAddress` as its first\n * argument. The issuer's middleware wraps `authenticateRequest()` from\n * `@pafi/issuer` to extract that address from the Bearer token before\n * routing.\n */\nexport class IssuerApiHandlers {\n private readonly authService: AuthService;\n private readonly ledger: IPointLedger;\n private readonly provider: PublicClient;\n /**\n * Set of supported PointToken addresses (checksum-normalized). Handlers\n * validate the request's `pointTokenAddress` against this set.\n */\n private readonly supportedTokens: Set<Address>;\n private readonly chainId: number;\n private readonly contracts: ApiConfigResponse[\"contracts\"];\n private readonly pafiWebUrl?: string;\n private readonly feeManager?: FeeManager;\n private readonly poolsProvider?: PoolsProvider;\n private readonly redemption?: RedemptionService;\n private readonly rateLimiter: IRateLimiter;\n private readonly mintFeeWrapperAddress?: Address;\n /**\n * Per-token feeBps cache. Refreshed on /config when stale. feeBps\n * changes only when ops calls `wrapper.setRecipients`, so 5-min TTL\n * is more than safe for FE display purposes.\n */\n private readonly feeBpsCache = new Map<\n Address,\n { value: number; expiresAt: number }\n >();\n private static readonly FEE_BPS_TTL_MS = 5 * 60 * 1000;\n\n constructor(config: IssuerApiHandlersConfig) {\n this.authService = config.authService;\n this.ledger = config.ledger;\n this.provider = config.provider;\n this.rateLimiter = config.rateLimiter ?? new NoopRateLimiter();\n\n const raw =\n config.pointTokenAddresses && config.pointTokenAddresses.length > 0\n ? config.pointTokenAddresses\n : config.pointTokenAddress\n ? [config.pointTokenAddress]\n : [];\n if (raw.length === 0) {\n // Constructor-time misconfiguration — surface as a plain Error\n // so issuer's bootstrap code fails immediately. Don't wrap in\n // PafiSdkError because there is no controller to map this to;\n // boot-time errors should crash the process.\n throw new Error(\n \"IssuerApiHandlers: pointTokenAddress or pointTokenAddresses required\",\n );\n }\n const normalized = raw.map((a) => getAddress(a));\n this.supportedTokens = new Set(normalized);\n\n this.chainId = config.chainId;\n this.contracts = config.contracts;\n if (config.pafiWebUrl) this.pafiWebUrl = config.pafiWebUrl;\n if (config.feeManager) this.feeManager = config.feeManager;\n if (config.poolsProvider) this.poolsProvider = config.poolsProvider;\n if (config.redemption) this.redemption = config.redemption;\n if (config.mintFeeWrapperAddress) {\n this.mintFeeWrapperAddress = getAddress(config.mintFeeWrapperAddress);\n }\n }\n\n // =========================================================================\n // Public handlers (no auth required)\n // =========================================================================\n\n /**\n * `GET /auth/nonce`\n *\n * @param rateLimitKey Caller-side rate-limit key (typically client IP).\n * The HTTP layer (controller/middleware) extracts\n * this from the request and passes it through.\n * When omitted, no rate limit applies — production\n * callers SHOULD always pass a key.\n */\n async handleGetNonce(rateLimitKey?: string): Promise<ApiNonceResponse> {\n if (rateLimitKey) {\n const result = await this.rateLimiter.consume(\n rateLimitKey,\n \"auth_nonce\",\n );\n if (!result.allowed) {\n throw new ValidationError(\n \"RATE_LIMIT_EXCEEDED\",\n \"handleGetNonce: too many requests\",\n {\n retryAfterMs: result.retryAfterMs ?? 0,\n action: \"auth_nonce\",\n },\n );\n }\n }\n const nonce = await this.authService.getNonce();\n return { nonce };\n }\n\n /**\n * `POST /auth/login`\n *\n * @param body Login message + signature.\n * @param rateLimitKey Caller-side rate-limit key (typically client IP\n * or `body.userAddress` if known). See `handleGetNonce`.\n */\n async handleLogin(\n body: ApiLoginRequest,\n rateLimitKey?: string,\n ): Promise<ApiLoginResponse> {\n // Rate-limit BEFORE field validation: even malformed bodies that fail\n // shape checks consume CPU on parsing; throttle attackers that spam\n // such requests.\n if (rateLimitKey) {\n const result = await this.rateLimiter.consume(\n rateLimitKey,\n \"auth_login\",\n );\n if (!result.allowed) {\n throw new ValidationError(\n \"RATE_LIMIT_EXCEEDED\",\n \"handleLogin: too many requests\",\n {\n retryAfterMs: result.retryAfterMs ?? 0,\n action: \"auth_login\",\n },\n );\n }\n }\n if (\n !body ||\n typeof body.message !== \"string\" ||\n body.message.length === 0 ||\n typeof body.signature !== \"string\" ||\n body.signature.length <= 2\n ) {\n throw new ValidationError(\n \"INVALID_LOGIN_BODY\",\n \"handleLogin: message and signature are required\",\n );\n }\n if (body.message.length > 4096) {\n throw new ValidationError(\"MESSAGE_TOO_LONG\", \"message too long\");\n }\n if (body.signature.length > 260) {\n throw new ValidationError(\"SIGNATURE_TOO_LONG\", \"signature too long\");\n }\n const result = await this.authService.login(body.message, body.signature);\n return {\n token: result.token,\n userAddress: result.userAddress,\n expiresAt: result.expiresAt.getTime(),\n };\n }\n\n /**\n * `GET /config?chainId=<id>`\n *\n * Returns the contract addresses and chain id that the frontend SDK\n * needs to build EIP-712 messages and interact with on-chain.\n */\n async handleConfig(chainId: number): Promise<ApiConfigResponse> {\n if (!Number.isInteger(chainId) || chainId <= 0) {\n throw new ValidationError(\"INVALID_CHAIN_ID\", \"invalid chainId\", {\n chainId,\n });\n }\n if (chainId !== this.chainId) {\n throw new ValidationError(\n \"UNSUPPORTED_CHAIN_ID\",\n `handleConfig: unsupported chainId ${chainId}`,\n { requested: chainId, supported: this.chainId },\n );\n }\n const contracts: ApiConfigResponse[\"contracts\"] = {\n ...this.contracts,\n pointTokens: Array.from(this.supportedTokens),\n };\n if (this.mintFeeWrapperAddress) {\n contracts.mintFeeWrapper = this.mintFeeWrapperAddress;\n }\n const response: ApiConfigResponse = {\n chainId: this.chainId,\n contracts,\n };\n if (this.mintFeeWrapperAddress) {\n const byToken = await this.resolveFeeBpsByToken(\n this.mintFeeWrapperAddress,\n );\n if (byToken) response.mintFeeBpsByToken = byToken;\n }\n if (this.pafiWebUrl) response.pafiWebUrl = this.pafiWebUrl;\n return response;\n }\n\n /**\n * Read `totalFeeBps(pointToken)` for every supported PointToken from\n * the wrapper. Cached per-token for 5 minutes. Returns `undefined`\n * (caller drops the field) if every read fails — FE must treat\n * \"field missing\" as \"fee unknown, do not display\".\n */\n private async resolveFeeBpsByToken(\n wrapper: Address,\n ): Promise<Record<string, number> | undefined> {\n const now = Date.now();\n const out: Record<string, number> = {};\n let anyFresh = false;\n await Promise.all(\n Array.from(this.supportedTokens).map(async (token) => {\n const cached = this.feeBpsCache.get(token);\n if (cached && cached.expiresAt > now) {\n out[token.toLowerCase()] = cached.value;\n anyFresh = true;\n return;\n }\n try {\n const bps = await getMintFeeBps(this.provider, wrapper, token);\n this.feeBpsCache.set(token, {\n value: bps,\n expiresAt: now + IssuerApiHandlers.FEE_BPS_TTL_MS,\n });\n out[token.toLowerCase()] = bps;\n anyFresh = true;\n } catch {\n // RPC failure — fall back to last cached value if we have one\n // (better stale than missing). Drop the entry only when we\n // have nothing.\n if (cached) {\n out[token.toLowerCase()] = cached.value;\n anyFresh = true;\n }\n }\n }),\n );\n return anyFresh ? out : undefined;\n }\n\n /** `GET /gas-fee` — quoted in USDT (6-decimal base units). */\n async handleGasFee(): Promise<ApiGasFeeResponse> {\n if (!this.feeManager) {\n throw new ConfigurationError(\n \"FEE_MANAGER_NOT_CONFIGURED\",\n \"handleGasFee: feeManager is not configured on this issuer\",\n );\n }\n const gasFeeUsdt = await this.feeManager.estimateGasFee();\n return { gasFeeUsdt };\n }\n\n // =========================================================================\n // Protected handlers (JWT required — userAddress extracted by middleware)\n // =========================================================================\n\n /** `POST /auth/logout` */\n async handleLogout(token: string): Promise<void> {\n await this.authService.logout(token);\n }\n\n /**\n * `GET /pools?chainId=<id>&pointToken=<addr>`\n *\n * Delegates to the injected `PoolsProvider`. The handler itself does\n * not know where pools come from — that's an issuer decision.\n */\n async handlePools(\n _userAddress: Address,\n request: ApiPoolsRequest,\n ): Promise<ApiPoolsResponse> {\n if (!this.poolsProvider) {\n throw new ConfigurationError(\n \"POOLS_PROVIDER_NOT_CONFIGURED\",\n \"handlePools: poolsProvider is not configured on this issuer\",\n );\n }\n if (request.chainId !== this.chainId) {\n throw new ValidationError(\n \"UNSUPPORTED_CHAIN_ID\",\n `handlePools: unsupported chainId ${request.chainId}`,\n { requested: request.chainId, supported: this.chainId },\n );\n }\n return this.poolsProvider(request);\n }\n\n /**\n * `GET /user?chainId=<id>&user=<addr>&pointToken=<addr>`\n *\n * Returns per-user state the frontend needs to build a fresh\n * `ReceiverConsent`: on-chain nonces + minter status + off-chain\n * balance.\n */\n async handleUser(\n userAddress: Address,\n request: ApiUserRequest,\n ): Promise<ApiUserResponse> {\n if (request.chainId !== this.chainId) {\n throw new ValidationError(\n \"UNSUPPORTED_CHAIN_ID\",\n `handleUser: unsupported chainId ${request.chainId}`,\n { requested: request.chainId, supported: this.chainId },\n );\n }\n const normalizedAuthed = getAddress(userAddress);\n const normalizedRequest = getAddress(request.userAddress);\n if (normalizedAuthed !== normalizedRequest) {\n throw new ValidationError(\n \"USER_ADDRESS_MISMATCH\",\n \"handleUser: request userAddress must match authenticated user\",\n { authenticated: normalizedAuthed, requested: normalizedRequest },\n );\n }\n const pointToken = getAddress(request.pointTokenAddress);\n if (!this.supportedTokens.has(pointToken)) {\n throw new ValidationError(\n \"UNSUPPORTED_POINT_TOKEN\",\n `handleUser: unsupported pointToken ${pointToken}`,\n { requested: pointToken },\n );\n }\n\n const [mintRequestNonce, receiverConsentNonce, offChainBalance, onChainBalance, minter] =\n await Promise.all([\n getMintRequestNonce(this.provider, pointToken, normalizedAuthed),\n getReceiverConsentNonce(this.provider, pointToken, normalizedAuthed),\n this.ledger.getBalance(normalizedAuthed, pointToken),\n getPointTokenBalance(this.provider, pointToken, normalizedAuthed),\n isMinter(this.provider, pointToken, normalizedAuthed),\n ]);\n\n return {\n mintRequestNonce,\n receiverConsentNonce,\n offChainBalance,\n onChainBalance,\n totalBalance: offChainBalance + onChainBalance,\n balance: offChainBalance, // deprecated alias\n isMinter: minter,\n };\n }\n\n // Note: legacy `handleClaim` (sync sponsored-claim returning calls[]) was\n // removed in 0.5.43 — callers should use `PTClaimHandler` directly or\n // wire `IssuerApiAdapter.claim()` which composes the full flow.\n\n /**\n * `GET /redemption/preview?pointToken=<addr>`\n *\n * Returns the headroom currently available to `userAddress` under the\n * configured RedemptionPolicy. Pure read — does not record anything.\n * Use this for UI to render \"X PT redeemable now / next available at …\".\n */\n async handleRedemptionPreview(\n userAddress: Address,\n request: ApiRedemptionPreviewRequest,\n ): Promise<ApiRedemptionPreviewResponse> {\n if (!this.redemption) {\n throw new ConfigurationError(\n \"REDEMPTION_NOT_CONFIGURED\",\n \"handleRedemptionPreview: redemption is not configured on this issuer\",\n );\n }\n const tokenAddress = request.pointTokenAddress\n ? this.requireSupportedToken(getAddress(request.pointTokenAddress), \"handleRedemptionPreview\")\n : undefined;\n const preview = await this.redemption.preview(\n getAddress(userAddress),\n tokenAddress,\n );\n return preview;\n }\n\n /**\n * `POST /redemption/evaluate`\n *\n * Pre-flight check before the issuer signs a BurnRequest. Returns\n * { allowed, denial?, preview }. Caller (the burn-orchestrator) MUST\n * re-check on the actual initiate path — evaluate is read-only and a\n * caller could race two requests under the same headroom. The intended\n * write path is: evaluate → sign BurnRequest → reserve pending credit\n * → call `service.redemption.recordSuccessfulInitiate()`.\n */\n async handleRedemptionEvaluate(\n userAddress: Address,\n request: ApiRedemptionEvaluateRequest,\n ): Promise<ApiRedemptionEvaluateResponse> {\n if (!this.redemption) {\n throw new ConfigurationError(\n \"REDEMPTION_NOT_CONFIGURED\",\n \"handleRedemptionEvaluate: redemption is not configured on this issuer\",\n );\n }\n if (request.amountPt <= 0n) {\n throw new ValidationError(\n \"INVALID_AMOUNT\",\n \"handleRedemptionEvaluate: amountPt must be positive\",\n { amountPt: request.amountPt.toString() },\n );\n }\n const tokenAddress = request.pointTokenAddress\n ? this.requireSupportedToken(getAddress(request.pointTokenAddress), \"handleRedemptionEvaluate\")\n : undefined;\n const decision = await this.redemption.evaluate(\n getAddress(userAddress),\n request.amountPt,\n tokenAddress,\n );\n const response: ApiRedemptionEvaluateResponse = {\n allowed: decision.allowed,\n preview: decision.preview,\n };\n if (decision.denial) response.denial = decision.denial;\n return response;\n }\n\n private requireSupportedToken(\n pointToken: Address,\n handler: string,\n ): Address {\n if (!this.supportedTokens.has(pointToken)) {\n throw new ValidationError(\n \"UNSUPPORTED_POINT_TOKEN\",\n `${handler}: unsupported pointToken ${pointToken}`,\n { requested: pointToken },\n );\n }\n return pointToken;\n }\n}\n","import type { Address, Hex, PublicClient, WalletClient } from \"viem\";\nimport { getAddress } from \"viem\";\nimport type {\n BurnRequest,\n PartialUserOperation,\n PointTokenDomainConfig,\n} from \"@pafi-dev/core\";\nimport {\n signBurnRequest,\n POINT_TOKEN_V2_ABI,\n getPointTokenBalance,\n getContractAddresses,\n} from \"@pafi-dev/core\";\nimport type { IPointLedger } from \"../../ledger/types\";\nimport type { RelayService } from \"../../relay/relayService\";\nimport type { FeeManager } from \"../../relay/feeManager\";\nimport type { RedemptionService } from \"../../redemption/service\";\nimport type { RedemptionDenialCode } from \"@pafi-dev/core\";\nimport { PafiSdkError } from \"../../errors\";\n\n/**\n * v1.4 reverse flow — user-initiated PT redeem.\n *\n * User has on-chain PT, wants to convert back to off-chain points. The\n * issuer backend signs a `BurnRequest` with its burner signer, reserves\n * an off-chain pending credit, and returns an unsigned UserOp. The FE\n * submits the UserOp via EIP-7702 + PAFI sponsor-relayer. On confirmation,\n * `Transfer(user → 0x0)` is emitted; `BurnIndexer` resolves the pending\n * credit to a real off-chain credit.\n *\n * Contract path (mock ABI — matches deployed PointToken):\n *\n * burn(address from, uint256 amount, uint256 deadline, bytes burnerSig)\n *\n * On-chain checks:\n * - msg.sender == from (enforced via EIP-7702 delegation on user EOA)\n * - burnerSig signer ∈ burners[]\n * - nonce == burnRequestNonces[from]\n * - now <= deadline\n *\n * The user never signs an EIP-712 message in this flow. Their only\n * signature is the ERC-4337 UserOp signature, which the AA wallet\n * handles. Consent is implicit: by submitting the UserOp they authorize\n * the burn.\n */\nexport interface PTRedeemHandlerConfig {\n ledger: IPointLedger;\n relayService: RelayService;\n provider: PublicClient;\n\n /** PointToken contract address (chain-specific). */\n pointTokenAddress: Address;\n /** BatchExecutor delegation target (chain-specific). */\n batchExecutorAddress: Address;\n\n /** Chain id — used for the BurnRequest EIP-712 domain. */\n chainId: number;\n /**\n * EIP-712 domain fields. Must match the on-chain PointToken's domain\n * separator, or on-chain signature recovery fails. `name` is\n * typically the PointToken's ERC-20 name. `verifyingContract`\n * defaults to `pointTokenAddress`.\n */\n domain: {\n name: string;\n verifyingContract?: Address;\n };\n\n /**\n * Issuer burner signer wallet — signs the `BurnRequest` EIP-712.\n * Must be whitelisted via `PointToken.addBurner(signerAddr)` at\n * provisioning time. Typically HSM/KMS-backed in prod.\n */\n burnerSignerWallet: WalletClient;\n\n /**\n * Optional — when wired, used to estimate the PT gas-reimbursement\n * fee. The handler self-computes `feeAmount` + `feeRecipient` so the\n * caller doesn't repeat the boilerplate at each endpoint. Pass\n * `feeAmount` on `PTRedeemRequest` to override.\n */\n feeService?: FeeManager;\n\n /**\n * How long the pending credit stays reserved if the burn never lands.\n * Default: 15 min — long enough for a bundler submission + confirmation.\n */\n redeemLockDurationMs?: number;\n\n /**\n * How far ahead of `now` to set the BurnRequest deadline. Default:\n * 15 min. Must be long enough for Bundler + EntryPoint to execute;\n * short enough to prevent replay if the UserOp is abandoned.\n */\n signatureDeadlineSeconds?: number;\n\n /** Clock injection for tests; defaults to `Date.now`. */\n now?: () => number;\n\n /**\n * Optional — when wired, the handler enforces the per-issuer\n * RedemptionPolicy (daily limit / cooldown / blackout / per-tx range)\n * BEFORE signing the BurnRequest. On denial it throws\n * PTRedeemError(\"REDEMPTION_POLICY_DENIED\", ...) with the policy\n * denial code in `policyDenialCode`. After a successful sign+reserve,\n * the handler records the redemption against the user's history so\n * the next preview reflects the spend.\n */\n redemptionService?: RedemptionService;\n}\n\nexport interface PTRedeemRequest {\n /** Address extracted from the verified JWT — must match `userAddress`. */\n authenticatedAddress: Address;\n userAddress: Address;\n amount: bigint;\n /** ERC-4337 account nonce for the user's EOA. */\n aaNonce: bigint;\n /**\n * Optional override — explicit PT fee transfer appended after the\n * burn (sponsored path). When omitted, the handler auto-computes\n * via `feeService.estimateGasFee()` (if configured) and resolves\n * the recipient from `getContractAddresses(chainId).pafiFeeRecipient`.\n */\n feeAmount?: bigint;\n feeRecipient?: Address;\n /**\n * Optional — required only when the handler self-computes the\n * recipient. When the caller passes an explicit `feeRecipient`, this\n * is ignored.\n */\n chainId?: number;\n}\n\nexport interface PTRedeemResponse {\n /**\n * Sponsored path — UserOp with PT fee transfer. BurnRequest signed for\n * `request.amount - feeAmount`. User holds exactly `request.amount` PT\n * and the fee comes out of the redeem amount, NOT on top.\n */\n lockId: string;\n userOp: PartialUserOperation;\n /** = request.amount - feeAmount. What BurnIndexer credits off-chain on sponsored fire. */\n netCreditAmount: bigint;\n /**\n * Resolved fee — computed via `feeService.estimateGasFee()` when the\n * caller didn't override on the request, else echoed back from the\n * request override. Useful for the FE confirmation screen.\n */\n feeAmount: bigint;\n\n /**\n * Fallback path — UserOp with NO fee transfer. BurnRequest signed for\n * full `request.amount`. User pays gas in ETH directly. Present only\n * when `feeAmount > 0`.\n *\n * Same nonce as sponsored: only one of the two can fire on-chain;\n * the other lock auto-expires after `expiresInSeconds`. BurnIndexer\n * matches by burn amount and resolves the correct lock.\n */\n fallback?: {\n lockId: string;\n userOp: PartialUserOperation;\n /** = request.amount. Full credit when fallback fires. */\n netCreditAmount: bigint;\n };\n\n /** Seconds until the lock expires if the burn doesn't land. */\n expiresInSeconds: number;\n /** The BurnRequest deadline (unix seconds) — FE uses this to surface a countdown. */\n signatureDeadline: bigint;\n}\n\nconst DEFAULT_REDEEM_LOCK_MS = 15 * 60 * 1000;\nconst DEFAULT_SIG_DEADLINE_SEC = 15 * 60;\n\nexport type PTRedeemErrorCode =\n | \"UNAUTHORIZED\"\n | \"INVALID_AMOUNT\"\n | \"NONCE_READ_FAILED\"\n | \"NONCE_IN_FLIGHT\"\n | \"LEDGER_NOT_SUPPORTED\"\n | \"SIGNING_FAILED\"\n | \"REDEMPTION_POLICY_DENIED\";\n\nexport class PTRedeemError extends PafiSdkError {\n readonly httpStatus = \"unprocessable\" as const;\n readonly code: PTRedeemErrorCode;\n readonly policyDenialCode?: RedemptionDenialCode;\n\n constructor(\n code: PTRedeemErrorCode,\n message: string,\n options?: { policyDenialCode?: RedemptionDenialCode },\n ) {\n super(message);\n this.code = code;\n if (options?.policyDenialCode) {\n this.policyDenialCode = options.policyDenialCode;\n }\n }\n}\n\nexport class PTRedeemHandler {\n private readonly ledger: IPointLedger;\n private readonly relayService: RelayService;\n private readonly provider: PublicClient;\n private readonly feeService?: FeeManager;\n private readonly pointTokenAddress: Address;\n private readonly batchExecutorAddress: Address;\n private readonly chainId: number;\n private readonly domain: PTRedeemHandlerConfig[\"domain\"];\n private readonly burnerSignerWallet: WalletClient;\n private readonly redeemLockDurationMs: number;\n private readonly signatureDeadlineSeconds: number;\n private readonly now: () => number;\n private readonly redemptionService?: RedemptionService;\n\n /**\n * Per-user in-flight nonce guard (single-process only).\n *\n * Prevents two concurrent requests from reading the same on-chain\n * burnRequestNonce before either has completed, which would produce two\n * signed UserOps with the same nonce — only one succeeds on-chain; the\n * other leaves an orphaned pending credit and a wasted signer call.\n *\n * NOTE: This guard is effective only within a single Node.js process. For\n * multi-instance deployments (k8s, PM2 cluster), enforce mutual exclusion\n * via a distributed lock (Redis SETNX / Postgres advisory lock) keyed on\n * `(userAddress, pointTokenAddress)` BEFORE calling `handle()`.\n */\n private readonly inFlightNonces = new Map<string, Set<bigint>>();\n\n constructor(config: PTRedeemHandlerConfig) {\n if (!config.ledger.reservePendingCredit) {\n throw new PTRedeemError(\n \"LEDGER_NOT_SUPPORTED\",\n \"PTRedeemHandler requires a ledger that implements reservePendingCredit() (v0.3.0+)\",\n );\n }\n if (!config.burnerSignerWallet) {\n throw new PTRedeemError(\n \"SIGNING_FAILED\",\n \"PTRedeemHandler requires burnerSignerWallet (issuer burner signer)\",\n );\n }\n this.ledger = config.ledger;\n this.relayService = config.relayService;\n this.provider = config.provider;\n this.feeService = config.feeService;\n this.pointTokenAddress = getAddress(config.pointTokenAddress);\n this.batchExecutorAddress = getAddress(config.batchExecutorAddress);\n this.chainId = config.chainId;\n this.domain = config.domain;\n this.burnerSignerWallet = config.burnerSignerWallet;\n // Warn if WalletClient is backed by a private key account (PrivateKeyAccount type check)\n if (this.burnerSignerWallet?.account?.type === 'local') {\n console.warn('[PAFI] PTRedeemHandler: burnerSignerWallet uses a local (private key) account. Use a KMS-backed signer in production.');\n }\n this.redeemLockDurationMs =\n config.redeemLockDurationMs ?? DEFAULT_REDEEM_LOCK_MS;\n this.signatureDeadlineSeconds =\n config.signatureDeadlineSeconds ?? DEFAULT_SIG_DEADLINE_SEC;\n this.now = config.now ?? (() => Date.now());\n if (config.redemptionService) {\n this.redemptionService = config.redemptionService;\n }\n }\n\n async handle(request: PTRedeemRequest): Promise<PTRedeemResponse> {\n if (getAddress(request.authenticatedAddress) !== getAddress(request.userAddress)) {\n throw new PTRedeemError(\n \"UNAUTHORIZED\",\n `userAddress (${request.userAddress}) does not match authenticated session (${request.authenticatedAddress})`,\n );\n }\n if (request.amount <= 0n) {\n throw new PTRedeemError(\"INVALID_AMOUNT\", \"redeem amount must be positive\");\n }\n\n // Pre-flight RedemptionPolicy check. Done BEFORE any chain read or\n // signer call so a denied request fails fast and doesn't burn an\n // RPC quota or signer slot. Race window vs concurrent requests is\n // bounded by `inFlightNonces` below — two parallel requests that\n // both pass evaluate() under the same headroom can still slip\n // through, but the second one will be rejected as NONCE_IN_FLIGHT.\n if (this.redemptionService) {\n const decision = await this.redemptionService.evaluate(\n request.userAddress,\n request.amount,\n this.pointTokenAddress,\n );\n if (!decision.allowed) {\n const denial = decision.denial!;\n throw new PTRedeemError(\n \"REDEMPTION_POLICY_DENIED\",\n `redemption denied: ${denial.message}`,\n { policyDenialCode: denial.code },\n );\n }\n }\n\n // Read the current burnRequestNonces[user] from-chain.\n let burnNonce: bigint;\n try {\n burnNonce = (await this.provider.readContract({\n address: this.pointTokenAddress,\n abi: POINT_TOKEN_V2_ABI,\n functionName: \"burnRequestNonces\",\n args: [request.userAddress],\n })) as bigint;\n } catch (err) {\n throw new PTRedeemError(\n \"NONCE_READ_FAILED\",\n `failed to read burnRequestNonces(${request.userAddress}): ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n\n // Guard: reject concurrent requests that share the same on-chain nonce.\n // Without this, two parallel requests both read nonce=N, sign two UserOps\n // with nonce=N, and create two pending credits — the losing UserOp reverts\n // on-chain, leaving an orphaned PENDING credit and a wasted signer call.\n const userKey = getAddress(request.userAddress).toLowerCase();\n let userNonces = this.inFlightNonces.get(userKey);\n if (!userNonces) {\n userNonces = new Set<bigint>();\n this.inFlightNonces.set(userKey, userNonces);\n }\n if (userNonces.has(burnNonce)) {\n throw new PTRedeemError(\n \"NONCE_IN_FLIGHT\",\n `A burn request for nonce ${burnNonce} is already in progress for ${request.userAddress}. Retry after the current request completes.`,\n );\n }\n userNonces.add(burnNonce);\n\n try {\n return await this._handleAfterNonceLock(request, burnNonce);\n } finally {\n userNonces.delete(burnNonce);\n if (userNonces.size === 0) this.inFlightNonces.delete(userKey);\n }\n }\n\n private async _handleAfterNonceLock(\n request: PTRedeemRequest,\n burnNonce: bigint,\n ): Promise<PTRedeemResponse> {\n // Self-compute fee + recipient when caller didn't override.\n // Mirrors PTClaimHandler so issuer endpoints stay symmetric.\n let fee: bigint;\n if (request.feeAmount !== undefined) {\n fee = request.feeAmount > 0n ? request.feeAmount : 0n;\n } else if (this.feeService) {\n fee = await this.feeService.estimateGasFee();\n } else {\n fee = 0n;\n }\n const feeRecipient =\n request.feeRecipient ??\n (request.chainId !== undefined\n ? getContractAddresses(request.chainId).pafiFeeRecipient\n : getContractAddresses(this.chainId).pafiFeeRecipient);\n\n if (fee > 0n && fee >= request.amount) {\n throw new PTRedeemError(\n \"INVALID_AMOUNT\",\n `fee (${fee}) must be strictly less than redeem amount (${request.amount})`,\n );\n }\n\n // Verify on-chain balance BEFORE signing — avoid wasting signature slots.\n const onChainBalance = await getPointTokenBalance(\n this.provider,\n this.pointTokenAddress,\n request.userAddress,\n );\n if (onChainBalance < request.amount) {\n throw new PTRedeemError(\n \"INVALID_AMOUNT\",\n `insufficient on-chain PT balance: have ${onChainBalance}, need ${request.amount}`,\n );\n }\n\n const deadline = BigInt(\n Math.floor(this.now() / 1000) + this.signatureDeadlineSeconds,\n );\n const domain: PointTokenDomainConfig = {\n name: this.domain.name,\n chainId: this.chainId,\n verifyingContract: this.domain.verifyingContract ?? this.pointTokenAddress,\n };\n\n // Sponsored path: BurnRequest for (amount - fee). Ops include fee\n // transfer FIRST so a user holding exactly `amount` can complete.\n const sponsoredBurnAmount = request.amount - fee;\n const sponsoredBurnRequest: BurnRequest = {\n from: request.userAddress,\n amount: sponsoredBurnAmount,\n nonce: burnNonce,\n deadline,\n };\n\n let sponsoredSig: Hex;\n try {\n sponsoredSig = (\n await signBurnRequest(this.burnerSignerWallet, domain, sponsoredBurnRequest)\n ).serialized;\n } catch (err) {\n throw new PTRedeemError(\n \"SIGNING_FAILED\",\n `failed to sign sponsored BurnRequest: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n // Reserve sponsored credit (= amount - fee).\n const sponsoredLockId = await this.ledger.reservePendingCredit!(\n request.userAddress,\n sponsoredBurnAmount,\n this.redeemLockDurationMs,\n this.pointTokenAddress,\n );\n\n try {\n const sponsoredUserOp = await this.relayService.prepareBurn({\n mode: \"burnWithSig\",\n userAddress: request.userAddress,\n aaNonce: request.aaNonce,\n pointTokenAddress: this.pointTokenAddress,\n batchExecutorAddress: this.batchExecutorAddress,\n burnRequest: sponsoredBurnRequest,\n burnerSignature: sponsoredSig,\n feeAmount: fee,\n feeRecipient,\n });\n\n // Fallback path: only built when fee > 0. BurnRequest for full `amount`,\n // no fee transfer. Same nonce as sponsored — only one can fire on-chain.\n // The unused lock expires after `redeemLockDurationMs`.\n let fallback: PTRedeemResponse[\"fallback\"] = undefined;\n if (fee > 0n) {\n const fallbackBurnRequest: BurnRequest = {\n from: request.userAddress,\n amount: request.amount,\n nonce: burnNonce,\n deadline,\n };\n\n let fallbackSig: Hex;\n try {\n fallbackSig = (\n await signBurnRequest(this.burnerSignerWallet, domain, fallbackBurnRequest)\n ).serialized;\n } catch (err) {\n throw new PTRedeemError(\n \"SIGNING_FAILED\",\n `failed to sign fallback BurnRequest: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n const fallbackLockId = await this.ledger.reservePendingCredit!(\n request.userAddress,\n request.amount,\n this.redeemLockDurationMs,\n this.pointTokenAddress,\n );\n\n // Inner try — if fallback build fails, release fallback lock.\n // Outer try (below) handles sponsored lock cleanup.\n let fallbackUserOp;\n try {\n fallbackUserOp = await this.relayService.prepareBurn({\n mode: \"burnWithSig\",\n userAddress: request.userAddress,\n aaNonce: request.aaNonce,\n pointTokenAddress: this.pointTokenAddress,\n batchExecutorAddress: this.batchExecutorAddress,\n burnRequest: fallbackBurnRequest,\n burnerSignature: fallbackSig,\n // Explicit 0n — fallback is fee-free regardless of how\n // RelayService is configured. Without this, an\n // auto-quoting RelayService would try to quote a fee here\n // and re-add the PT.transfer we're trying to strip.\n feeAmount: 0n,\n });\n } catch (err) {\n await this.ledger.releaseLock(fallbackLockId).catch(() => {\n /* noop — lock will auto-expire via TTL */\n });\n throw err;\n }\n\n fallback = {\n lockId: fallbackLockId,\n userOp: fallbackUserOp,\n netCreditAmount: request.amount,\n };\n }\n\n // Record against the user's redemption history AFTER both UserOps\n // are built but BEFORE returning. The full requested `amount` is\n // counted (not the net), since sponsored and fallback share the\n // same burnNonce — exactly one will land on-chain and the user\n // commits to a single redemption of `amount` PT either way.\n // Best-effort: a failure here doesn't roll back the reserves —\n // the locks will auto-expire if the burn doesn't land.\n if (this.redemptionService) {\n await this.redemptionService\n .recordSuccessfulInitiate({\n user: request.userAddress,\n amountPt: request.amount,\n pointTokenAddress: this.pointTokenAddress,\n reservationId: sponsoredLockId,\n })\n .catch(() => {\n /* noop — non-critical, history will reconcile from burn events */\n });\n }\n\n return {\n lockId: sponsoredLockId,\n userOp: sponsoredUserOp,\n netCreditAmount: sponsoredBurnAmount,\n feeAmount: fee,\n fallback,\n expiresInSeconds: Math.floor(this.redeemLockDurationMs / 1000),\n signatureDeadline: deadline,\n };\n } catch (err) {\n // Release sponsored lock on ANY post-reserve failure.\n await this.ledger.releaseLock(sponsoredLockId).catch(() => {\n /* noop — lock will auto-expire via TTL */\n });\n throw err;\n }\n }\n}\n","import type { Address, Hex } from \"viem\";\nimport type { IPointLedger, LockedMintRequest, PendingCredit } from \"../ledger/types\";\nimport type { PafiBackendClient } from \"../pafi-backend/client\";\nimport { PafiSdkError } from \"../errors\";\n\n/**\n * Status snapshot returned to mobile clients polling a mint lock.\n * Matches the legacy gg56 shape so existing FE code keeps working.\n */\nexport interface MintStatusResponse {\n lockId: string;\n status: \"PENDING\" | \"MINTED\" | \"EXPIRED\" | \"FAILED\";\n txHash: Hex | null;\n amount: string;\n createdAt: string;\n expiresAt: string;\n}\n\nexport interface BurnStatusResponse {\n lockId: string;\n status: \"PENDING\" | \"RESOLVED\" | \"EXPIRED\";\n txHash: Hex | null;\n amount: string;\n createdAt: string;\n expiresAt: string;\n resolvedAt?: string | null;\n}\n\nexport interface MintStatusParams {\n lockId: string;\n /** User EOA from auth — handler returns 404 if the lock isn't theirs. */\n userAddress: Address;\n ledger: IPointLedger;\n /**\n * PAFI backend client for the bundler-receipt fallback. Optional —\n * when omitted, status only reflects what the on-chain `PointIndexer`\n * has finalized into the ledger. With it, the handler queries the\n * bundler receipt when:\n * - lock.status === \"PENDING\"\n * - lock.userOpHash is bound (set by `/claim/submit`)\n *\n * If the bundler reports the UserOp confirmed, the handler updates\n * the ledger lock + returns `MINTED` immediately, bypassing\n * `PointIndexer`'s amount-based race (multiple PENDING locks with\n * the same amount can be matched to the wrong tx_hash).\n */\n pafiBackendClient?: PafiBackendClient | null;\n /** Optional logger for \"ledger update failed\" warnings. */\n onWarning?: (msg: string) => void;\n}\n\nexport interface BurnStatusParams {\n lockId: string;\n userAddress: Address;\n ledger: IPointLedger;\n pafiBackendClient?: PafiBackendClient | null;\n onWarning?: (msg: string) => void;\n}\n\nclass LockNotFoundError extends PafiSdkError {\n readonly code = \"LOCK_NOT_FOUND\";\n readonly httpStatus = \"not_found\" as const;\n constructor() {\n super(\"Lock not found or does not belong to authenticated user\");\n }\n}\n\nexport { LockNotFoundError };\n\n/**\n * Handle GET /claim/status/:lockId.\n *\n * Returns the lock's current state from the ledger. When the ledger\n * supports `userOpHash` binding AND the lock is still PENDING, falls\n * back to the bundler receipt — the canonical truth that bypasses\n * `PointIndexer`'s amount-based matching race.\n *\n * Throws `LockNotFoundError` when the lock doesn't exist or doesn't\n * belong to `userAddress`. Caller maps to HTTP 404.\n */\nexport async function handleClaimStatus(\n params: MintStatusParams,\n): Promise<MintStatusResponse> {\n if (!params.ledger.getMintLock) {\n throw new Error(\n \"handleClaimStatus: ledger does not implement `getMintLock` — implement the optional method on `IPointLedger` or write a custom status handler.\",\n );\n }\n\n const lock = await params.ledger.getMintLock(\n params.lockId,\n params.userAddress,\n );\n if (\n !lock ||\n lock.userAddress.toLowerCase() !== params.userAddress.toLowerCase()\n ) {\n throw new LockNotFoundError();\n }\n\n let status: LockedMintRequest[\"status\"] = lock.status;\n let txHash: Hex | null = lock.txHash ?? null;\n\n // Bundler-receipt fallback. Only runs when:\n // - status is still PENDING (terminal states are authoritative)\n // - userOpHash is bound (i.e. /claim/submit ran successfully)\n // - PafiBackendClient is supplied (caller opted in)\n if (\n status === \"PENDING\" &&\n lock.userOpHash &&\n params.pafiBackendClient\n ) {\n try {\n const receipt = await params.pafiBackendClient.getUserOpReceipt(\n lock.userOpHash,\n );\n if (receipt) {\n status = receipt.success ? \"MINTED\" : \"FAILED\";\n txHash = receipt.txHash;\n // Best-effort persist so the next poll hits the cheap ledger\n // path and so PointIndexer/audit downstream gets the correct\n // tx_hash even if the indexer's amount-match resolved a\n // different sibling lock.\n await params.ledger\n .updateMintStatus(lock.lockId, status, receipt.txHash)\n .catch((err) => {\n params.onWarning?.(\n `handleClaimStatus: ledger updateMintStatus failed for lock ${lock.lockId}: ${err}`,\n );\n });\n }\n } catch (err) {\n params.onWarning?.(\n `handleClaimStatus: bundler-receipt fallback failed for lock ${lock.lockId}: ${err}`,\n );\n }\n }\n\n return {\n lockId: lock.lockId,\n status,\n txHash,\n amount: lock.amount.toString(),\n createdAt: new Date(lock.createdAt).toISOString(),\n expiresAt: new Date(lock.expiresAt).toISOString(),\n };\n}\n\n/**\n * Handle GET /redeem/status/:lockId. Symmetric to `handleClaimStatus`\n * for the burn → off-chain credit flow.\n *\n * Bundler-receipt fallback resolves the credit via\n * `ledger.resolveCreditByBurnTx` when the bundler reports the burn\n * UserOp succeeded.\n */\nexport async function handleRedeemStatus(\n params: BurnStatusParams,\n): Promise<BurnStatusResponse> {\n if (!params.ledger.getPendingCredit) {\n throw new Error(\n \"handleRedeemStatus: ledger does not implement `getPendingCredit` — implement the optional method on `IPointLedger` or write a custom status handler.\",\n );\n }\n\n const credit = await params.ledger.getPendingCredit(\n params.lockId,\n params.userAddress,\n );\n if (\n !credit ||\n credit.userAddress.toLowerCase() !== params.userAddress.toLowerCase()\n ) {\n throw new LockNotFoundError();\n }\n\n let status: PendingCredit[\"status\"] = credit.status;\n let txHash: Hex | null = credit.txHash ?? null;\n\n if (\n status === \"PENDING\" &&\n credit.userOpHash &&\n params.pafiBackendClient\n ) {\n try {\n const receipt = await params.pafiBackendClient.getUserOpReceipt(\n credit.userOpHash,\n );\n if (receipt && receipt.success) {\n status = \"RESOLVED\";\n txHash = receipt.txHash;\n if (params.ledger.resolveCreditByBurnTx) {\n await params.ledger\n .resolveCreditByBurnTx(credit.lockId, receipt.txHash)\n .catch((err) => {\n params.onWarning?.(\n `handleRedeemStatus: resolveCreditByBurnTx failed for lock ${credit.lockId}: ${err}`,\n );\n });\n }\n }\n } catch (err) {\n params.onWarning?.(\n `handleRedeemStatus: bundler-receipt fallback failed for lock ${credit.lockId}: ${err}`,\n );\n }\n }\n\n return {\n lockId: credit.lockId,\n status,\n txHash,\n amount: credit.amount.toString(),\n createdAt: new Date(credit.createdAt).toISOString(),\n expiresAt: new Date(credit.expiresAt).toISOString(),\n resolvedAt: credit.resolvedAt\n ? new Date(credit.resolvedAt).toISOString()\n : null,\n };\n}\n","import type { Address, Hex, PublicClient } from \"viem\";\nimport { getAddress } from \"viem\";\nimport {\n ENTRY_POINT_V08,\n parseEip7702DelegatedAddress,\n type PartialUserOperation,\n} from \"@pafi-dev/core\";\nimport {\n prepareMobileUserOp,\n serializeEntryToJsonRpc,\n type IPendingUserOpStore,\n type PrepareMobileUserOpResult,\n} from \"../userop-store\";\nimport {\n requestPaymaster,\n relayUserOp,\n} from \"../pafi-backend/helpers\";\nimport type { PafiBackendClient } from \"../pafi-backend/client\";\nimport { PafiSdkError } from \"../errors\";\n\n/**\n * Mobile prepare/submit orchestrators — abstract the duplicate glue\n * between `/claim/prepare`+`/claim/submit` and `/redeem/prepare`+\n * `/redeem/submit`. Both share the same shape:\n *\n * prepare: fees + delegation check → paymaster → prepareMobileUserOp\n * submit: fetch entry → serialize+sign → relay → bind hash → delete\n *\n * Issuer controllers shrink to ~30 LoC each — wire the handler that\n * produces `partialUserOp[+ fallback]`, hand off to these.\n */\n\nexport class PendingUserOpNotFoundError extends PafiSdkError {\n readonly code = \"PENDING_USEROP_NOT_FOUND\";\n readonly httpStatus = \"not_found\" as const;\n constructor(lockId: string) {\n super(\n `No pending UserOp found for lockId ${lockId} — it may have expired or already been submitted.`,\n );\n }\n}\n\n/**\n * Thrown when the authenticated user attempts to submit a pending\n * UserOp that belongs to a different sender. Map to 403.\n *\n * The pending-userop store is keyed by `lockId` (a UUID), but lockIds\n * are predictable by anyone who logs them — without this check, user A\n * could submit user B's UserOp once they leak/guess the lockId. The\n * UserOp signature itself recovers to user B (so on-chain it would\n * fail), but the issuer would still bind the bundler hash to user B's\n * lock and consume relay quota, enabling DoS / state pollution.\n */\nexport class PendingUserOpForbiddenError extends PafiSdkError {\n readonly code = \"PENDING_USEROP_FORBIDDEN\";\n readonly httpStatus = \"forbidden\" as const;\n constructor(lockId: string) {\n super(\n `Pending UserOp ${lockId} does not belong to the authenticated user.`,\n );\n }\n}\n\nexport interface HandleMobilePrepareParams {\n /** User EOA — used for the delegation check. */\n userAddress: Address;\n chainId: number;\n /** Lock id (issuer-generated) keying both store entry + ledger row. */\n lockId: string;\n /**\n * Partial UserOp from the upstream handler (PTClaimHandler /\n * PTRedeemHandler). The orchestrator will top up `maxFeePerGas` /\n * `maxPriorityFeePerGas` from `provider.estimateFeesPerGas()` if the\n * fields aren't already set, then request paymaster sponsorship.\n */\n partialUserOp: PartialUserOperation;\n /** Optional fee-stripped fallback variant. */\n partialUserOpFallback?: PartialUserOperation;\n /**\n * Scenario tag — passed to `requestSponsorship` so the relayer can\n * apply per-scenario L1 enforcement (`mint`, `burn`, etc.).\n */\n scenario: string;\n /** Target contract for the paymaster intent. */\n pointTokenAddress: Address;\n\n /** TTL the store entry should outlive. Typically the lock duration in seconds. */\n ttlSeconds: number;\n\n store: IPendingUserOpStore;\n provider: PublicClient;\n /** Optional — when omitted, paymaster is skipped and `sponsored: false` is returned. */\n pafiBackendClient?: PafiBackendClient | null;\n onWarning?: (msg: string) => void;\n}\n\nexport interface HandleMobilePrepareResult extends PrepareMobileUserOpResult {\n /**\n * True when paymaster sponsorship was applied to the sponsored variant.\n * (Renamed from `sponsored` to avoid clashing with\n * `PrepareMobileUserOpResult.sponsored` which is the PreparedUserOp object.)\n */\n isSponsored: boolean;\n /**\n * True when the user's EOA has no EIP-7702 delegation set on-chain.\n * The mobile client must run the `/delegate/*` flow first.\n */\n needsDelegation: boolean;\n}\n\n/**\n * Build the mobile prepare response. End-to-end:\n *\n * 1. Pull `estimateFeesPerGas` + `getCode(user)` in parallel.\n * 2. Top up sponsored UserOp fees if the upstream handler left them\n * unset.\n * 3. `requestPaymaster` — non-fatal: if PAFI declines, the sponsored\n * variant still works, the FE just falls back to the unsponsored\n * response.\n * 4. `prepareMobileUserOp` — merge + hash + persist.\n */\nexport async function handleMobilePrepare(\n params: HandleMobilePrepareParams,\n): Promise<HandleMobilePrepareResult> {\n const [fees, userCode] = await Promise.all([\n params.provider.estimateFeesPerGas(),\n params.provider.getCode({ address: params.userAddress }),\n ]);\n\n const needsDelegation = parseEip7702DelegatedAddress(userCode) === null;\n\n const sponsoredOp = {\n ...params.partialUserOp,\n maxFeePerGas: fees.maxFeePerGas ?? params.partialUserOp.maxFeePerGas ?? 0n,\n maxPriorityFeePerGas:\n fees.maxPriorityFeePerGas ??\n params.partialUserOp.maxPriorityFeePerGas ??\n 0n,\n };\n\n const paymasterFields = await requestPaymaster({\n client: params.pafiBackendClient,\n chainId: params.chainId,\n scenario: params.scenario,\n userOp: sponsoredOp,\n pointTokenAddress: params.pointTokenAddress,\n onWarning: params.onWarning,\n });\n\n const prepared = await prepareMobileUserOp({\n lockId: params.lockId,\n partialUserOp: sponsoredOp,\n partialUserOpFallback: params.partialUserOpFallback,\n paymasterFields,\n chainId: params.chainId,\n store: params.store,\n ttlSeconds: params.ttlSeconds,\n });\n\n return {\n ...prepared,\n isSponsored: !!paymasterFields,\n needsDelegation,\n };\n}\n\nexport interface HandleMobileSubmitParams {\n lockId: string;\n /**\n * Authenticated user EOA — extracted from the JWT / session by the\n * caller. Enforces ownership: the helper rejects with\n * `PendingUserOpForbiddenError` when `entry.sender !==\n * authenticatedAddress`.\n */\n authenticatedAddress: Address;\n /** User signature over `userOpHash` (or `userOpHashFallback`). */\n signature: Hex;\n /** Which variant the user actually signed. Defaults to `'sponsored'`. */\n variant?: \"sponsored\" | \"fallback\";\n store: IPendingUserOpStore;\n /**\n * Bind the bundler-returned hash to the lock so `claim/redeem status`\n * can fall back to the bundler receipt when the indexer's\n * amount-based match races and resolves a sibling. Different ledgers\n * use different methods (`bindMintUserOpHash` vs `bindCreditUserOpHash`),\n * so the caller passes the correct one.\n */\n bindUserOpHash: (lockId: string, userOpHash: Hex) => Promise<void>;\n\n pafiBackendClient?: PafiBackendClient | null;\n /** Defaults to `ENTRY_POINT_V08`. */\n entryPoint?: string;\n}\n\n/**\n * Submit a previously-prepared mobile UserOp to the bundler.\n *\n * Throws:\n * - `PendingUserOpNotFoundError` — entry expired or already submitted.\n * Map to 404.\n * - `PendingUserOpForbiddenError` — `entry.sender` doesn't match the\n * authenticated user. Map to 403.\n * - `BundlerNotConfiguredError` — `pafiBackendClient` missing. Map to 503.\n * - `BundlerRejectedError` — bundler rejected the UserOp. Map to 422.\n */\nexport async function handleMobileSubmit(\n params: HandleMobileSubmitParams,\n): Promise<{ userOpHash: Hex }> {\n const entry = await params.store.get(params.lockId);\n if (!entry) {\n throw new PendingUserOpNotFoundError(params.lockId);\n }\n\n // Ownership check — entry.sender must match the JWT-authenticated\n // user. Without this, user A could submit user B's pending UserOp\n // once they leak/guess B's lockId. Compare via `getAddress` so\n // checksummed/lowercased forms both work.\n if (\n getAddress(entry.sender) !== getAddress(params.authenticatedAddress)\n ) {\n throw new PendingUserOpForbiddenError(params.lockId);\n }\n\n const variant = params.variant ?? \"sponsored\";\n const userOpJson = serializeEntryToJsonRpc(entry, params.signature, variant);\n\n const result = await relayUserOp({\n client: params.pafiBackendClient,\n userOp: userOpJson,\n entryPoint: params.entryPoint ?? ENTRY_POINT_V08,\n });\n\n await params.bindUserOpHash(params.lockId, result.userOpHash);\n await params.store.delete(params.lockId);\n\n return { userOpHash: result.userOpHash };\n}\n","import type { Hex } from \"viem\";\nimport { serializeUserOpToJsonRpc } from \"@pafi-dev/core\";\nimport type { PendingUserOpEntry } from \"./types\";\n\n/**\n * Convert a stored `PendingUserOpEntry` (decimal-string fields) plus a\n * signature into the JSON-RPC wire format for `eth_sendUserOperation`.\n *\n * Bridges the gap between the serialized storage format (decimal strings,\n * safe for JSON/Redis) and `serializeUserOpToJsonRpc` which expects bigints.\n */\nexport function serializeEntryToJsonRpc(\n entry: PendingUserOpEntry,\n signature: Hex,\n variant: \"sponsored\" | \"fallback\" = \"sponsored\",\n): Record<string, string | null> {\n // Fallback variant: same sender / nonce / fees but different\n // callData (no PT operator-fee transfer), no paymaster fields.\n // User pays ERC-4337 gas in ETH directly.\n if (variant === \"fallback\") {\n if (!entry.fallback) {\n throw new Error(\n \"serializeEntryToJsonRpc: variant=fallback requested but the stored entry has no `fallback` branch — caller should resubmit with variant='sponsored' or re-prepare with a fee configured.\",\n );\n }\n return serializeUserOpToJsonRpc(\n {\n sender: entry.sender,\n nonce: BigInt(entry.nonce),\n callData: entry.fallback.callData,\n callGasLimit: BigInt(entry.fallback.callGasLimit),\n verificationGasLimit: BigInt(entry.fallback.verificationGasLimit),\n preVerificationGas: BigInt(entry.fallback.preVerificationGas),\n maxFeePerGas: BigInt(entry.maxFeePerGas),\n maxPriorityFeePerGas: BigInt(entry.maxPriorityFeePerGas),\n // intentionally no paymaster — user pays ETH gas\n },\n signature,\n );\n }\n\n return serializeUserOpToJsonRpc(\n {\n sender: entry.sender,\n nonce: BigInt(entry.nonce),\n callData: entry.callData,\n callGasLimit: BigInt(entry.callGasLimit),\n verificationGasLimit: BigInt(entry.verificationGasLimit),\n preVerificationGas: BigInt(entry.preVerificationGas),\n maxFeePerGas: BigInt(entry.maxFeePerGas),\n maxPriorityFeePerGas: BigInt(entry.maxPriorityFeePerGas),\n paymaster: entry.paymaster,\n paymasterVerificationGasLimit:\n entry.paymasterVerificationGasLimit != null\n ? BigInt(entry.paymasterVerificationGasLimit)\n : undefined,\n paymasterPostOpGasLimit:\n entry.paymasterPostOpGasLimit != null\n ? BigInt(entry.paymasterPostOpGasLimit)\n : undefined,\n paymasterData: entry.paymasterData,\n },\n signature,\n );\n}\n","import type { IPendingUserOpStore, PendingUserOpEntry } from \"./types\";\n\ninterface MemoryEntry {\n entry: PendingUserOpEntry;\n expiresAt: number;\n}\n\n/**\n * In-memory `IPendingUserOpStore` for **single-instance dev / test\n * harnesses only**.\n *\n * Multi-instance deployments (k8s, PM2 cluster) MUST use a shared store\n * — typically Redis with a TTL key or Postgres with `expires_at`. If\n * `prepare` lands on instance A and `submit` on instance B, an\n * in-memory store on A loses the entry.\n *\n * Entries are evicted lazily on `get()` if expired. Periodic sweep is\n * not implemented — the in-memory map's footprint scales with\n * outstanding pending UserOps, which is bounded by the issuer's lock\n * duration (default 15 min).\n */\nexport class MemoryPendingUserOpStore implements IPendingUserOpStore {\n private readonly entries = new Map<string, MemoryEntry>();\n private readonly now: () => number;\n\n constructor(now: () => number = () => Date.now()) {\n this.now = now;\n }\n\n async save(\n lockId: string,\n entry: PendingUserOpEntry,\n ttlSeconds: number,\n ): Promise<void> {\n this.entries.set(lockId, {\n entry,\n expiresAt: this.now() + ttlSeconds * 1000,\n });\n }\n\n async get(lockId: string): Promise<PendingUserOpEntry | null> {\n const hit = this.entries.get(lockId);\n if (!hit) return null;\n if (hit.expiresAt <= this.now()) {\n this.entries.delete(lockId);\n return null;\n }\n return hit.entry;\n }\n\n async delete(lockId: string): Promise<void> {\n this.entries.delete(lockId);\n }\n}\n","import type { Address, Hex } from \"viem\";\nimport {\n buildUserOpTypedData,\n computeUserOpHash,\n type PartialUserOperation,\n type UserOpTypedData,\n} from \"@pafi-dev/core\";\nimport type { IPendingUserOpStore, PendingUserOpEntry } from \"./types\";\n\n/**\n * Re-shape `UserOpTypedData` so all `bigint` fields become hex strings —\n * required for JSON transport over HTTP. Mirrors the inverse of what\n * `walletClient.signTypedData` accepts on the client (it auto-coerces hex\n * strings back to bigints for `uint256` fields).\n */\nexport type SerializedUserOpTypedData = {\n domain: UserOpTypedData[\"domain\"];\n types: UserOpTypedData[\"types\"];\n primaryType: UserOpTypedData[\"primaryType\"];\n message: {\n sender: Address;\n nonce: Hex;\n initCode: Hex;\n callData: Hex;\n accountGasLimits: Hex;\n preVerificationGas: Hex;\n gasFees: Hex;\n paymasterAndData: Hex;\n };\n};\n\n/**\n * Convert a `UserOpTypedData` payload into the JSON-safe wire form\n * (bigint → hex string). The mobile client passes this directly to\n * `eth_signTypedData_v4` / viem's `signTypedData`.\n */\nexport function serializeUserOpTypedData(\n td: UserOpTypedData,\n): SerializedUserOpTypedData {\n return {\n domain: td.domain,\n types: td.types,\n primaryType: td.primaryType,\n message: {\n sender: td.message.sender,\n nonce: `0x${td.message.nonce.toString(16)}` as Hex,\n initCode: td.message.initCode,\n callData: td.message.callData,\n accountGasLimits: td.message.accountGasLimits,\n preVerificationGas: `0x${td.message.preVerificationGas.toString(\n 16,\n )}` as Hex,\n gasFees: td.message.gasFees,\n paymasterAndData: td.message.paymasterAndData,\n },\n };\n}\n\n/**\n * Merge Pimlico's paymaster-sponsorship response into a UserOp\n * skeleton, applying only fields that are actually defined.\n *\n * `pm_sponsorUserOperation` returns:\n * - `paymaster` / `paymasterData` — required for the sponsored sig\n * - `paymasterVerificationGasLimit` / `paymasterPostOpGasLimit`\n * - **re-estimated** `callGasLimit` / `verificationGasLimit` /\n * `preVerificationGas` — the paymaster signature is computed over\n * these new values\n * - **bundler-required** `maxFeePerGas` / `maxPriorityFeePerGas` —\n * Base RPC's `eth_feeHistory` underestimates, bundler rejects\n *\n * Callers MUST re-merge ALL of these into the userOp BEFORE computing\n * the EIP-712 userOpHash — otherwise both the paymaster signature\n * (AA34) and the user signature (AA24, recovered against a different\n * hash) fail at validation.\n *\n * Skips fields that are undefined so legacy paymaster responses\n * (without re-estimated gas) don't accidentally zero out the original\n * estimates.\n */\nexport function mergePaymasterFields<T extends object>(\n userOp: T,\n paymasterFields:\n | {\n paymaster: Address;\n paymasterData: Hex;\n paymasterVerificationGasLimit: bigint;\n paymasterPostOpGasLimit: bigint;\n callGasLimit?: bigint;\n verificationGasLimit?: bigint;\n preVerificationGas?: bigint;\n maxFeePerGas?: bigint;\n maxPriorityFeePerGas?: bigint;\n }\n | undefined,\n): T {\n if (!paymasterFields) return userOp;\n const merged: Record<string, unknown> = {\n ...(userOp as Record<string, unknown>),\n };\n for (const [k, v] of Object.entries(paymasterFields)) {\n if (v !== undefined) merged[k] = v;\n }\n return merged as unknown as T;\n}\n\nexport interface PreparedUserOp {\n /** The bundler-ready UserOp (with paymaster + Pimlico-quoted gas). */\n userOp: PartialUserOperation & {\n maxFeePerGas: bigint;\n maxPriorityFeePerGas: bigint;\n paymaster?: Address;\n paymasterData?: Hex;\n paymasterVerificationGasLimit?: bigint;\n paymasterPostOpGasLimit?: bigint;\n };\n /** Hex-encoded EIP-712 digest. Equals `EntryPoint.getUserOpHash`. */\n userOpHash: Hex;\n /** Typed-data payload — pass directly to `eth_signTypedData_v4`. */\n typedData: SerializedUserOpTypedData;\n}\n\nexport interface PrepareMobileUserOpParams {\n /** Lock id (issuer-generated) keying both store entry + ledger row. */\n lockId: string;\n /**\n * Sponsored variant — built with the PT operator-fee transfer\n * included. SDK or caller should set `partialUserOp.maxFeePerGas` /\n * `maxPriorityFeePerGas` from `provider.estimateFeesPerGas()` before\n * calling — they get overridden by Pimlico's quote anyway, but\n * they must be valid bigints for the merge.\n */\n partialUserOp: PartialUserOperation & {\n maxFeePerGas: bigint;\n maxPriorityFeePerGas: bigint;\n };\n /**\n * Optional fee-stripped fallback variant — built with `gasFeePt: 0n`\n * (no PT operator-fee transfer). Submitted when paymaster refuses\n * sponsorship and the user pays ETH gas directly.\n */\n partialUserOpFallback?: PartialUserOperation & {\n maxFeePerGas?: bigint;\n maxPriorityFeePerGas?: bigint;\n };\n /** Paymaster sponsorship response, or `undefined` if PAFI declined. */\n paymasterFields?: {\n paymaster: Address;\n paymasterData: Hex;\n paymasterVerificationGasLimit: bigint;\n paymasterPostOpGasLimit: bigint;\n callGasLimit?: bigint;\n verificationGasLimit?: bigint;\n preVerificationGas?: bigint;\n maxFeePerGas?: bigint;\n maxPriorityFeePerGas?: bigint;\n };\n chainId: number;\n /** Pending-userop store implementation (Redis/Postgres/Memory). */\n store: IPendingUserOpStore;\n /** TTL the store entry should outlive — typically the MintRequest deadline. */\n ttlSeconds: number;\n}\n\nexport interface PrepareMobileUserOpResult {\n sponsored: PreparedUserOp;\n /**\n * Set when `partialUserOpFallback` was supplied AND the PT fee was\n * non-zero (i.e. sponsored ≠ fallback). Mobile client picks which\n * variant to sign + submit; SDK's `serializeEntryToJsonRpc` reads\n * the `variant` flag to dispatch.\n */\n fallback?: PreparedUserOp;\n /** What got persisted into the pending-userop store. */\n entry: PendingUserOpEntry;\n}\n\n/**\n * Build the sponsored UserOp + (optional) fee-stripped fallback for the\n * mobile prepare/submit flow.\n *\n * What this does, end-to-end:\n * 1. Merge Pimlico's paymaster sponsorship + re-quoted gas into the\n * caller's `partialUserOp` skeleton.\n * 2. Compute the EIP-712 userOpHash + the typed-data payload (in\n * JSON-safe form for HTTP transport).\n * 3. (Optional) Repeat for the `partialUserOpFallback` skeleton with\n * no paymaster fields — produces a separate hash + typed-data so\n * the client can re-sign if it falls back.\n * 4. Persist a single store entry containing BOTH callData variants\n * keyed by lockId. `serializeEntryToJsonRpc` reads the `variant`\n * param at submit time.\n *\n * Replaces ~100 LoC of glue per scenario in issuer controllers.\n */\nexport async function prepareMobileUserOp(\n params: PrepareMobileUserOpParams,\n): Promise<PrepareMobileUserOpResult> {\n const userOp = mergePaymasterFields(\n params.partialUserOp,\n params.paymasterFields,\n ) as unknown as PreparedUserOp[\"userOp\"];\n\n const userOpHash = computeUserOpHash(userOp, params.chainId);\n const typedData = serializeUserOpTypedData(\n buildUserOpTypedData(userOp, params.chainId),\n );\n\n let fallback: PreparedUserOp | undefined;\n let fallbackEntry: PendingUserOpEntry[\"fallback\"];\n if (params.partialUserOpFallback) {\n // Fallback fees MUST mirror the sponsored variant's *post-paymaster*\n // values — the pending-userop store reuses top-level fees for both\n // variants when serializing on submit, so hashing fallback against\n // pre-paymaster RPC estimates causes AA24 (signed digest ≠ submitted).\n const fallbackUserOp = {\n ...params.partialUserOpFallback,\n maxFeePerGas: userOp.maxFeePerGas,\n maxPriorityFeePerGas: userOp.maxPriorityFeePerGas,\n };\n const fallbackHash = computeUserOpHash(fallbackUserOp, params.chainId);\n const fallbackTypedData = serializeUserOpTypedData(\n buildUserOpTypedData(fallbackUserOp, params.chainId),\n );\n fallback = {\n userOp: fallbackUserOp as PreparedUserOp[\"userOp\"],\n userOpHash: fallbackHash,\n typedData: fallbackTypedData,\n };\n fallbackEntry = {\n callData: fallbackUserOp.callData,\n callGasLimit: fallbackUserOp.callGasLimit.toString(),\n verificationGasLimit: fallbackUserOp.verificationGasLimit.toString(),\n preVerificationGas: fallbackUserOp.preVerificationGas.toString(),\n userOpHash: fallbackHash,\n };\n }\n\n const entry: PendingUserOpEntry = {\n sender: userOp.sender,\n nonce: userOp.nonce.toString(),\n callData: userOp.callData,\n callGasLimit: userOp.callGasLimit.toString(),\n verificationGasLimit: userOp.verificationGasLimit.toString(),\n preVerificationGas: userOp.preVerificationGas.toString(),\n maxFeePerGas: userOp.maxFeePerGas.toString(),\n maxPriorityFeePerGas: userOp.maxPriorityFeePerGas.toString(),\n paymaster: userOp.paymaster,\n paymasterVerificationGasLimit:\n userOp.paymasterVerificationGasLimit?.toString(),\n paymasterPostOpGasLimit: userOp.paymasterPostOpGasLimit?.toString(),\n paymasterData: userOp.paymasterData,\n chainId: params.chainId,\n userOpHash,\n fallback: fallbackEntry,\n };\n\n await params.store.save(params.lockId, entry, params.ttlSeconds);\n\n return {\n sponsored: { userOp, userOpHash, typedData },\n fallback,\n entry,\n };\n}\n","export interface RetryConfig {\n maxAttempts?: number;\n initialDelayMs?: number;\n maxDelayMs?: number;\n maxRetryAfterMs?: number;\n}\n\nexport interface PafiBackendConfig {\n /**\n * chainId — used to derive the sponsor-relayer base URL via\n * `getPafiServiceUrls(chainId).sponsorRelayer`. SDK ships with the\n * URL per chainId; bump SDK version to retarget.\n */\n chainId: number;\n issuerId: string;\n apiKey: string;\n fetchImpl?: typeof fetch;\n timeoutMs?: number;\n retry?: RetryConfig;\n}\n\nexport type PafiBackendErrorCode =\n | \"MISSING_ISSUER_ID\"\n | \"MISSING_API_KEY\"\n | \"ISSUER_UNAUTHORIZED\"\n | \"USER_UNAUTHORIZED\"\n | \"INTENT_REJECTED\"\n | \"MINT_CAP_EXCEEDED\"\n | \"ISSUER_INACTIVE\"\n | \"BROKER_NOT_WHITELISTED\"\n | \"RATE_LIMIT_EXCEEDED\"\n | \"RATE_LIMIT_EXCEEDED_DAILY\"\n | \"RATE_LIMIT_EXCEEDED_PER_USER\"\n | \"ISSUER_BUDGET_EXCEEDED\"\n | \"RATE_LIMITER_UNAVAILABLE\"\n | \"PAYMASTER_UNAVAILABLE\"\n | \"TARGET_NOT_ALLOWLISTED\"\n | \"BAD_REQUEST\"\n | \"INTERNAL_ERROR\"\n | \"TIMEOUT\"\n | \"NETWORK_ERROR\"\n | (string & {});\n\nexport class PafiBackendError extends Error {\n public readonly retryAfter?: number;\n private readonly serverSafeToRetry?: boolean;\n\n constructor(\n public code: PafiBackendErrorCode,\n message: string,\n public httpStatus: number,\n public details?: unknown,\n opts?: { retryAfter?: number; safeToRetry?: boolean },\n ) {\n super(message);\n this.name = \"PafiBackendError\";\n if (opts?.retryAfter !== undefined) this.retryAfter = opts.retryAfter;\n if (opts?.safeToRetry !== undefined) this.serverSafeToRetry = opts.safeToRetry;\n }\n\n get safeToRetry(): boolean {\n if (this.serverSafeToRetry !== undefined) return this.serverSafeToRetry;\n switch (this.code) {\n case \"RATE_LIMITER_UNAVAILABLE\":\n case \"INTERNAL_ERROR\":\n case \"TIMEOUT\":\n case \"NETWORK_ERROR\":\n return true;\n case \"RATE_LIMIT_EXCEEDED\":\n case \"RATE_LIMIT_EXCEEDED_DAILY\":\n case \"RATE_LIMIT_EXCEEDED_PER_USER\":\n case \"ISSUER_BUDGET_EXCEEDED\":\n return true;\n default:\n return false;\n }\n }\n}\n","import type { Address, Hex } from \"viem\";\nimport type { ENTRY_POINT_V08 } from \"@pafi-dev/core\";\nimport type { PafiBackendClient } from \"./client\";\nimport { PafiBackendError } from \"./types\";\nimport { PafiSdkError } from \"../errors\";\n\n/**\n * Typed errors thrown by the helpers below — issuer controllers map\n * these to the appropriate HTTP status. We don't depend on @nestjs/common\n * here because the SDK is framework-agnostic; the consuming controller\n * wraps each one in its preferred exception class.\n */\nexport class BundlerNotConfiguredError extends PafiSdkError {\n readonly code = \"BUNDLER_NOT_CONFIGURED\";\n readonly httpStatus = \"service_unavailable\" as const;\n constructor() {\n super(\n \"PAFI backend client not configured — set PAFI_BACKEND_URL, PAFI_ISSUER_ID, PAFI_API_KEY to enable mobile submit.\",\n );\n }\n}\n\nexport class BundlerRejectedError extends PafiSdkError {\n readonly code = \"BUNDLER_REJECTED\";\n readonly httpStatus = \"unprocessable\" as const;\n readonly cause: unknown;\n constructor(message: string, cause: unknown) {\n super(message);\n this.cause = cause;\n }\n}\n\nexport interface RequestPaymasterParams {\n /** PAFI backend client. When `null` / `undefined` → returns `undefined`. */\n client: PafiBackendClient | null | undefined;\n chainId: number;\n scenario: string;\n /** UserOp skeleton — must have all gas + fee fields set. */\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 };\n /** Target contract (typically the PointToken). */\n pointTokenAddress: Address;\n /**\n * Function name to surface in audit / paymaster context. Defaults to\n * a per-scenario sensible value (`mint` / `burn` / `swap` / generic\n * scenario name).\n */\n functionName?: string;\n /**\n * EIP-7702 authorization tuple — REQUIRED for the `delegate`\n * scenario. Forwarded to sponsor-relayer's `/paymaster/sponsor`\n * which embeds it into the UserOp before `pm_sponsorUserOperation`,\n * letting Pimlico simulate the delegation atomically with the\n * sponsored UserOp. Without this, simulator throws\n * `AA20 account not deployed`. v0.7.5 added.\n */\n eip7702Auth?: {\n chainId: string;\n address: string;\n nonce: string;\n r: string;\n s: string;\n yParity: string;\n };\n /** Optional logger for the \"sponsorship declined\" warning. */\n onWarning?: (msg: string) => void;\n}\n\n/**\n * Thin wrapper around `PafiBackendClient.requestSponsorship` with the\n * \"non-fatal on failure\" semantics every issuer wants:\n *\n * - When the client is missing → returns `undefined` (the caller falls\n * back to a self-funded UserOp).\n * - When the network call throws OR PAFI declines (rate limit, intent\n * rejection, paymaster outage) → returns `undefined` after logging,\n * so the controller doesn't hard-fail. The caller's\n * `prepareMobileUserOp` / `mergePaymasterFields` will gracefully\n * produce the unsponsored variant.\n *\n * Replaces ~30 LoC of try/catch + scenario-to-function mapping every\n * issuer would copy.\n */\nexport async function requestPaymaster(\n params: RequestPaymasterParams,\n): Promise<\n Awaited<ReturnType<PafiBackendClient[\"requestSponsorship\"]>> | undefined\n> {\n if (!params.client) return undefined;\n\n const fn = params.functionName ?? defaultFunctionForScenario(params.scenario);\n\n try {\n return await params.client.requestSponsorship({\n chainId: params.chainId,\n scenario: params.scenario,\n userOp: params.userOp,\n target: {\n contract: params.pointTokenAddress,\n function: fn,\n pointToken: params.pointTokenAddress,\n },\n ...(params.eip7702Auth ? { eip7702Auth: params.eip7702Auth } : {}),\n });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (err instanceof PafiBackendError && isTransientPaymasterError(err.code)) {\n params.onWarning?.(`Paymaster sponsorship declined (transient): ${msg}`);\n return undefined;\n }\n // Non-transient: rethrow so the issuer sees the real failure.\n throw err;\n }\n}\n\nfunction isTransientPaymasterError(code: string): boolean {\n switch (code) {\n case \"NETWORK_ERROR\":\n case \"TIMEOUT\":\n case \"PAYMASTER_UNAVAILABLE\":\n case \"RATE_LIMITER_UNAVAILABLE\":\n case \"INTERNAL_ERROR\":\n return true;\n default:\n return false;\n }\n}\n\nfunction defaultFunctionForScenario(scenario: string): string {\n switch (scenario) {\n case \"mint\":\n return \"mint\";\n case \"burn\":\n return \"burn\";\n case \"swap\":\n return \"swap\";\n case \"perp-deposit\":\n return \"deposit\";\n default:\n return scenario;\n }\n}\n\nexport interface RelayUserOpParams {\n client: PafiBackendClient | null | undefined;\n /** EntryPoint address — typically `ENTRY_POINT_V08` from core. */\n entryPoint: typeof ENTRY_POINT_V08 | string;\n userOp: Record<string, string | null>;\n /** EIP-7702 authorization (delegation UserOps only). */\n eip7702Auth?: {\n chainId: string;\n address: string;\n nonce: string;\n r: string;\n s: string;\n yParity: string;\n };\n}\n\n/**\n * Submit a serialized UserOp to the Pimlico bundler via PAFI's\n * sponsor-relayer. Handles the \"client missing\" / \"bundler rejected\"\n * branches as typed errors so the controller can map to HTTP cleanly.\n *\n * Every issuer mobile flow has this exact wrapper — moved into SDK\n * to drop ~30 LoC of try/catch + error-shape boilerplate per\n * controller.\n *\n * Throws:\n * - `BundlerNotConfiguredError` — caller didn't configure\n * `PafiBackendClient`. Map to 503.\n * - `BundlerRejectedError` — bundler returned an error. Map to 422\n * (the FE can show the reason — usually `AA21` / `AA34` / etc.).\n */\nexport async function relayUserOp(\n params: RelayUserOpParams,\n): Promise<{ userOpHash: Hex }> {\n if (!params.client) {\n throw new BundlerNotConfiguredError();\n }\n\n try {\n const result = await params.client.relayUserOperation({\n userOp: params.userOp,\n entryPoint: params.entryPoint as string,\n eip7702Auth: params.eip7702Auth,\n });\n return { userOpHash: result.userOpHash };\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new BundlerRejectedError(msg, err);\n }\n}\n","import type { Address, PublicClient, WalletClient } from \"viem\";\nimport { getAddress } from \"viem\";\nimport {\n decodeBatchExecuteCalls,\n getContractAddresses,\n type PartialUserOperation,\n} from \"@pafi-dev/core\";\n\ntype DecodedCall = ReturnType<typeof decodeBatchExecuteCalls>[number];\nimport type { IPointLedger } from \"../../ledger/types\";\nimport type { RelayService } from \"../../relay/relayService\";\nimport type { FeeManager } from \"../../relay/feeManager\";\nimport { IssuerStateError } from \"../../issuer-state/types\";\nimport { PafiSdkError } from \"../../errors\";\n\n/**\n * Structural shape — accepts both the raw `IssuerStateValidator` from\n * `@pafi-dev/issuer/issuer-state` and any host-framework wrapper (e.g.\n * a NestJS Injectable that delegates to it). Only `preValidateMint`\n * is needed on this surface.\n */\nexport interface IssuerStateValidatorLike {\n preValidateMint(pointToken: Address, amount: bigint): Promise<unknown>;\n}\n\n/**\n * v1.4 sig-gated mint handler — mirrors `PTRedeemHandler` on the mint side.\n *\n * Pre-validates against IssuerRegistry + on-chain totalSupply, locks the\n * off-chain balance, builds the sponsored UserOp (mint + PT fee\n * transfer) plus an optional fallback variant (mint only — for\n * paymaster-refused fallback).\n *\n * Caller fetches AA + mintRequest nonces (so issuers can plug in their\n * own composer — gg56 uses a timestamp-key 2D nonce). Caller layers\n * paymaster sponsorship + sponsorAuth on top of the returned UserOps.\n */\nexport type PTClaimErrorCode =\n | \"INVALID_AMOUNT\"\n | \"VALIDATION_FAILED\"\n | \"BUILD_FAILED\";\n\nexport class PTClaimError extends PafiSdkError {\n readonly httpStatus = \"unprocessable\" as const;\n readonly code: PTClaimErrorCode;\n readonly details?: Record<string, unknown>;\n\n constructor(\n code: PTClaimErrorCode,\n message: string,\n details?: Record<string, unknown>,\n ) {\n super(message);\n this.code = code;\n this.details = details;\n }\n}\n\nexport interface PTClaimHandlerConfig {\n ledger: IPointLedger;\n relayService: RelayService;\n provider: PublicClient;\n /** Issuer minter signer wallet — passed through to RelayService.prepareMint. */\n issuerSignerWallet: WalletClient;\n\n /**\n * EIP-712 domain `name` for `MintRequest`. Typically the PointToken\n * ERC-20 name. RelayService will set chainId + verifyingContract from\n * the request.\n */\n pointTokenDomainName: string;\n\n /** Optional — when wired, used to estimate the PT gas-reimbursement fee. */\n feeService?: FeeManager;\n /** Optional — pre-validates issuer status + cap before locking balance. */\n issuerStateValidator?: IssuerStateValidatorLike;\n\n /** How long the off-chain balance lock survives if the mint never lands. Default 15 min. */\n lockDurationMs?: number;\n /** How far ahead of `now` to set the MintRequest deadline. Default 15 min. */\n signatureDeadlineSeconds?: number;\n now?: () => number;\n\n /**\n * Optional override for the v1.6+ MintFeeWrapper address. By default the\n * handler auto-resolves the wrapper from the request's `chainId` via\n * `getContractAddresses(chainId).mintFeeWrapper`. Set this only when:\n * - testing against a non-canonical wrapper deploy, or\n * - the SDK's hardcoded address is stale and you can't bump the SDK yet\n *\n * Pass the dead-zero address to opt OUT of the wrapper path (force direct\n * mint with no fee). Pass `undefined` (default) to use SDK's lookup.\n *\n * Caller responsibility either way:\n * - DAO must have called `IssuerRegistry.setMintFeeWrapper(...)` so\n * `addIssuer` cascades the recipient list.\n * - The PointToken must have been registered with the wrapper (via\n * `addIssuer` cascade or owner-only `wrapper.registerToken`).\n */\n mintFeeWrapperAddress?: Address;\n}\n\n/**\n * Recognised \"no wrapper\" sentinels — both the canonical zero address\n * and the SDK's `PLACEHOLDER_DEAD(\"dead\")` placeholder used when a chain\n * hasn't deployed the wrapper yet. Treated identically: skip wrapper,\n * fall back to direct mint.\n */\nfunction isNoWrapper(address: Address | undefined): boolean {\n if (!address) return true;\n const lower = address.toLowerCase();\n return (\n lower === \"0x0000000000000000000000000000000000000000\" ||\n lower === \"0x000000000000000000000000000000000000dead\"\n );\n}\n\nexport interface PTClaimRequest {\n authenticatedAddress: Address;\n userAddress: Address;\n amount: bigint;\n pointTokenAddress: Address;\n chainId: number;\n /** ERC-4337 account nonce for the user's EOA. */\n aaNonce: bigint;\n /** Current `mintRequestNonces[userAddress]` from PointToken. */\n mintRequestNonce: bigint;\n}\n\nexport interface PTClaimResponse {\n /** Sponsored UserOp — mint + PT fee transfer (when feeAmount > 0). */\n userOp: PartialUserOperation;\n /**\n * Fallback UserOp — mint only, no PT fee transfer. Present only when\n * `feeAmount > 0`. User pays gas in ETH directly.\n */\n fallback?: PartialUserOperation;\n lockId: string;\n feeAmount: bigint;\n signatureDeadline: bigint;\n expiresInSeconds: number;\n /** Decoded calls for the sponsored UserOp (convenience for FE-submit path). */\n calls: DecodedCall[];\n /** Decoded calls for the fallback UserOp (when present). */\n callsFallback?: DecodedCall[];\n}\n\nconst DEFAULT_LOCK_MS = 15 * 60 * 1000;\nconst DEFAULT_SIG_DEADLINE_SEC = 15 * 60;\n\nexport class PTClaimHandler {\n private readonly cfg: PTClaimHandlerConfig & {\n lockDurationMs: number;\n signatureDeadlineSeconds: number;\n now: () => number;\n };\n\n constructor(config: PTClaimHandlerConfig) {\n this.cfg = {\n ...config,\n lockDurationMs: config.lockDurationMs ?? DEFAULT_LOCK_MS,\n signatureDeadlineSeconds:\n config.signatureDeadlineSeconds ?? DEFAULT_SIG_DEADLINE_SEC,\n now: config.now ?? (() => Date.now()),\n };\n }\n\n async handle(request: PTClaimRequest): Promise<PTClaimResponse> {\n if (\n getAddress(request.authenticatedAddress) !==\n getAddress(request.userAddress)\n ) {\n throw new PTClaimError(\n \"VALIDATION_FAILED\",\n `userAddress (${request.userAddress}) does not match authenticated session (${request.authenticatedAddress})`,\n );\n }\n if (request.amount <= 0n) {\n throw new PTClaimError(\"INVALID_AMOUNT\", \"claim amount must be positive\");\n }\n\n if (this.cfg.issuerStateValidator) {\n try {\n await this.cfg.issuerStateValidator.preValidateMint(\n request.pointTokenAddress,\n request.amount,\n );\n } catch (err) {\n if (err instanceof IssuerStateError) throw err;\n throw new PTClaimError(\n \"VALIDATION_FAILED\",\n `issuer-state pre-validate failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n\n const chainAddresses = getContractAddresses(request.chainId);\n const { batchExecutor: batchExecutorAddress } = chainAddresses;\n\n // Resolve wrapper address: explicit override > SDK default > none.\n // Pass `undefined` to RelayService.prepareMint when no wrapper —\n // RelayService treats undefined as direct-mint mode.\n const wrapperOverride = this.cfg.mintFeeWrapperAddress;\n const wrapperFromSdk = chainAddresses.mintFeeWrapper;\n const resolvedWrapper: Address | undefined = wrapperOverride !== undefined\n ? (isNoWrapper(wrapperOverride) ? undefined : wrapperOverride)\n : (isNoWrapper(wrapperFromSdk) ? undefined : wrapperFromSdk);\n\n const lockId = await this.cfg.ledger.lockForMinting(\n request.userAddress,\n request.amount,\n this.cfg.lockDurationMs,\n request.pointTokenAddress,\n );\n\n try {\n const signatureDeadline = BigInt(\n Math.floor(this.cfg.now() / 1000) + this.cfg.signatureDeadlineSeconds,\n );\n\n const feeAmount = this.cfg.feeService\n ? await this.cfg.feeService.estimateGasFee()\n : 0n;\n\n const domain = {\n name: this.cfg.pointTokenDomainName,\n chainId: request.chainId,\n verifyingContract: request.pointTokenAddress,\n };\n\n let userOp: PartialUserOperation;\n try {\n userOp = await this.cfg.relayService.prepareMint({\n userAddress: request.userAddress,\n aaNonce: request.aaNonce,\n batchExecutorAddress,\n pointTokenAddress: request.pointTokenAddress,\n amount: request.amount,\n issuerSignerWallet: this.cfg.issuerSignerWallet,\n domain,\n mintRequestNonce: request.mintRequestNonce,\n deadline: signatureDeadline,\n mintFeeWrapperAddress: resolvedWrapper,\n // No feeAmount/feeRecipient — RelayService auto-resolves.\n });\n } catch (err) {\n throw new PTClaimError(\n \"BUILD_FAILED\",\n `prepareMint failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n let fallback: PartialUserOperation | undefined;\n if (feeAmount > 0n) {\n try {\n fallback = await this.cfg.relayService.prepareMint({\n userAddress: request.userAddress,\n aaNonce: request.aaNonce,\n batchExecutorAddress,\n pointTokenAddress: request.pointTokenAddress,\n amount: request.amount,\n issuerSignerWallet: this.cfg.issuerSignerWallet,\n domain,\n mintRequestNonce: request.mintRequestNonce,\n deadline: signatureDeadline,\n feeAmount: 0n,\n mintFeeWrapperAddress: resolvedWrapper,\n });\n } catch (err) {\n throw new PTClaimError(\n \"BUILD_FAILED\",\n `prepareMint (fallback) failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n\n const calls = decodeBatchExecuteCalls(userOp.callData);\n const callsFallback = fallback\n ? decodeBatchExecuteCalls(fallback.callData)\n : undefined;\n\n return {\n userOp,\n fallback,\n lockId,\n feeAmount,\n signatureDeadline,\n expiresInSeconds: Math.floor(this.cfg.lockDurationMs / 1000),\n calls,\n callsFallback,\n };\n } catch (err) {\n // Release the lock on ANY failure after acquire — fee quote, sign,\n // build, fallback build. Swallow release errors so the original\n // throw surfaces (don't shadow the real failure).\n await this.cfg.ledger.releaseLock(lockId).catch(() => {\n /* noop — lock will auto-expire via TTL */\n });\n throw err;\n }\n }\n}\n","import type { Address } from \"viem\";\nimport { PafiSdkError } from \"../errors\";\n\nexport interface IssuerRegistryRecord {\n issuerAddress: Address;\n signerAddress: Address;\n name: string;\n symbol: string;\n active: boolean;\n pointToken: Address;\n mintingOracle: Address;\n}\n\n/**\n * v1.6 — per-token cap config read from `MintingOracle.tokenCaps()`.\n * Caps moved off the issuer record so a single issuer can run many\n * PointTokens with different supply policies.\n */\nexport interface TokenCapRecord {\n declaredTotalSupply: bigint;\n capBasisPoints: number;\n}\n\nexport interface PreValidateMintResult {\n /** Registry record read at pre-validation time. */\n issuer: IssuerRegistryRecord;\n /** Per-token cap config from MintingOracle. */\n tokenCap: TokenCapRecord;\n /** Current on-chain PointToken.totalSupply(). */\n totalSupply: bigint;\n /** declaredTotalSupply × capBasisPoints / 10000. */\n hardCap: bigint;\n /** hardCap − totalSupply (clamped to 0). */\n remaining: bigint;\n}\n\n/**\n * Thrown by `IssuerStateValidator.preValidateMint()`.\n * `code` maps 1:1 to the HTTP error the issuer API surfaces to clients.\n *\n * `MINT_CAP_EXCEEDED` is `safeToRetry: true` because the cap may free\n * up between requests as other mints expire or settle. The other two\n * codes are configuration-time states — the FE can't recover by retry.\n */\nexport type IssuerStateErrorCode =\n | \"ISSUER_NOT_REGISTERED\"\n | \"ISSUER_INACTIVE\"\n | \"MINT_CAP_EXCEEDED\";\n\nexport class IssuerStateError extends PafiSdkError {\n readonly httpStatus = \"unprocessable\" as const;\n readonly code: IssuerStateErrorCode;\n readonly details?: Record<string, unknown>;\n readonly safeToRetry: boolean;\n\n constructor(\n code: IssuerStateErrorCode,\n message: string,\n details?: Record<string, unknown>,\n ) {\n super(message);\n this.code = code;\n this.details = details;\n this.safeToRetry = code === \"MINT_CAP_EXCEEDED\";\n }\n}\n","import type { Address, PublicClient } from \"viem\";\nimport {\n BROKER_HASHES,\n ORDERLY_RELAY_ABI,\n ORDERLY_VAULT_ABI,\n ORDERLY_VAULT_ADDRESSES,\n TOKEN_HASHES,\n buildPerpDepositViaRelay,\n computeAccountId,\n decodeBatchExecuteCalls,\n getContractAddresses,\n quoteOperatorFeeUsdt,\n type PartialUserOperation,\n} from \"@pafi-dev/core\";\n\ntype DecodedCall = ReturnType<typeof decodeBatchExecuteCalls>[number];\nimport { PafiSdkError } from \"../../errors\";\n\n/**\n * Orderly perp-deposit handler — builds the sponsored + fallback\n * UserOps for the PAFI Relay path.\n *\n * Resolves USDC + verifies the broker is whitelisted on the Vault,\n * quotes the Relay's USDC fee (covers LayerZero msg.value out of the\n * Relay's ETH reserve), then builds two UserOps:\n *\n * - **sponsored** — USDC.transfer(fee, PAFI) + USDC.approve(relay,\n * total) + Relay.deposit(req). User reimburses gas in USDC\n * (input-token rule — user holds USDC at start of batch).\n * - **fallback** — USDC.approve + Relay.deposit only. User pays\n * ERC-4337 gas in ETH. Built only when `feeAmount > 0`.\n *\n * `maxFee` is set to `quote × 1.5` — slippage cap on the Relay's\n * USD-pricing during the inclusion window. If the actual fee at\n * execution exceeds maxFee the Relay reverts (`FeeExceedsMax`).\n *\n * v0.7 — Switched gas fee from PT (output-side, mixed token UX) to\n * USDC (input-side, single token UX). User now only needs USDC to\n * complete the deposit, no separate PT balance required.\n */\nexport type PerpDepositErrorCode =\n | \"PERP_DEPOSIT_UNAVAILABLE\"\n | \"BROKER_NOT_WHITELISTED\"\n | \"RELAY_FEE_EXCEEDS_AMOUNT\"\n | \"FEE_EXCEEDS_AMOUNT\"\n | \"INVALID_AMOUNT\";\n\nexport class PerpDepositError extends PafiSdkError {\n readonly httpStatus = \"unprocessable\" as const;\n readonly code: PerpDepositErrorCode;\n readonly safeToRetry: boolean;\n\n constructor(code: PerpDepositErrorCode, message: string) {\n super(message);\n this.code = code;\n this.safeToRetry = code === \"RELAY_FEE_EXCEEDS_AMOUNT\";\n }\n}\n\nexport interface PerpDepositHandlerConfig {\n provider: PublicClient;\n /**\n * Slippage premium applied on top of the Relay quote when computing\n * `maxFee`. Default 50% (factor 150). The Relay reverts if actual fee\n * exceeds `maxFee` so a generous cap reduces re-quote churn.\n */\n maxFeePremiumBps?: number;\n /**\n * Gas units used by `quoteOperatorFeeUsdt` to size the USDC gas\n * reimbursement. Default 500_000 (covers approve + Relay.deposit\n * + LayerZero call). Pass a tighter Pimlico estimate if available.\n */\n gasUnits?: bigint;\n /** Premium bps for `quoteOperatorFeeUsdt`. Default 12_000 (1.2×). */\n gasPremiumBps?: number;\n}\n\nexport interface PerpDepositRequest {\n userAddress: Address;\n chainId: number;\n amount: bigint;\n brokerId: keyof typeof BROKER_HASHES;\n /** ERC-4337 account nonce. */\n aaNonce: bigint;\n}\n\nexport interface PerpDepositResponse {\n userOp: PartialUserOperation;\n fallback?: PartialUserOperation;\n\n feeAmount: bigint;\n relayTokenFee: bigint;\n maxFee: bigint;\n netDeposit: bigint;\n accountId: `0x${string}`;\n brokerHash: `0x${string}`;\n usdcAddress: Address;\n relayAddress: Address;\n\n calls: DecodedCall[];\n callsFallback?: DecodedCall[];\n}\n\nconst DEFAULT_MAX_FEE_PREMIUM_BPS = 5000; // 50%\n\nexport class PerpDepositHandler {\n private readonly cfg: PerpDepositHandlerConfig & {\n maxFeePremiumBps: number;\n };\n\n constructor(config: PerpDepositHandlerConfig) {\n this.cfg = {\n ...config,\n maxFeePremiumBps:\n config.maxFeePremiumBps ?? DEFAULT_MAX_FEE_PREMIUM_BPS,\n };\n }\n\n async handle(request: PerpDepositRequest): Promise<PerpDepositResponse> {\n if (request.amount <= 0n) {\n throw new PerpDepositError(\"INVALID_AMOUNT\", \"amount must be positive\");\n }\n\n const brokerHash = BROKER_HASHES[request.brokerId];\n const tokenHash = TOKEN_HASHES.USDC;\n\n const vault = ORDERLY_VAULT_ADDRESSES[request.chainId];\n if (!vault) {\n throw new PerpDepositError(\n \"PERP_DEPOSIT_UNAVAILABLE\",\n `no Orderly Vault for chainId ${request.chainId}`,\n );\n }\n\n const { orderlyRelay: relayAddress, pafiFeeRecipient } =\n getContractAddresses(request.chainId);\n\n const [usdcAddress, brokerAllowed] = await Promise.all([\n this.cfg.provider.readContract({\n address: vault,\n abi: ORDERLY_VAULT_ABI,\n functionName: \"getAllowedToken\",\n args: [tokenHash],\n }) as Promise<Address>,\n this.cfg.provider.readContract({\n address: vault,\n abi: ORDERLY_VAULT_ABI,\n functionName: \"getAllowedBroker\",\n args: [brokerHash],\n }) as Promise<boolean>,\n ]);\n\n if (!brokerAllowed) {\n throw new PerpDepositError(\n \"BROKER_NOT_WHITELISTED\",\n `broker \"${request.brokerId}\" is not whitelisted on Orderly Vault`,\n );\n }\n\n const accountId = computeAccountId(request.userAddress, brokerHash);\n\n const requestForQuote = {\n token: usdcAddress,\n receiver: request.userAddress,\n brokerHash,\n totalAmount: request.amount,\n maxFee: 0n,\n };\n\n // Quote USDC gas fee in parallel with the Relay's tokenFee. USDC\n // is 6-dec stable; `quoteOperatorFeeUsdt` returns USD raw at 6 dec\n // (semantically identical for USDC).\n const [relayTokenFee, usdcGasFee] = await Promise.all([\n this.cfg.provider.readContract({\n address: relayAddress,\n abi: ORDERLY_RELAY_ABI,\n functionName: \"quoteTokenFee\",\n args: [requestForQuote],\n }) as Promise<bigint>,\n quoteOperatorFeeUsdt({\n provider: this.cfg.provider,\n chainId: request.chainId,\n gasUnits: this.cfg.gasUnits,\n premiumBps: this.cfg.gasPremiumBps,\n }),\n ]);\n\n if (relayTokenFee >= request.amount) {\n throw new PerpDepositError(\n \"RELAY_FEE_EXCEEDS_AMOUNT\",\n `Relay quoted fee ${relayTokenFee} >= deposit amount ${request.amount}`,\n );\n }\n if (usdcGasFee > 0n && usdcGasFee >= request.amount) {\n throw new PerpDepositError(\n \"FEE_EXCEEDS_AMOUNT\",\n `USDC gas fee ${usdcGasFee} >= deposit amount ${request.amount}`,\n );\n }\n const maxFee =\n (relayTokenFee * BigInt(10000 + this.cfg.maxFeePremiumBps)) / 10000n;\n\n const depositReq = {\n token: usdcAddress,\n receiver: request.userAddress,\n brokerHash,\n totalAmount: request.amount,\n maxFee,\n };\n\n const sponsoredOp = buildPerpDepositViaRelay({\n userAddress: request.userAddress,\n aaNonce: request.aaNonce,\n relayAddress,\n request: depositReq,\n gasFeeUsdc: usdcGasFee,\n gasFeeUsdcRecipient: pafiFeeRecipient,\n });\n\n const fallbackOp =\n usdcGasFee > 0n\n ? buildPerpDepositViaRelay({\n userAddress: request.userAddress,\n aaNonce: request.aaNonce,\n relayAddress,\n request: depositReq,\n })\n : undefined;\n\n return {\n userOp: sponsoredOp,\n fallback: fallbackOp,\n feeAmount: usdcGasFee,\n relayTokenFee,\n maxFee,\n netDeposit: request.amount - relayTokenFee,\n accountId,\n brokerHash,\n usdcAddress,\n relayAddress,\n calls: decodeBatchExecuteCalls(sponsoredOp.callData),\n callsFallback: fallbackOp\n ? decodeBatchExecuteCalls(fallbackOp.callData)\n : undefined,\n };\n }\n}\n","import type { Address, Hex } from \"viem\";\nimport {\n ENTRY_POINT_V08,\n buildDelegationUserOp,\n buildEip7702Authorization,\n computeUserOpHash,\n buildUserOpTypedData,\n getContractAddresses,\n serializeUserOpToJsonRpc,\n} from \"@pafi-dev/core\";\nimport { requestPaymaster, relayUserOp } from \"../pafi-backend/helpers\";\nimport type { PafiBackendClient } from \"../pafi-backend/client\";\nimport {\n serializeEntryToJsonRpc,\n serializeUserOpTypedData,\n type IPendingUserOpStore,\n type SerializedUserOpTypedData,\n} from \"../userop-store\";\nimport {\n PendingUserOpForbiddenError,\n PendingUserOpNotFoundError,\n} from \"./mobileHandlers\";\nimport { getAddress } from \"viem\";\n\n/**\n * EIP-7702 delegation flow uses the same mobile prepare/submit pattern\n * as claim/redeem:\n *\n * prepare: user signs `authorization` locally (Privy hook)\n * → POST /delegate/prepare with `authSig`\n * → backend builds delegation-anchor UserOp + paymaster\n * sponsorship (with eip7702Auth in pm_sponsorUserOperation)\n * + persists pending entry → returns `{ lockId,\n * userOpHash, typedData }`\n *\n * submit: user signs `userOpHash` locally (signTypedData)\n * → POST /delegate/submit `{ lockId, userOpSig }`\n * → backend retrieves entry + embeds userOpSig + relays\n * with `eip7702Auth` field on the UserOp\n *\n * **Why two signatures?** EIP-7702 authorization sets the EOA's\n * bytecode (delegation), but `validateUserOp` ALSO requires the user\n * to sign the userOpHash with ECDSA — Simple7702Account.validateUserOp\n * recovers the signer from `userOp.signature` and compares to\n * `address(this)`. An empty `signature: \"0x\"` reverts with OZ's\n * `ECDSAInvalidSignatureLength(uint256)` (selector `0xfce698f7`) inside\n * AA23. v0.7.7 — replaced single-shot `handleDelegateSubmit` that\n * tried to submit unsigned UserOps with this 2-step prepare/submit.\n */\nexport interface HandleDelegatePrepareParams {\n userAddress: Address;\n chainId: number;\n /** EOA tx nonce; mobile fetches via publicClient.getTransactionCount. */\n delegationNonce: bigint;\n /** ERC-4337 account nonce. Caller fetches via EntryPoint.getNonce. */\n aaNonce: bigint;\n /**\n * 65-byte secp256k1 signature over the EIP-7702 authorization hash\n * (chainId, address=batchExecutor, nonce). User signs locally via\n * Privy `useSign7702Authorization` BEFORE calling this endpoint.\n */\n authSig: Hex | string;\n fees: { maxFeePerGas?: bigint; maxPriorityFeePerGas?: bigint };\n\n /** Issuer-generated lock id (UUID) keying the pending entry. */\n lockId: string;\n store: IPendingUserOpStore;\n /** Pending entry TTL (seconds). Match the issuer's mobile lock duration. */\n ttlSeconds: number;\n\n pafiBackendClient?: PafiBackendClient | null;\n onWarning?: (msg: string) => void;\n\n gasLimits?: {\n callGasLimit?: bigint;\n verificationGasLimit?: bigint;\n preVerificationGas?: bigint;\n };\n}\n\nexport interface HandleDelegatePrepareResult {\n lockId: string;\n userOpHash: Hex;\n typedData: SerializedUserOpTypedData;\n expiresInSeconds: number;\n /** True when paymaster sponsorship attached (gas free). */\n isSponsored: boolean;\n}\n\nexport interface HandleDelegateSubmitParams {\n lockId: string;\n /** Authenticated user EOA — must match the entry's `sender`. */\n authenticatedAddress: Address;\n /** User signature over the persisted userOpHash. */\n userOpSig: Hex;\n store: IPendingUserOpStore;\n pafiBackendClient?: PafiBackendClient | null;\n /** Defaults to `ENTRY_POINT_V08`. */\n entryPoint?: string;\n}\n\nexport interface HandleDelegateSubmitResult {\n userOpHash: Hex;\n}\n\nconst DEFAULT_DELEGATE_GAS = {\n callGasLimit: 100_000n,\n verificationGasLimit: 150_000n,\n preVerificationGas: 50_000n,\n};\n\n/**\n * Build delegation-anchor UserOp + obtain paymaster sponsorship +\n * persist as a pending entry. Mobile signs the returned userOpHash\n * locally and submits via `handleDelegateSubmit`.\n */\nexport async function handleDelegatePrepare(\n params: HandleDelegatePrepareParams,\n): Promise<HandleDelegatePrepareResult> {\n const { batchExecutor } = getContractAddresses(params.chainId);\n\n // 1. Build the delegation-anchor UserOp (self-call EOA, empty calldata).\n const partial = buildDelegationUserOp({\n userAddress: params.userAddress,\n aaNonce: params.aaNonce,\n gasLimits: {\n callGasLimit:\n params.gasLimits?.callGasLimit ?? DEFAULT_DELEGATE_GAS.callGasLimit,\n verificationGasLimit:\n params.gasLimits?.verificationGasLimit ??\n DEFAULT_DELEGATE_GAS.verificationGasLimit,\n preVerificationGas:\n params.gasLimits?.preVerificationGas ??\n DEFAULT_DELEGATE_GAS.preVerificationGas,\n },\n });\n\n const userOp = {\n sender: partial.sender,\n nonce: partial.nonce,\n callData: partial.callData,\n callGasLimit: partial.callGasLimit,\n verificationGasLimit: partial.verificationGasLimit,\n preVerificationGas: partial.preVerificationGas,\n maxFeePerGas: params.fees.maxFeePerGas ?? 0n,\n maxPriorityFeePerGas: params.fees.maxPriorityFeePerGas ?? 0n,\n };\n\n // 2. Build the EIP-7702 authorization tuple from the user's authSig.\n const authorization = buildEip7702Authorization({\n chainId: params.chainId,\n address: batchExecutor as Address,\n nonce: params.delegationNonce,\n authSig: params.authSig,\n });\n\n // 3. Request paymaster sponsorship — `eip7702Auth` lets Pimlico's\n // pm_sponsorUserOperation simulate validateUserOp on the freshly\n // delegated EOA bytecode (otherwise `AA20 account not deployed`).\n const paymasterFields = await requestPaymaster({\n client: params.pafiBackendClient,\n chainId: params.chainId,\n scenario: \"delegate\",\n userOp,\n pointTokenAddress: batchExecutor as Address,\n eip7702Auth: authorization,\n onWarning: params.onWarning,\n });\n\n // 4. Merge paymaster gas overrides BEFORE computing userOpHash —\n // Pimlico re-estimates gas during sponsor + signs over the new\n // values, so the hash must be over post-merge fields or both\n // paymaster sig (AA34) and user sig (AA24) fail.\n const merged = {\n sender: userOp.sender,\n nonce: userOp.nonce,\n callData: userOp.callData,\n callGasLimit:\n paymasterFields?.callGasLimit ?? userOp.callGasLimit,\n verificationGasLimit:\n paymasterFields?.verificationGasLimit ?? userOp.verificationGasLimit,\n preVerificationGas:\n paymasterFields?.preVerificationGas ?? userOp.preVerificationGas,\n maxFeePerGas:\n paymasterFields?.maxFeePerGas ?? userOp.maxFeePerGas,\n maxPriorityFeePerGas:\n paymasterFields?.maxPriorityFeePerGas ?? userOp.maxPriorityFeePerGas,\n paymaster: paymasterFields?.paymaster,\n paymasterVerificationGasLimit:\n paymasterFields?.paymasterVerificationGasLimit,\n paymasterPostOpGasLimit: paymasterFields?.paymasterPostOpGasLimit,\n paymasterData: paymasterFields?.paymasterData,\n };\n\n const userOpHash = computeUserOpHash(merged, params.chainId);\n const typed = buildUserOpTypedData(merged, params.chainId);\n\n // 5. Persist pending entry — submit will read it back.\n await params.store.save(\n params.lockId,\n {\n sender: merged.sender,\n nonce: merged.nonce.toString(10),\n callData: merged.callData,\n callGasLimit: merged.callGasLimit.toString(10),\n verificationGasLimit: merged.verificationGasLimit.toString(10),\n preVerificationGas: merged.preVerificationGas.toString(10),\n maxFeePerGas: merged.maxFeePerGas.toString(10),\n maxPriorityFeePerGas: merged.maxPriorityFeePerGas.toString(10),\n ...(merged.paymaster ? { paymaster: merged.paymaster } : {}),\n ...(merged.paymasterVerificationGasLimit\n ? {\n paymasterVerificationGasLimit:\n merged.paymasterVerificationGasLimit.toString(10),\n }\n : {}),\n ...(merged.paymasterPostOpGasLimit\n ? {\n paymasterPostOpGasLimit:\n merged.paymasterPostOpGasLimit.toString(10),\n }\n : {}),\n ...(merged.paymasterData ? { paymasterData: merged.paymasterData } : {}),\n chainId: params.chainId,\n userOpHash,\n eip7702Auth: authorization,\n },\n params.ttlSeconds,\n );\n\n return {\n lockId: params.lockId,\n userOpHash,\n typedData: serializeUserOpTypedData(typed),\n expiresInSeconds: params.ttlSeconds,\n isSponsored: !!paymasterFields,\n };\n}\n\n/**\n * Retrieve the persisted delegate UserOp, embed the user's signature,\n * and submit to the bundler with the cached `eip7702Auth` field.\n *\n * Throws:\n * - `PendingUserOpNotFoundError` — entry expired or already submitted (404)\n * - `PendingUserOpForbiddenError` — sender mismatch (403)\n * - `BundlerNotConfiguredError` — `pafiBackendClient` missing (503)\n * - `BundlerRejectedError` — bundler rejected the UserOp (422)\n */\nexport async function handleDelegateSubmit(\n params: HandleDelegateSubmitParams,\n): Promise<HandleDelegateSubmitResult> {\n const entry = await params.store.get(params.lockId);\n if (!entry) {\n throw new PendingUserOpNotFoundError(params.lockId);\n }\n if (\n getAddress(entry.sender) !== getAddress(params.authenticatedAddress)\n ) {\n throw new PendingUserOpForbiddenError(params.lockId);\n }\n if (!entry.eip7702Auth) {\n // Should never happen for delegate scenario — the prepare step\n // always persists eip7702Auth. Defensive: refuse rather than\n // silently relay an unsigned-delegation UserOp.\n throw new Error(\n `delegate entry ${params.lockId} missing eip7702Auth — prepare step did not run correctly`,\n );\n }\n\n const userOpJson = serializeEntryToJsonRpc(entry, params.userOpSig, \"sponsored\");\n\n const result = await relayUserOp({\n client: params.pafiBackendClient,\n userOp: userOpJson,\n entryPoint: params.entryPoint ?? ENTRY_POINT_V08,\n eip7702Auth: entry.eip7702Auth,\n });\n\n await params.store.delete(params.lockId);\n\n return { userOpHash: result.userOpHash };\n}\n\n// `serializeUserOpToJsonRpc` kept as a re-imported reference so tooling\n// that drops unused imports doesn't strip it.\nvoid serializeUserOpToJsonRpc;\n","import { randomUUID } from \"node:crypto\";\nimport type { Address, Hex, PublicClient, WalletClient } from \"viem\";\nimport { getAddress } from \"viem\";\nimport {\n buildAndSignSponsorAuth,\n computeAuthorizationHash,\n decodeBatchExecuteCalls,\n encodeBatchExecute,\n ENTRY_POINT_V08,\n getContractAddresses,\n parseEip7702DelegatedAddress,\n type BuiltSponsorAuth,\n type SponsorshipScenario,\n} from \"@pafi-dev/core\";\nimport type { IssuerService } from \"../config\";\nimport type { IPointLedger } from \"../ledger/types\";\nimport type { IPendingUserOpStore } from \"../userop-store/types\";\nimport type { PafiBackendClient } from \"../pafi-backend/client\";\nimport type { PTRedeemHandler } from \"./handlers/ptRedeemHandler\";\nimport type { PTClaimHandler } from \"./handlers/ptClaimHandler\";\nimport type { PerpDepositHandler } from \"./handlers/perpDepositHandler\";\nimport {\n handleClaimStatus,\n handleRedeemStatus,\n type MintStatusResponse,\n type BurnStatusResponse,\n} from \"./statusHandlers\";\nimport {\n handleMobilePrepare,\n handleMobileSubmit,\n type HandleMobilePrepareResult,\n} from \"./mobileHandlers\";\nimport {\n handleDelegatePrepare,\n handleDelegateSubmit,\n} from \"./delegateHandler\";\nimport type { SerializedUserOpTypedData } from \"../userop-store\";\n\n/**\n * Adapter that absorbs every \"framework-agnostic\" endpoint body into a\n * single class so issuer controllers stay thin (one line per endpoint).\n *\n * What this absorbs:\n * - Reading + reshaping IssuerApiHandlers responses into wire DTOs\n * (bigint → string, etc.)\n * - Composing handler.handle() output with sponsorAuth + decoded calls\n * - Wiring handleMobilePrepare / handleMobileSubmit / handleClaimStatus\n * - Building the EIP-7702 delegate UserOp + relay\n * - Quoting PT → USDT for the cashout preview\n *\n * What stays in the issuer controller:\n * - HTTP routing decorators (`@Get`, `@Post`, `@UseGuards`)\n * - Auth context extraction (`@User() user: AuthContext`)\n * - Body DTO classes (with framework-specific decorators)\n * - Nonce composer (issuer-specific; e.g. gg56 uses 2D timestamp keys)\n *\n * Every method that can throw a typed SDK error throws `PafiSdkError` —\n * the controller wraps every call with `try/catch + mapSdkError`, or\n * the adapter pre-binds an `errorMapper` and auto-translates.\n */\n\nexport interface IssuerApiAdapterConfig {\n issuerService: IssuerService;\n ledger: IPointLedger;\n provider: PublicClient;\n /** Issuer signer wallet — used for `buildSponsorAuth` (EIP-712 sign). */\n issuerSignerWallet: WalletClient;\n /** Optional issuer id — when omitted, sponsorAuth is skipped (returns `undefined`). */\n pafiIssuerId?: string;\n\n /** Sig-gated mint handler. Required for `claim` / `claimPrepare`. */\n ptClaimHandler?: PTClaimHandler | null;\n /** Reverse-flow handler. Required for `redeem` / `redeemPrepare`. */\n ptRedeemHandler?: PTRedeemHandler | null;\n /** Orderly perp-deposit handler. Required for `perpDeposit`. */\n perpHandler?: PerpDepositHandler | null;\n\n /** Pending UserOp store — required for mobile prepare/submit. */\n pendingUserOpStore: IPendingUserOpStore;\n /** PAFI backend client — required for mobile submit + delegate submit + status fallback. */\n pafiBackendClient?: PafiBackendClient | null;\n\n /** Optional logger surface for non-fatal warnings. */\n onWarning?: (msg: string) => void;\n}\n\n// ---------------------------------------------------------------------------\n// Wire DTO shapes\n//\n// Issuer controllers can declare their own DTO classes that match these\n// shapes — TypeScript structural typing ensures the adapter's output\n// fits the controller's response type without import coupling.\n// ---------------------------------------------------------------------------\n\nexport interface ConfigDto {\n chainId: number;\n contracts: Record<string, string | undefined>;\n}\n\nexport interface GasFeeDto {\n gasFeeUsdt: string;\n}\n\nexport interface PoolsDto {\n pools: unknown[];\n}\n\nexport interface UserDto {\n mintRequestNonce: string;\n receiverConsentNonce: string;\n offChainBalance: string;\n onChainBalance: string;\n totalBalance: string;\n /** @deprecated alias for `offChainBalance` */\n balance: string;\n isMinter: boolean;\n}\n\n// QuoteDto removed (2026-04-27) — quote moved to @pafi-dev/trading.\n\nexport interface DecodedCallDto {\n to: string;\n data: string;\n value: string;\n}\n\nexport interface ClaimDto {\n calls: DecodedCallDto[];\n callsFallback?: DecodedCallDto[];\n feeAmount: string;\n lockId: string;\n signatureDeadline: string;\n sponsorAuth?: BuiltSponsorAuth;\n}\n\nexport interface RedeemDto {\n calls: DecodedCallDto[];\n callsFallback?: DecodedCallDto[];\n feeAmount: string;\n lockId: string;\n lockIdFallback?: string;\n netCreditAmount: string;\n netCreditAmountFallback?: string;\n expiresInSeconds: number;\n signatureDeadline: string;\n sponsorAuth?: BuiltSponsorAuth;\n}\n\n// SwapDto removed (2026-04-27) — swap moved to @pafi-dev/trading.\n\nexport interface PerpDepositDto {\n calls: DecodedCallDto[];\n callsFallback?: DecodedCallDto[];\n relayTokenFee: string;\n maxFee: string;\n netDeposit: string;\n ptGasFee: string;\n accountId: Hex;\n brokerHash: Hex;\n usdcAddress: Address;\n relayAddress: Address;\n sponsorAuth?: BuiltSponsorAuth;\n}\n\nexport interface MobilePrepareDto {\n lockId: string;\n userOpHash: Hex;\n typedData: SerializedUserOpTypedData;\n userOpHashFallback?: Hex;\n typedDataFallback?: SerializedUserOpTypedData;\n feeAmount: string;\n signatureDeadline: string;\n expiresInSeconds: number;\n sponsored: boolean;\n needsDelegation: boolean;\n}\n\nexport interface RedeemPrepareDto extends MobilePrepareDto {\n netCreditAmount: string;\n netCreditAmountFallback?: string;\n}\n\nexport interface MobileSubmitDto {\n userOpHash: Hex;\n}\n\nexport interface DelegateStatusDto {\n isDelegated: boolean;\n batchExecutorAddress: Address;\n /**\n * EOA tx count fetched on-chain via `getTransactionCount(blockTag: 'pending')`.\n * Mobile passes this VERBATIM into Privy `signAuthorization({ ..., nonce })` —\n * MUST NOT hardcode `0` or fetch independently. For a fresh embedded wallet\n * it will be `\"0\"`, but re-delegation or post-activity wallets will be > 0.\n * Returned as decimal string (preserves bigint precision over JSON).\n */\n delegationNonce: string;\n chainId: number;\n}\n\nexport interface DelegatePrepareDto {\n /**\n * v0.7.7 — refactored to mobile prepare/submit pattern. Mobile signs\n * the EIP-7702 authorization LOCALLY (Privy `signAuthorization`),\n * then signs `userOpHash` (or `typedData`) BEFORE submit. See\n * `handleDelegatePrepare` for rationale.\n *\n * Pre-v0.7.7 callers expected `{ authorizationHash, delegationNonce,\n * batchExecutorAddress, chainId }` — `delegationNonce` +\n * `batchExecutorAddress` retained for back-compat so mobile can\n * compute the authorization hash if needed; `authorizationHash`\n * removed (mobile's Privy hook computes it locally).\n */\n lockId: string;\n userOpHash: Hex;\n typedData: SerializedUserOpTypedData;\n expiresInSeconds: number;\n isSponsored: boolean;\n /** Echoed for mobile to recompute the authorization hash if desired. */\n delegationNonce: string;\n batchExecutorAddress: Address;\n chainId: number;\n}\n\n// ---------------------------------------------------------------------------\n// Adapter\n// ---------------------------------------------------------------------------\n\nexport class AdapterMisconfiguredError extends Error {\n readonly code = \"ADAPTER_MISCONFIGURED\" as const;\n constructor(message: string) {\n super(message);\n this.name = \"AdapterMisconfiguredError\";\n }\n}\n\nexport class IssuerApiAdapter {\n private readonly cfg: IssuerApiAdapterConfig;\n\n constructor(config: IssuerApiAdapterConfig) {\n if (config.ptClaimHandler) {\n if (typeof config.ledger.bindMintUserOpHash !== \"function\") {\n throw new AdapterMisconfiguredError(\n \"ledger.bindMintUserOpHash is required when ptClaimHandler is wired (mobile claim flow). \" +\n \"Implement it on your IPointLedger or omit ptClaimHandler from IssuerApiAdapter config.\",\n );\n }\n if (typeof config.ledger.getMintLock !== \"function\") {\n throw new AdapterMisconfiguredError(\n \"ledger.getMintLock is required when ptClaimHandler is wired — claimStatus uses it to look up the lock.\",\n );\n }\n }\n if (config.ptRedeemHandler) {\n if (typeof config.ledger.reservePendingCredit !== \"function\") {\n throw new AdapterMisconfiguredError(\n \"ledger.reservePendingCredit is required when ptRedeemHandler is wired (burn/redeem reverse flow). \" +\n \"PTRedeemHandler also enforces this at construction; see ledger/types.ts comments.\",\n );\n }\n if (typeof config.ledger.bindCreditUserOpHash !== \"function\") {\n throw new AdapterMisconfiguredError(\n \"ledger.bindCreditUserOpHash is required when ptRedeemHandler is wired (mobile redeem flow).\",\n );\n }\n if (typeof config.ledger.getPendingCredit !== \"function\") {\n throw new AdapterMisconfiguredError(\n \"ledger.getPendingCredit is required when ptRedeemHandler is wired — redeemStatus uses it to look up the credit.\",\n );\n }\n }\n this.cfg = config;\n }\n\n // ------------------------------ Read endpoints ---------------------------\n\n async config(chainId: number): Promise<ConfigDto> {\n const result = await this.cfg.issuerService.api.handleConfig(chainId);\n return {\n chainId: result.chainId,\n contracts: result.contracts as Record<string, string | undefined>,\n };\n }\n\n async gasFee(): Promise<GasFeeDto> {\n const result = await this.cfg.issuerService.api.handleGasFee();\n return { gasFeeUsdt: result.gasFeeUsdt.toString() };\n }\n\n async pools(\n authenticatedAddress: Address,\n chainId: number,\n pointTokenAddress: Address,\n ): Promise<PoolsDto> {\n const result = await this.cfg.issuerService.api.handlePools(\n authenticatedAddress,\n { chainId, pointTokenAddress: getAddress(pointTokenAddress) },\n );\n return { pools: result.pools };\n }\n\n async user(\n authenticatedAddress: Address,\n chainId: number,\n userAddress: Address,\n pointTokenAddress: Address,\n ): Promise<UserDto> {\n const result = await this.cfg.issuerService.api.handleUser(\n authenticatedAddress,\n {\n chainId,\n userAddress: getAddress(userAddress),\n pointTokenAddress: getAddress(pointTokenAddress),\n },\n );\n return {\n mintRequestNonce: result.mintRequestNonce.toString(),\n receiverConsentNonce: result.receiverConsentNonce.toString(),\n offChainBalance: result.offChainBalance.toString(),\n onChainBalance: result.onChainBalance.toString(),\n totalBalance: result.totalBalance.toString(),\n balance: result.offChainBalance.toString(),\n isMinter: result.isMinter,\n };\n }\n\n // quote() removed (2026-04-27) — FE PAFI calls @pafi-dev/trading\n // directly. Issuer SDK doesn't ship swap/quote anymore.\n\n // ------------------------------ Action endpoints -------------------------\n\n async claim(input: {\n authenticatedAddress: Address;\n chainId: number;\n pointTokenAddress: Address;\n amount: bigint;\n aaNonce: bigint;\n mintRequestNonce: bigint;\n }): Promise<ClaimDto> {\n const ptClaimHandler = this.assertHandler(\n this.cfg.ptClaimHandler,\n \"ptClaimHandler\",\n \"claim\",\n );\n const pointTokenAddress = getAddress(input.pointTokenAddress);\n const result = await ptClaimHandler.handle({\n authenticatedAddress: input.authenticatedAddress,\n userAddress: input.authenticatedAddress,\n amount: input.amount,\n pointTokenAddress,\n chainId: input.chainId,\n aaNonce: input.aaNonce,\n mintRequestNonce: input.mintRequestNonce,\n });\n\n const sponsorAuth = await this.buildSponsorAuth(\n input.authenticatedAddress,\n result.userOp.callData,\n input.chainId,\n \"mint\",\n );\n\n return {\n calls: result.calls,\n callsFallback: result.callsFallback,\n feeAmount: result.feeAmount.toString(),\n lockId: result.lockId,\n signatureDeadline: result.signatureDeadline.toString(),\n sponsorAuth,\n };\n }\n\n async redeem(input: {\n authenticatedAddress: Address;\n chainId: number;\n amount: bigint;\n aaNonce: bigint;\n }): Promise<RedeemDto> {\n this.assertRedeemHandler();\n const response = await this.cfg.ptRedeemHandler!.handle({\n userAddress: input.authenticatedAddress,\n authenticatedAddress: input.authenticatedAddress,\n amount: input.amount,\n aaNonce: input.aaNonce,\n chainId: input.chainId,\n });\n\n const sponsorAuth = await this.buildSponsorAuth(\n input.authenticatedAddress,\n response.userOp.callData,\n input.chainId,\n \"burn\",\n );\n\n return {\n calls: decodeBatchExecuteCalls(response.userOp.callData),\n callsFallback: response.fallback\n ? decodeBatchExecuteCalls(response.fallback.userOp.callData)\n : undefined,\n feeAmount: response.feeAmount.toString(),\n lockId: response.lockId,\n lockIdFallback: response.fallback?.lockId,\n netCreditAmount: response.netCreditAmount.toString(),\n netCreditAmountFallback: response.fallback?.netCreditAmount.toString(),\n expiresInSeconds: response.expiresInSeconds,\n signatureDeadline: response.signatureDeadline.toString(),\n sponsorAuth,\n };\n }\n\n // swap() removed (2026-04-27) — moved to @pafi-dev/trading.\n // PAFI's web FE calls TradingHandlers.handleSwap directly.\n\n async perpDeposit(input: {\n authenticatedAddress: Address;\n chainId: number;\n amount: bigint;\n brokerId: Parameters<PerpDepositHandler[\"handle\"]>[0][\"brokerId\"];\n aaNonce: bigint;\n }): Promise<PerpDepositDto> {\n const perpHandler = this.assertHandler(\n this.cfg.perpHandler,\n \"perpHandler\",\n \"perpDeposit\",\n );\n const result = await perpHandler.handle({\n userAddress: input.authenticatedAddress,\n chainId: input.chainId,\n amount: input.amount,\n brokerId: input.brokerId,\n aaNonce: input.aaNonce,\n });\n\n const sponsorAuth = await this.buildSponsorAuth(\n input.authenticatedAddress,\n result.userOp.callData,\n input.chainId,\n \"perp-deposit\",\n );\n\n return {\n calls: result.calls,\n callsFallback: result.callsFallback,\n relayTokenFee: result.relayTokenFee.toString(),\n maxFee: result.maxFee.toString(),\n netDeposit: result.netDeposit.toString(),\n ptGasFee: result.feeAmount.toString(),\n accountId: result.accountId,\n brokerHash: result.brokerHash,\n usdcAddress: result.usdcAddress,\n relayAddress: result.relayAddress,\n sponsorAuth,\n };\n }\n\n // ------------------------------ Mobile endpoints -------------------------\n\n async claimPrepare(input: {\n authenticatedAddress: Address;\n chainId: number;\n pointTokenAddress: Address;\n amount: bigint;\n aaNonce: bigint;\n mintRequestNonce: bigint;\n }): Promise<MobilePrepareDto> {\n const ptClaimHandler = this.assertHandler(\n this.cfg.ptClaimHandler,\n \"ptClaimHandler\",\n \"claimPrepare\",\n );\n const pointTokenAddress = getAddress(input.pointTokenAddress);\n const claimResult = await ptClaimHandler.handle({\n authenticatedAddress: input.authenticatedAddress,\n userAddress: input.authenticatedAddress,\n amount: input.amount,\n pointTokenAddress,\n chainId: input.chainId,\n aaNonce: input.aaNonce,\n mintRequestNonce: input.mintRequestNonce,\n });\n\n const prepared = await this.runMobilePrepare(\n input.authenticatedAddress,\n input.chainId,\n claimResult.lockId,\n claimResult.userOp,\n claimResult.fallback,\n \"mint\",\n pointTokenAddress,\n claimResult.expiresInSeconds,\n );\n\n return {\n lockId: claimResult.lockId,\n userOpHash: prepared.sponsored.userOpHash,\n typedData: prepared.sponsored.typedData,\n userOpHashFallback: prepared.fallback?.userOpHash,\n typedDataFallback: prepared.fallback?.typedData,\n feeAmount: claimResult.feeAmount.toString(),\n signatureDeadline: claimResult.signatureDeadline.toString(),\n expiresInSeconds: claimResult.expiresInSeconds,\n sponsored: prepared.isSponsored,\n needsDelegation: prepared.needsDelegation,\n };\n }\n\n async claimSubmit(input: {\n authenticatedAddress: Address;\n lockId: string;\n signature: Hex;\n variant?: \"sponsored\" | \"fallback\";\n }): Promise<MobileSubmitDto> {\n return await handleMobileSubmit({\n lockId: input.lockId,\n authenticatedAddress: input.authenticatedAddress,\n signature: input.signature,\n variant: input.variant,\n store: this.cfg.pendingUserOpStore,\n bindUserOpHash: (lockId, hash) =>\n this.cfg.ledger.bindMintUserOpHash!(lockId, hash),\n pafiBackendClient: this.cfg.pafiBackendClient,\n });\n }\n\n async redeemPrepare(input: {\n authenticatedAddress: Address;\n chainId: number;\n pointTokenAddress: Address;\n amount: bigint;\n aaNonce: bigint;\n }): Promise<RedeemPrepareDto> {\n this.assertRedeemHandler();\n const pointTokenAddress = getAddress(input.pointTokenAddress);\n\n const redeemResponse = await this.cfg.ptRedeemHandler!.handle({\n userAddress: input.authenticatedAddress,\n authenticatedAddress: input.authenticatedAddress,\n amount: input.amount,\n aaNonce: input.aaNonce,\n chainId: input.chainId,\n });\n\n const prepared = await this.runMobilePrepare(\n input.authenticatedAddress,\n input.chainId,\n redeemResponse.lockId,\n redeemResponse.userOp,\n redeemResponse.fallback?.userOp,\n \"burn\",\n pointTokenAddress,\n redeemResponse.expiresInSeconds,\n );\n\n return {\n lockId: redeemResponse.lockId,\n userOpHash: prepared.sponsored.userOpHash,\n typedData: prepared.sponsored.typedData,\n userOpHashFallback: prepared.fallback?.userOpHash,\n typedDataFallback: prepared.fallback?.typedData,\n netCreditAmount: redeemResponse.netCreditAmount.toString(),\n netCreditAmountFallback:\n redeemResponse.fallback?.netCreditAmount.toString(),\n feeAmount: redeemResponse.feeAmount.toString(),\n signatureDeadline: redeemResponse.signatureDeadline.toString(),\n expiresInSeconds: redeemResponse.expiresInSeconds,\n sponsored: prepared.isSponsored,\n needsDelegation: prepared.needsDelegation,\n };\n }\n\n async redeemSubmit(input: {\n authenticatedAddress: Address;\n lockId: string;\n signature: Hex;\n variant?: \"sponsored\" | \"fallback\";\n }): Promise<MobileSubmitDto> {\n return await handleMobileSubmit({\n lockId: input.lockId,\n authenticatedAddress: input.authenticatedAddress,\n signature: input.signature,\n variant: input.variant,\n store: this.cfg.pendingUserOpStore,\n bindUserOpHash: (lockId, hash) =>\n this.cfg.ledger.bindCreditUserOpHash!(lockId, hash),\n pafiBackendClient: this.cfg.pafiBackendClient,\n });\n }\n\n async claimStatus(\n authenticatedAddress: Address,\n lockId: string,\n ): Promise<MintStatusResponse> {\n return await handleClaimStatus({\n lockId,\n userAddress: authenticatedAddress,\n ledger: this.cfg.ledger,\n pafiBackendClient: this.cfg.pafiBackendClient,\n onWarning: this.cfg.onWarning,\n });\n }\n\n async redeemStatus(\n authenticatedAddress: Address,\n lockId: string,\n ): Promise<BurnStatusResponse> {\n return await handleRedeemStatus({\n lockId,\n userAddress: authenticatedAddress,\n ledger: this.cfg.ledger,\n pafiBackendClient: this.cfg.pafiBackendClient,\n onWarning: this.cfg.onWarning,\n });\n }\n\n // ------------------------------ Delegate endpoints -----------------------\n\n async delegateStatus(\n authenticatedAddress: Address,\n chainId: number,\n ): Promise<DelegateStatusDto> {\n const { batchExecutor } = getContractAddresses(chainId);\n // v0.7.8 — fetch delegationNonce on-chain so mobile doesn't have to.\n // Mobile MUST use this exact value when calling Privy\n // `signAuthorization({ contractAddress, chainId, nonce })`. Hardcoding\n // `0` works only for the very first delegation of a brand-new EOA;\n // re-delegate / post-activity wallets have non-zero nonce and any\n // mismatch reverts AA23 \"invalid authorization nonce\".\n //\n // `blockTag: 'pending'` includes mempool to avoid race when the user\n // has another tx in flight (rare for embedded wallets, but free\n // safety margin).\n const [code, nonce] = await Promise.all([\n this.cfg.provider.getCode({ address: authenticatedAddress }),\n this.cfg.provider.getTransactionCount({\n address: authenticatedAddress,\n blockTag: \"pending\",\n }),\n ]);\n return {\n isDelegated: parseEip7702DelegatedAddress(code) !== null,\n batchExecutorAddress: batchExecutor,\n delegationNonce: nonce.toString(),\n chainId,\n };\n }\n\n /**\n * Build the delegation-anchor UserOp + obtain paymaster sponsorship\n * + persist as a pending entry. Mobile must:\n *\n * 1. Sign EIP-7702 authorization LOCALLY (Privy `signAuthorization`\n * with `{contractAddress: batchExecutorAddress, chainId,\n * nonce: delegationNonce}`) → 65-byte authSig hex.\n * 2. POST `/delegate/prepare` with `{ chainId, delegationNonce,\n * authSig }` → this method.\n * 3. Sign returned `userOpHash` LOCALLY (`signTypedData(typedData)`).\n * 4. POST `/delegate/submit` with `{ lockId, userOpSig }`.\n *\n * v0.7.7 — replaces single-shot delegateSubmit that tried to relay\n * a UserOp with empty `signature: \"0x\"` (Simple7702Account's\n * validateUserOp reverts `ECDSAInvalidSignatureLength` 0xfce698f7).\n */\n async delegatePrepare(\n authenticatedAddress: Address,\n input: {\n chainId: number;\n delegationNonce: bigint;\n authSig: Hex | string;\n aaNonce: bigint;\n },\n ): Promise<DelegatePrepareDto> {\n const { batchExecutor } = getContractAddresses(input.chainId);\n const fees = await this.cfg.provider.estimateFeesPerGas();\n const lockId = randomUUID();\n\n const result = await handleDelegatePrepare({\n userAddress: authenticatedAddress,\n chainId: input.chainId,\n delegationNonce: input.delegationNonce,\n aaNonce: input.aaNonce,\n authSig: input.authSig,\n fees,\n lockId,\n store: this.cfg.pendingUserOpStore,\n ttlSeconds: 15 * 60, // 15min — match claim/redeem mobile lock duration\n pafiBackendClient: this.cfg.pafiBackendClient,\n onWarning: this.cfg.onWarning,\n });\n\n return {\n lockId: result.lockId,\n userOpHash: result.userOpHash,\n typedData: result.typedData,\n expiresInSeconds: result.expiresInSeconds,\n isSponsored: result.isSponsored,\n delegationNonce: input.delegationNonce.toString(),\n batchExecutorAddress: batchExecutor,\n chainId: input.chainId,\n };\n }\n\n async delegateSubmit(input: {\n authenticatedAddress: Address;\n lockId: string;\n userOpSig: Hex;\n }): Promise<MobileSubmitDto> {\n const result = await handleDelegateSubmit({\n lockId: input.lockId,\n authenticatedAddress: input.authenticatedAddress,\n userOpSig: input.userOpSig,\n store: this.cfg.pendingUserOpStore,\n pafiBackendClient: this.cfg.pafiBackendClient,\n });\n return { userOpHash: result.userOpHash };\n }\n\n // ------------------------------ Internal helpers -------------------------\n\n /**\n * Build + sign a SponsorAuth payload. Returns `undefined` when no\n * issuer id is configured, so the controller can skip the field.\n */\n private async buildSponsorAuth(\n authenticatedAddress: Address,\n callData: Hex,\n chainId: number,\n scenario: SponsorshipScenario,\n ): Promise<BuiltSponsorAuth | undefined> {\n if (!this.cfg.pafiIssuerId) return undefined;\n return buildAndSignSponsorAuth({\n userAddress: authenticatedAddress,\n callData,\n chainId,\n scenario,\n issuerId: this.cfg.pafiIssuerId,\n issuerSignerWallet: this.cfg.issuerSignerWallet,\n });\n }\n\n private async runMobilePrepare(\n authenticatedAddress: Address,\n chainId: number,\n lockId: string,\n partialUserOp: Parameters<typeof handleMobilePrepare>[0][\"partialUserOp\"],\n partialUserOpFallback: Parameters<\n typeof handleMobilePrepare\n >[0][\"partialUserOpFallback\"],\n scenario: SponsorshipScenario,\n pointTokenAddress: Address,\n ttlSeconds: number,\n ): Promise<HandleMobilePrepareResult> {\n return await handleMobilePrepare({\n userAddress: authenticatedAddress,\n chainId,\n lockId,\n partialUserOp,\n partialUserOpFallback,\n scenario,\n pointTokenAddress,\n ttlSeconds,\n store: this.cfg.pendingUserOpStore,\n provider: this.cfg.provider,\n pafiBackendClient: this.cfg.pafiBackendClient,\n onWarning: this.cfg.onWarning,\n });\n }\n\n private assertRedeemHandler(): void {\n if (!this.cfg.ptRedeemHandler) {\n throw new Error(\n \"PTRedeemHandler not wired — IssuerApiAdapter.redeem* require a configured ptRedeemHandler.\",\n );\n }\n }\n\n /**\n * Narrow an optional handler to non-null and throw a clear error when\n * the issuer wired the adapter without it. Lets issuers opt out of\n * flows they don't expose (gg56 ships only mobile claim/redeem, so\n * `swapHandler` + `perpHandler` aren't constructed).\n */\n private assertHandler<T>(\n handler: T | null | undefined,\n fieldName: string,\n methodName: string,\n ): T {\n if (handler === null || handler === undefined) {\n throw new Error(\n `${fieldName} not wired — IssuerApiAdapter.${methodName}() requires a configured ${fieldName}.`,\n );\n }\n return handler;\n }\n}\n\n// Reference exports kept to silence \"unused import\" warnings on\n// generators that drop unused identifiers, since the helpers are still\n// referenced from method signatures via parameter inference.\nvoid encodeBatchExecute;\nvoid ENTRY_POINT_V08;\n","import { isAddress, type Address } from \"viem\";\nimport type { PoolKey } from \"@pafi-dev/core\";\nimport type {\n ApiPoolsRequest,\n ApiPoolsResponse,\n PoolsProvider,\n} from \"../api/types\";\n\nimport { PAFI_SUBGRAPH_URL } from \"@pafi-dev/core\";\nexport { PAFI_SUBGRAPH_URL };\n\n/**\n * Config for `createSubgraphPoolsProvider`.\n */\nexport interface SubgraphPoolsProviderConfig {\n /**\n * Fully qualified subgraph GraphQL endpoint.\n * Defaults to the PAFI-hosted subgraph (`PAFI_SUBGRAPH_URL`).\n * Override only when pointing at a staging or custom deployment.\n */\n subgraphUrl?: string;\n\n /**\n * Cache TTL in milliseconds. Pool discovery is near-static — a 30s\n * cache removes subgraph load without meaningfully delaying UX.\n * Set to 0 to disable caching. Default: 30_000.\n */\n cacheTtlMs?: number;\n\n /**\n * Optional fetch override for test harnesses. Defaults to global `fetch`.\n */\n fetchImpl?: typeof fetch;\n\n /**\n * Optional clock override for tests.\n */\n now?: () => number;\n}\n\ninterface CacheEntry {\n expiresAt: number;\n pools: PoolKey[];\n}\n\ninterface PafiTokenWithPool {\n id: string;\n pool: {\n id: string;\n feeTier: string;\n tickSpacing: string;\n hooks: string;\n token0: { id: string };\n token1: { id: string };\n } | null;\n}\n\ninterface GraphQLResponse {\n data?: { pafiToken: PafiTokenWithPool | null };\n errors?: { message: string }[];\n}\n\nconst DEFAULT_CACHE_TTL_MS = 30_000;\n\nconst POOL_QUERY = `\n query GetPoolForPointToken($id: ID!) {\n pafiToken(id: $id) {\n id\n pool {\n id\n feeTier\n tickSpacing\n hooks\n token0 { id }\n token1 { id }\n }\n }\n }\n`;\n\n/**\n * Create a `PoolsProvider` backed by the PAFI subgraph.\n *\n * Queries the `pafiTokens` entity for the given `pointTokenAddress`,\n * reads its linked `Pool`, and returns a single-element `PoolKey[]`.\n * Multiple pools per token would require a subgraph schema change.\n *\n * The result is cached in-process with a short TTL (default 30s). Pool\n * discovery is near-static so this avoids hammering the subgraph without\n * blocking config changes for long.\n *\n * Returns `{ pools: [] }` if:\n * - the token is not registered on PAFI yet (no PafiToken entity)\n * - the token is registered but its pool has not been initialised\n * - the subgraph is unreachable or returns an error (logs to console,\n * does not throw — callers should handle empty pool list gracefully)\n *\n * Assumes the PAFI subgraph schema. Issuers with a custom subgraph must\n * implement `PoolsProvider` themselves instead of using this helper.\n *\n * @example\n * ```ts\n * import { createSubgraphPoolsProvider, createIssuerService } from \"@pafi/issuer\";\n *\n * const service = createIssuerService({\n * // ...other config\n * poolsProvider: createSubgraphPoolsProvider({\n * subgraphUrl: \"https://graph.pacificfinance.org/subgraphs/name/pafi\",\n * }),\n * });\n * ```\n */\nexport function createSubgraphPoolsProvider(\n config: SubgraphPoolsProviderConfig = {},\n): PoolsProvider {\n const subgraphUrl = config.subgraphUrl ?? PAFI_SUBGRAPH_URL;\n\n try {\n const parsed = new URL(subgraphUrl);\n if (process.env.NODE_ENV === \"production\" && parsed.protocol !== \"https:\") {\n throw new Error(\"subgraphUrl must use HTTPS in production\");\n }\n } catch (err) {\n if (err instanceof TypeError) {\n throw new Error(\n `subgraphPoolsProvider: invalid subgraphUrl: ${subgraphUrl}`,\n );\n }\n throw err;\n }\n\n const cacheTtl = config.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;\n const fetchImpl = config.fetchImpl ?? globalThis.fetch;\n const now = config.now ?? (() => Date.now());\n const cache = new Map<string, CacheEntry>();\n\n if (!fetchImpl) {\n throw new Error(\n \"createSubgraphPoolsProvider: no fetch implementation available — pass `fetchImpl` or run on Node 18+\",\n );\n }\n\n return async (request: ApiPoolsRequest): Promise<ApiPoolsResponse> => {\n const cacheKey = `${request.chainId}:${request.pointTokenAddress.toLowerCase()}`;\n\n if (cacheTtl > 0) {\n const cached = cache.get(cacheKey);\n if (cached && cached.expiresAt > now()) {\n return { pools: cached.pools };\n }\n }\n\n const pools = await fetchPoolsFromSubgraph(\n fetchImpl,\n subgraphUrl,\n request.pointTokenAddress,\n );\n\n if (cacheTtl > 0) {\n cache.set(cacheKey, {\n expiresAt: now() + cacheTtl,\n pools,\n });\n }\n\n return { pools };\n };\n}\n\nasync function fetchPoolsFromSubgraph(\n fetchImpl: typeof fetch,\n subgraphUrl: string,\n pointTokenAddress: Address,\n): Promise<PoolKey[]> {\n let response: Response;\n try {\n response = await fetchImpl(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 // Network failure — surface empty list, caller decides UX.\n console.warn(\n \"[subgraphPoolsProvider] subgraph unreachable:\",\n (err as Error).message,\n );\n return [];\n }\n\n if (!response.ok) {\n console.warn(\n `[subgraphPoolsProvider] subgraph returned ${response.status}`,\n );\n return [];\n }\n\n const json = (await response.json()) as GraphQLResponse;\n if (json.errors && json.errors.length > 0) {\n console.warn(\n \"[subgraphPoolsProvider] subgraph errors:\",\n json.errors.map((e) => e.message).join(\"; \"),\n );\n return [];\n }\n\n const token = json.data?.pafiToken;\n if (!token || !token.pool) {\n return [];\n }\n\n const { pool } = token;\n\n if (!isAddress(pool.hooks)) {\n console.error(\n \"[PAFI] SubgraphPoolsProvider: invalid hooks address in response:\",\n pool.hooks,\n \"— skipping pool\",\n );\n return [];\n }\n if (!isAddress(pool.token0.id) || !isAddress(pool.token1.id)) {\n console.error(\n \"[PAFI] SubgraphPoolsProvider: invalid token address in response — skipping pool\",\n );\n return [];\n }\n if (\n !Number.isFinite(Number(pool.feeTier)) ||\n !Number.isFinite(Number(pool.tickSpacing))\n ) {\n console.error(\n \"[PAFI] SubgraphPoolsProvider: invalid feeTier/tickSpacing — skipping pool\",\n );\n return [];\n }\n\n const [currency0, currency1] = sortCurrencies(\n pool.token0.id as Address,\n pool.token1.id as Address,\n );\n\n const poolKey: PoolKey = {\n currency0,\n currency1,\n fee: Number(pool.feeTier),\n tickSpacing: Number(pool.tickSpacing),\n hooks: pool.hooks as Address,\n };\n\n return [poolKey];\n}\n\n/**\n * Uniswap V4 requires `currency0 < currency1` in the PoolKey. Subgraph\n * stores token0/token1 already sorted, but we re-sort defensively in\n * case of schema drift.\n */\nfunction sortCurrencies(a: Address, b: Address): [Address, Address] {\n return a.toLowerCase() < b.toLowerCase() ? [a, b] : [b, a];\n}\n","import { PAFI_SUBGRAPH_URL } from \"./subgraphPoolsProvider\";\n\n/**\n * Config for `createSubgraphNativeUsdtQuoter`.\n */\nexport interface SubgraphNativeUsdtQuoterConfig {\n /**\n * Fully qualified subgraph GraphQL endpoint.\n * Defaults to the PAFI-hosted subgraph (`PAFI_SUBGRAPH_URL`).\n * Override only when pointing at a staging or custom deployment.\n */\n subgraphUrl?: string;\n\n /**\n * Decimals of the USDT token. Defaults to 6 (standard USDT/USDC on\n * Base, Ethereum, Polygon). Override for chains where USDT uses a\n * different decimals value.\n */\n usdtDecimals?: number;\n\n /**\n * Decimals of the native token (ETH on Base/mainnet/Arbitrum/Optimism,\n * MATIC on Polygon). Default: 18.\n */\n nativeDecimals?: number;\n\n /**\n * Cache TTL in milliseconds. ETH price drifts slowly relative to gas\n * estimation needs — a 30s cache keeps fees stable across bursts of\n * requests without letting them go stale during volatile markets.\n * Set to 0 to disable caching. Default: 30_000.\n */\n cacheTtlMs?: number;\n\n /**\n * Fallback price (USDT per native token, human-readable float) used\n * when the subgraph is unreachable. This keeps the backend operational\n * during subgraph outages rather than bricking cashouts. The fee will\n * be slightly off but the operator still gets paid. Default: 3000.\n */\n fallbackEthPriceUsd?: number;\n\n /** Optional fetch override for test harnesses. */\n fetchImpl?: typeof fetch;\n\n /** Optional clock override for tests. */\n now?: () => number;\n}\n\ninterface GraphQLResponse {\n data?: { bundle: { ethPriceUSD: string } | null };\n errors?: { message: string }[];\n}\n\nconst DEFAULT_CACHE_TTL_MS = 30_000;\nconst DEFAULT_FALLBACK_PRICE = 3000;\nconst DEFAULT_USDT_DECIMALS = 6;\nconst DEFAULT_NATIVE_DECIMALS = 18;\n\n/**\n * The PAFI subgraph maintains a single `Bundle` entity with id `\"1\"` that\n * stores the current ETH/USD price (updated on every Uniswap pool sync).\n * This is the same source the Uniswap analytics UIs use.\n */\nconst PRICE_QUERY = `\n query GetEthPrice {\n bundle(id: \"1\") {\n ethPriceUSD\n }\n }\n`;\n\n/**\n * Create a native→USDT quoter backed by the PAFI subgraph's\n * `Bundle.ethPriceUSD`. The returned function has the shape\n * `(amountNative: bigint) => Promise<bigint>` and can be passed as\n * `quoteNativeToFee` to `FeeManager` — in v1.4 the fee currency\n * is configured at the integration layer, not hardcoded here.\n *\n * Used by `FeeManager.estimateGasFee()` to convert the gas cost into\n * an ERC-20 amount charged as part of the sponsored UserOp batch.\n * Price precision is not critical — a 1-2% drift is acceptable since\n * the fee manager applies a `gasPremiumBps` buffer.\n *\n * The result is cached in-process with a short TTL (default 30s). If\n * the subgraph is unreachable, falls back to `fallbackEthPriceUsd` so\n * gas estimation doesn't block user flow during a subgraph outage.\n *\n * @example\n * ```ts\n * import { createSubgraphNativeUsdtQuoter, createIssuerService } from \"@pafi-dev/issuer\";\n *\n * const service = createIssuerService({\n * // ...other config\n * fee: {\n * quoteNativeToFee: createSubgraphNativeUsdtQuoter({\n * subgraphUrl: \"https://graph.pacificfinance.org/subgraphs/name/pafi\",\n * }),\n * },\n * });\n * ```\n */\nexport function createSubgraphNativeUsdtQuoter(\n config: SubgraphNativeUsdtQuoterConfig = {},\n): (amountNative: bigint) => Promise<bigint> {\n const subgraphUrl = config.subgraphUrl ?? PAFI_SUBGRAPH_URL;\n\n try {\n const parsed = new URL(subgraphUrl);\n if (process.env.NODE_ENV === \"production\" && parsed.protocol !== \"https:\") {\n throw new Error(\"subgraphUrl must use HTTPS in production\");\n }\n } catch (err) {\n if (err instanceof TypeError) {\n throw new Error(\n `createSubgraphNativeUsdtQuoter: invalid subgraphUrl: ${subgraphUrl}`,\n );\n }\n throw err;\n }\n\n const usdtDecimals = config.usdtDecimals ?? DEFAULT_USDT_DECIMALS;\n const nativeDecimals = config.nativeDecimals ?? DEFAULT_NATIVE_DECIMALS;\n const cacheTtl = config.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;\n const fallbackPrice = config.fallbackEthPriceUsd ?? DEFAULT_FALLBACK_PRICE;\n const fetchImpl = config.fetchImpl ?? globalThis.fetch;\n const now = config.now ?? (() => Date.now());\n\n if (!fetchImpl) {\n throw new Error(\n \"createSubgraphNativeUsdtQuoter: no fetch implementation available — pass `fetchImpl` or run on Node 18+\",\n );\n }\n\n // Cache: stores the USDT-per-native scaling factor (not the raw price\n // string) so conversion is a single bigint multiply per request.\n let cached: { usdtPerNative: bigint; expiresAt: number } | undefined;\n\n async function getUsdtPerNative(): Promise<bigint> {\n if (cacheTtl > 0 && cached && cached.expiresAt > now()) {\n return cached.usdtPerNative;\n }\n\n const price = await fetchEthPriceFromSubgraph(\n fetchImpl,\n subgraphUrl,\n );\n const usdtPerNative = toUsdtPerNative(\n price ?? fallbackPrice,\n usdtDecimals,\n );\n\n if (cacheTtl > 0) {\n cached = {\n usdtPerNative,\n expiresAt: now() + cacheTtl,\n };\n }\n\n return usdtPerNative;\n }\n\n return async (amountNative: bigint): Promise<bigint> => {\n if (amountNative === 0n) return 0n;\n const usdtPerNative = await getUsdtPerNative();\n // usdtPerNative is already scaled to USDT decimals (e.g. 3000 USD →\n // 3_000_000_000 for 6-decimal USDT). Dividing by 10^nativeDecimals\n // converts raw wei to whole native units implicitly.\n return (amountNative * usdtPerNative) / 10n ** BigInt(nativeDecimals);\n };\n}\n\nasync function fetchEthPriceFromSubgraph(\n fetchImpl: typeof fetch,\n subgraphUrl: string,\n): Promise<number | null> {\n let response: Response;\n try {\n response = await fetchImpl(subgraphUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ query: PRICE_QUERY }),\n });\n } catch (err) {\n console.warn(\n \"[subgraphNativeUsdtQuoter] subgraph unreachable:\",\n (err as Error).message,\n );\n return null;\n }\n\n if (!response.ok) {\n console.warn(\n `[subgraphNativeUsdtQuoter] subgraph returned ${response.status}`,\n );\n return null;\n }\n\n const json = (await response.json()) as GraphQLResponse;\n if (json.errors && json.errors.length > 0) {\n console.warn(\n \"[subgraphNativeUsdtQuoter] subgraph errors:\",\n json.errors.map((e) => e.message).join(\"; \"),\n );\n return null;\n }\n\n const raw = json.data?.bundle?.ethPriceUSD;\n if (!raw) return null;\n\n const parsed = Number(raw);\n if (!Number.isFinite(parsed) || parsed <= 0) {\n console.warn(\n `[subgraphNativeUsdtQuoter] invalid ethPriceUSD from subgraph: ${raw}`,\n );\n return null;\n }\n const MIN_REASONABLE_ETH_PRICE = 100; // $100\n const MAX_REASONABLE_ETH_PRICE = 100_000; // $100,000\n if (parsed < MIN_REASONABLE_ETH_PRICE || parsed > MAX_REASONABLE_ETH_PRICE) {\n console.warn(\n `[PAFI] SubgraphNativeUsdtQuoter: ETH/USD price ${parsed} is outside reasonable range. Using fallback.`,\n );\n return null;\n }\n return parsed;\n}\n\n/**\n * Convert a float \"USDT per 1 native token\" value into a bigint scaled\n * to USDT decimals. Done via string manipulation to preserve precision\n * beyond JS float (~15-17 significant digits).\n *\n * Example: price=3245.6789, usdtDecimals=6 → 3_245_678_900n\n */\nfunction toUsdtPerNative(priceFloat: number, usdtDecimals: number): bigint {\n // Normalize to fixed notation and strip exponent. `.toFixed` rounds to\n // the requested number of fractional digits and returns a plain string.\n const fixed = priceFloat.toFixed(usdtDecimals);\n const [whole, fraction = \"\"] = fixed.split(\".\");\n const padded = (fraction + \"0\".repeat(usdtDecimals)).slice(0, usdtDecimals);\n return BigInt(whole + padded);\n}\n","import { parseAbi } from \"viem\";\nimport type { Address, PublicClient } from \"viem\";\nimport { PAFI_SUBGRAPH_URL } from \"./subgraphPoolsProvider\";\n\nconst CHAINLINK_ABI = parseAbi([\n \"function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)\",\n]);\n\n/**\n * Max age for a Chainlink price before it's considered stale.\n * Base ETH/USD feed heartbeat is 1 hour (updates every hour or on 0.5% deviation).\n */\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\nexport interface NativePtQuoterConfig {\n /** Viem PublicClient — used to call Chainlink on-chain. */\n provider: PublicClient;\n /** Address of the PointToken being traded. */\n pointTokenAddress: Address;\n /** Chainlink ETH/USD feed address. Defaults to Base mainnet feed. */\n chainlinkFeedAddress?: Address;\n /** PAFI subgraph GraphQL endpoint. */\n subgraphUrl?: string;\n /** Cache TTL in ms. Default: 30_000. */\n cacheTtlMs?: number;\n /** Fallback ETH price (USD) when Chainlink is unreachable. Default: 3000. */\n fallbackEthPriceUsd?: number;\n /** Fallback PT price (USDT per 1 PT) when subgraph is unreachable. Default: 0.1. */\n fallbackPtPriceUsdt?: number;\n fetchImpl?: typeof fetch;\n now?: () => number;\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 * Create a native→PT quoter for use as `FeeManager.quoteNativeToFee`.\n *\n * Converts ETH gas cost → USDT (via Chainlink ETH/USD) → PT (via subgraph\n * pool price), returning the fee amount in PT raw units (18 decimals).\n *\n * Formula:\n * feeInPT = amountNative × ethPrice_8dec × ptPerUsdt_18dec / 10^26\n *\n * Both prices are cached in-process (default 30s TTL).\n *\n * @example\n * ```ts\n * fee: {\n * quoteNativeToFee: createNativePtQuoter({\n * provider,\n * pointTokenAddress: \"0x...\",\n * chainlinkFeedAddress: getContractAddresses(chainId).chainlinkEthUsd,\n * }),\n * }\n * ```\n */\nexport function createNativePtQuoter(\n config: NativePtQuoterConfig,\n): (amountNative: bigint) => Promise<bigint> {\n const {\n provider,\n pointTokenAddress,\n chainlinkFeedAddress = \"0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70\" as Address,\n subgraphUrl = PAFI_SUBGRAPH_URL,\n cacheTtlMs = 30_000,\n fallbackEthPriceUsd = 3_000,\n fallbackPtPriceUsdt = 0.1,\n fetchImpl = globalThis.fetch,\n now = () => Date.now(),\n } = config;\n\n let ethPriceCache: { value: bigint; expiresAt: number } | undefined;\n let ptPriceCache: { value: bigint; expiresAt: number } | undefined;\n\n async function getEthPrice8dec(): Promise<bigint> {\n const ts = now();\n if (ethPriceCache && ethPriceCache.expiresAt > ts) return ethPriceCache.value;\n\n try {\n const result = await provider.readContract({\n address: chainlinkFeedAddress,\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(ts / 1000)) - updatedAt;\n if (ageS > CHAINLINK_MAX_AGE_S) {\n throw new Error(`Chainlink: price stale by ${ageS}s`);\n }\n\n ethPriceCache = { value: answer, expiresAt: ts + cacheTtlMs };\n return answer;\n } catch (err) {\n console.warn(\"[nativePtQuoter] Chainlink unavailable, using fallback:\", (err as Error).message);\n return BigInt(Math.round(fallbackEthPriceUsd * 1e8));\n }\n }\n\n async function getPtPerUsdt18dec(): Promise<bigint> {\n const ts = now();\n if (ptPriceCache && ptPriceCache.expiresAt > ts) return ptPriceCache.value;\n\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) throw new Error(json.errors.map((e) => e.message).join(\"; \"));\n\n const pool = json.data?.pafiToken?.pool;\n if (!pool) throw new Error(\"pafiToken or pool not found in subgraph\");\n\n // PAFI V4 subgraph returns prices WITHOUT decimal adjustment.\n // token0Price = token1_raw / token0_raw (raw amounts ratio, not human-readable).\n //\n // Regardless of which token is PT/USDT, the field we pick always equals\n // USDT_raw / PT_raw (a small number, e.g. 1.082e-12 when 1 PT = 1.082 USDT):\n // PT=token0 → token0Price = USDT_raw / PT_raw\n // PT=token1 → token1Price = USDT_raw / PT_raw\n //\n // To convert to ptPerUsdt_18dec (\"raw PT units per 1 human USDT\"):\n // ptPerUsdt_18dec = 10^6 / (USDT_raw/PT_raw)\n // = 10^24 / parseBigDecimalTo18(priceStr)\n // where 10^6 = USDT decimals\n const isPtToken0 =\n pool.token0.id.toLowerCase() === pointTokenAddress.toLowerCase();\n const ptPerUsdtStr = isPtToken0 ? pool.token0Price : pool.token1Price;\n\n if (!ptPerUsdtStr || Number(ptPerUsdtStr) <= 0) {\n throw new Error(`invalid PT/USDT price from subgraph: ${ptPerUsdtStr}`);\n }\n\n const raw = parseBigDecimalTo18(ptPerUsdtStr);\n if (raw === 0n) throw new Error(`pool price parsed to zero: ${ptPerUsdtStr}`);\n const value = 10n ** 24n / raw;\n ptPriceCache = { value, expiresAt: ts + cacheTtlMs };\n return value;\n } catch (err) {\n console.warn(\"[nativePtQuoter] subgraph unavailable, using fallback:\", (err as Error).message);\n // fallbackPtPriceUsdt is USDT per 1 PT (e.g. 0.1 → 1 PT costs 0.1 USDT → 10 PT per USDT)\n const ptPerUsdtHuman = 1 / fallbackPtPriceUsdt;\n return parseBigDecimalTo18(ptPerUsdtHuman.toFixed(18));\n }\n }\n\n return async (amountNative: bigint): Promise<bigint> => {\n if (amountNative === 0n) return 0n;\n\n const [ethPrice8dec, ptPerUsdt18dec] = await Promise.all([\n getEthPrice8dec(),\n getPtPerUsdt18dec(),\n ]);\n\n // feeInPT_18dec = amountNative_wei × ethPrice_8dec × ptPerUsdt_18dec / 10^26\n //\n // Derivation:\n // feeInUSDT_6dec = amountNative × ethPrice_8dec / 10^(18+8-6) = / 10^20\n // feeInPT_18dec = feeInUSDT_6dec × ptPerUsdt_18dec / 10^(18-6) = / 10^12\n // combined = amountNative × ethPrice_8dec × ptPerUsdt_18dec / 10^32... wait\n //\n // Correct combined formula (verified with concrete numbers):\n // feeInPT_18dec = amountNative × ethPrice_8dec × ptPerUsdt_18dec / 10^(20+12) = / 10^32\n // But we scaled ptPerUsdt to 18 decimals, so:\n // feeInPT_18dec = amountNative × ethPrice_8dec × ptPerUsdt_18dec / 10^(8 + 18 + 18 - 18)\n // = / 10^26\n return (amountNative * ethPrice8dec * ptPerUsdt18dec) / 10n ** 26n;\n };\n}\n\n/**\n * Parse a BigDecimal string (e.g. \"10.523\") into a bigint scaled to\n * `decimals` decimal places (e.g. 18 → 10_523_000_000_000_000_000n).\n * Truncates extra digits silently.\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 type { Address, PublicClient } from \"viem\";\nimport { getPointTokenBalance } from \"@pafi-dev/core\";\nimport type { IPointLedger } from \"../ledger/types\";\n\n/**\n * Combined off-chain + on-chain balance for a single user / token pair.\n *\n * - `offChain` — the issuer's ledger balance (available, excluding locks)\n * - `onChain` — the user's ERC-20 balance from `PointToken.balanceOf`\n * - `total` — `offChain + onChain` (what the Issuer App displays)\n */\nexport interface CombinedBalance {\n offChain: bigint;\n onChain: bigint;\n total: bigint;\n}\n\nexport interface BalanceAggregatorConfig {\n provider: PublicClient;\n ledger: IPointLedger;\n}\n\n/**\n * v1.4 utility — aggregates off-chain + on-chain point balances into a\n * single view for the \"combined balance\" UI in the Issuer App.\n *\n * The `/user` API handler uses this internally; the helper is exposed\n * publicly so Issuer Apps can call it directly without going through\n * the HTTP layer (e.g., for server-rendered pages or admin dashboards).\n *\n * See [REQUIREMENTS_V2.md] §1 — \"The Issuer App displays a combined\n * balance (off-chain points + on-chain PT) and does not surface USDT.\"\n */\nexport class BalanceAggregator {\n private readonly provider: PublicClient;\n private readonly ledger: IPointLedger;\n\n constructor(config: BalanceAggregatorConfig) {\n if (!config.provider) {\n throw new Error(\"BalanceAggregator: provider is required\");\n }\n if (!config.ledger) {\n throw new Error(\"BalanceAggregator: ledger is required\");\n }\n this.provider = config.provider;\n this.ledger = config.ledger;\n }\n\n /**\n * Combined balance for a single (user, token) pair. Fetches off-chain\n * + on-chain in parallel.\n */\n async getCombinedBalance(\n user: Address,\n pointToken: Address,\n ): Promise<CombinedBalance> {\n const [offChain, onChain] = await Promise.all([\n this.ledger.getBalance(user, pointToken),\n getPointTokenBalance(this.provider, pointToken, user),\n ]);\n return {\n offChain,\n onChain,\n total: offChain + onChain,\n };\n }\n\n /**\n * Combined balance for multiple tokens owned by the same user. Runs\n * all lookups in parallel. Returns a Map keyed by the token address\n * (same casing as supplied — caller should normalize if needed).\n */\n async getCombinedBalanceMulti(\n user: Address,\n pointTokens: Address[],\n ): Promise<Map<Address, CombinedBalance>> {\n const entries = await Promise.all(\n pointTokens.map(async (token) => {\n const balance = await this.getCombinedBalance(user, token);\n return [token, balance] as const;\n }),\n );\n return new Map(entries);\n }\n}\n","import type { Address, Hex } from \"viem\";\nimport { getPafiServiceUrls } from \"@pafi-dev/core\";\nimport { PafiBackendError, type PafiBackendConfig } from \"./types\";\n\nexport interface RelayUserOpRequest {\n userOp: Record<string, string | null>;\n entryPoint: string;\n eip7702Auth?: {\n chainId: string;\n address: string;\n nonce: string;\n r: string;\n s: string;\n yParity: string;\n };\n}\n\nexport interface RelayUserOpResponse {\n userOpHash: Hex;\n}\n\nexport interface SponsorshipUserOp {\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\nexport interface SponsorshipTarget {\n contract: Address;\n function: string;\n pointToken: Address;\n}\n\nexport interface SponsorshipRequest {\n chainId: number;\n scenario: string;\n userOp: SponsorshipUserOp;\n target: SponsorshipTarget;\n /**\n * EIP-7702 authorization tuple — REQUIRED for the `delegate`\n * scenario so Pimlico's `pm_sponsorUserOperation` can simulate the\n * UserOp with the EOA already delegated. Without it, simulator\n * reverts `AA20 account not deployed` (chicken-and-egg: the same\n * UserOp anchors the delegation).\n *\n * Optional for non-delegate scenarios where the sender already\n * has bytecode (i.e. delegated previously). v0.7.5 added.\n */\n eip7702Auth?: {\n chainId: string;\n address: string;\n nonce: string;\n r: string;\n s: string;\n yParity: string;\n };\n}\n\nexport interface SponsorshipResponse {\n paymaster: Address;\n paymasterData: Hex;\n paymasterVerificationGasLimit: bigint;\n paymasterPostOpGasLimit: bigint;\n /**\n * Pimlico's `pm_sponsorUserOperation` re-estimates these gas fields\n * and signs its paymaster signature over the new values. Callers\n * MUST overwrite the matching userOp fields with these BEFORE\n * computing the EIP-712 userOpHash and submitting to the bundler.\n * Otherwise both `AA34` (paymaster sig) and `AA24` (sender sig) will\n * fire — the EntryPoint hashes the actual on-chain field values, and\n * a mismatch invalidates every sig over the hash.\n */\n callGasLimit?: bigint;\n verificationGasLimit?: bigint;\n preVerificationGas?: bigint;\n /**\n * Bundler-required gas price (Pimlico's `pimlico_getUserOperationGasPrice`\n * fast tier). Pimlico's paymaster signs over these — caller MUST apply\n * to the userOp before computing the EIP-712 userOpHash. Base RPC's\n * `eth_feeHistory` underestimates the bundler floor by 10-15 %, so\n * relying on it produces \"maxFeePerGas must be at least ...\" rejections.\n */\n maxFeePerGas?: bigint;\n maxPriorityFeePerGas?: bigint;\n expiresAt: number;\n}\n\nfunction serializeBigInt(_key: string, value: unknown): unknown {\n return typeof value === \"bigint\" ? value.toString(10) : value;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport class PafiBackendClient {\n private readonly config: PafiBackendConfig;\n private readonly baseUrl: string;\n\n constructor(config: PafiBackendConfig) {\n if (!config.chainId) throw new Error(\"PafiBackendClient: chainId is required\");\n if (!config.issuerId) throw new Error(\"PafiBackendClient: issuerId is required\");\n if (!config.apiKey) throw new Error(\"PafiBackendClient: apiKey is required\");\n this.config = config;\n this.baseUrl = getPafiServiceUrls(config.chainId).sponsorRelayer;\n }\n\n async requestSponsorship(\n request: SponsorshipRequest,\n ): Promise<SponsorshipResponse> {\n const maxAttempts = this.config.retry?.maxAttempts ?? 1;\n const initialDelayMs = this.config.retry?.initialDelayMs ?? 100;\n const maxDelayMs = this.config.retry?.maxDelayMs ?? 10_000;\n const maxRetryAfterMs = this.config.retry?.maxRetryAfterMs;\n\n let lastError: PafiBackendError | undefined;\n let delay = initialDelayMs;\n\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n return await this._doRequest(request);\n } catch (err) {\n if (!(err instanceof PafiBackendError)) throw err;\n lastError = err;\n\n if (attempt >= maxAttempts) break;\n if (!err.safeToRetry) break;\n\n const retryAfterMs =\n err.retryAfter !== undefined ? err.retryAfter * 1000 : undefined;\n if (\n maxRetryAfterMs !== undefined &&\n retryAfterMs !== undefined &&\n retryAfterMs > maxRetryAfterMs\n ) {\n break;\n }\n\n await sleep(retryAfterMs ?? delay);\n delay = Math.min(delay * 2, maxDelayMs);\n }\n }\n\n throw lastError;\n }\n\n /**\n * Fetch ERC-4337 UserOp receipt via PAFI's authenticated bundler proxy.\n * Returns `null` when the bundler hasn't seen the userOp yet — caller\n * should keep polling. Used by status endpoints to short-circuit the\n * on-chain indexer when several PENDING locks share the same amount.\n */\n async getUserOpReceipt(\n userOpHash: Hex,\n ): Promise<{ success: boolean; txHash: Hex; blockNumber: string } | null> {\n const fetchFn = this.config.fetchImpl ?? fetch;\n const url = `${this.baseUrl}/bundler/receipt`;\n\n let response: Response;\n try {\n response = await fetchFn(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.config.apiKey}`,\n \"X-Issuer-Id\": this.config.issuerId,\n },\n body: JSON.stringify({ userOpHash }),\n });\n } catch (err: unknown) {\n throw new PafiBackendError(\n \"NETWORK_ERROR\",\n `Network error: ${err instanceof Error ? err.message : String(err)}`,\n 0,\n );\n }\n\n const text = await response.text();\n let json: Record<string, unknown> = {};\n try {\n json = JSON.parse(text);\n } catch {\n // non-JSON\n }\n\n if (!response.ok) {\n const code = (json.code as string) ?? \"INTERNAL_ERROR\";\n const message = (json.message as string) ?? `HTTP ${response.status}`;\n throw new PafiBackendError(code, message, response.status, json);\n }\n\n if (json.pending) return null;\n return {\n success: json.success as boolean,\n txHash: json.txHash as Hex,\n blockNumber: json.blockNumber as string,\n };\n }\n\n async relayUserOperation(\n request: RelayUserOpRequest,\n ): Promise<RelayUserOpResponse> {\n const fetchFn = this.config.fetchImpl ?? fetch;\n const url = `${this.baseUrl}/bundler/relay`;\n\n let response: Response;\n try {\n response = await fetchFn(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.config.apiKey}`,\n \"X-Issuer-Id\": this.config.issuerId,\n },\n body: JSON.stringify(request),\n });\n } catch (err: unknown) {\n throw new PafiBackendError(\n \"NETWORK_ERROR\",\n `Network error: ${err instanceof Error ? err.message : String(err)}`,\n 0,\n );\n }\n\n const text = await response.text();\n let json: Record<string, unknown> = {};\n try {\n json = JSON.parse(text);\n } catch {\n // non-JSON body\n }\n\n if (!response.ok) {\n const code = (json.code as string) ?? \"INTERNAL_ERROR\";\n const message = (json.message as string) ?? `HTTP ${response.status}`;\n throw new PafiBackendError(code, message, response.status, json);\n }\n\n return { userOpHash: json.userOpHash as Hex };\n }\n\n private async _doRequest(\n request: SponsorshipRequest,\n ): Promise<SponsorshipResponse> {\n const fetchFn = this.config.fetchImpl ?? fetch;\n const url = `${this.baseUrl}/paymaster/sponsor`;\n const body = JSON.stringify(request, serializeBigInt);\n\n let response: Response;\n try {\n response = await fetchFn(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.config.apiKey}`,\n \"X-Issuer-Id\": this.config.issuerId,\n },\n body,\n });\n } catch (err: unknown) {\n throw new PafiBackendError(\n \"NETWORK_ERROR\",\n `Network error: ${err instanceof Error ? err.message : String(err)}`,\n 0,\n );\n }\n\n const text = await response.text();\n let json: Record<string, unknown> = {};\n try {\n json = JSON.parse(text);\n } catch {\n // non-JSON body — treat as internal error\n }\n\n if (!response.ok) {\n const code = (json.code as string) ?? \"INTERNAL_ERROR\";\n const message = (json.message as string) ?? `HTTP ${response.status}`;\n const retryAfter =\n typeof json.retryAfter === \"number\" ? json.retryAfter : undefined;\n const safeToRetry =\n typeof json.safeToRetry === \"boolean\" ? json.safeToRetry : undefined;\n throw new PafiBackendError(code, message, response.status, json, {\n retryAfter,\n safeToRetry,\n });\n }\n\n return {\n paymaster: json.paymaster as Address,\n paymasterData: json.paymasterData as Hex,\n paymasterVerificationGasLimit: BigInt(\n json.paymasterVerificationGasLimit as string,\n ),\n paymasterPostOpGasLimit: BigInt(json.paymasterPostOpGasLimit as string),\n callGasLimit:\n json.callGasLimit != null\n ? BigInt(json.callGasLimit as string)\n : undefined,\n verificationGasLimit:\n json.verificationGasLimit != null\n ? BigInt(json.verificationGasLimit as string)\n : undefined,\n preVerificationGas:\n json.preVerificationGas != null\n ? BigInt(json.preVerificationGas as string)\n : undefined,\n maxFeePerGas:\n json.maxFeePerGas != null\n ? BigInt(json.maxFeePerGas as string)\n : undefined,\n maxPriorityFeePerGas:\n json.maxPriorityFeePerGas != null\n ? BigInt(json.maxPriorityFeePerGas as string)\n : undefined,\n expiresAt: json.expiresAt as number,\n };\n }\n}\n","import { getAddress } from \"viem\";\nimport type { Address, PublicClient, WalletClient } from \"viem\";\nimport { getContractAddresses } from \"@pafi-dev/core\";\nimport type { IPointLedger } from \"./ledger/types\";\nimport { DefaultPolicyEngine } from \"./policy/defaultPolicy\";\nimport type { IPolicyEngine } from \"./policy/types\";\nimport { MemorySessionStore } from \"./auth/memorySessionStore\";\nimport type { ISessionStore } from \"./auth/types\";\nimport { AuthService } from \"./auth/loginVerifier\";\nimport { RelayService } from \"./relay/relayService\";\nimport { FeeManager, type FeeManagerConfig } from \"./relay/feeManager\";\nimport { PointIndexer } from \"./indexer/pointIndexer\";\nimport type { IIndexerCursorStore } from \"./indexer/types\";\nimport { IssuerApiHandlers } from \"./api/handlers\";\nimport type {\n ApiConfigResponse,\n PoolsProvider,\n} from \"./api/types\";\nimport { RedemptionService } from \"./redemption/service\";\nimport { PolicyProvider } from \"./redemption/policyProvider\";\nimport type {\n IRedemptionHistoryStore,\n PolicyProviderConfig,\n} from \"./redemption/types\";\n\n// ---------------------------------------------------------------------------\n// Config shape\n// ---------------------------------------------------------------------------\n\n/**\n * Top-level configuration for `createIssuerService`.\n *\n * In v1.4 the SDK is HTTP-client-free: it signs EIP-712 messages, reads\n * on-chain state, builds unsigned UserOperations, and maintains the\n * off-chain ledger. It never broadcasts transactions — that's the\n * frontend's responsibility via Bundler + Paymaster.\n *\n * **Multi-token (0.2.0+):** Pass `pointTokenAddresses: Address[]` to\n * support multiple PointTokens under a single issuer backend. Legacy\n * `pointTokenAddress: Address` still works for single-token deployments.\n * When both are provided, `pointTokenAddresses` takes precedence.\n */\nexport interface IssuerServiceConfig {\n // -- Chain + contracts --------------------------------------------------\n\n chainId: number;\n /** Legacy single-token shortcut; prefer `pointTokenAddresses`. */\n pointTokenAddress?: Address;\n /** All PointToken addresses this issuer supports. */\n pointTokenAddresses?: Address[];\n /**\n * Issuer-specific contract addresses merged into the `/config` response.\n * PAFI-owned addresses (batchExecutor, usdt, issuerRegistry, mintingOracle,\n * pafiHook) are auto-resolved from `getContractAddresses(chainId)` and\n * can be omitted. Only `pointToken` / `pointTokens` must be provided.\n */\n contracts?: Pick<\n ApiConfigResponse[\"contracts\"],\n \"pointToken\" | \"pointTokens\" | \"relay\"\n >;\n\n // -- Infra --------------------------------------------------------------\n\n /**\n * Shared `PublicClient` used for on-chain reads, simulation, indexer\n * polling, and gas-price lookups. Must be pointed at the target chain.\n */\n provider: PublicClient;\n\n // -- Auth ---------------------------------------------------------------\n\n auth: {\n jwtSecret: string;\n /** SIWE-style login-message domain, e.g. `\"app.example.com\"`. */\n domain: string;\n /** Passed straight to `jose` (`\"24h\"`, `\"7d\"`, …). Default `\"24h\"`. */\n jwtExpiresIn?: string;\n };\n\n // -- Storage ------------------------------------------------------------\n\n /**\n * Off-chain point ledger — the source of truth for user balances and\n * in-flight minting locks. Every issuer provides their own database-backed\n * implementation (Postgres, Redis, etc.) that implements `IPointLedger`.\n * The SDK does not ship a production ledger; each issuer's data model and\n * infrastructure are different.\n */\n ledger: IPointLedger;\n\n /**\n * Policy engine — optional, defaults to `DefaultPolicyEngine` which checks\n * off-chain balance. Extend or replace to add KYC, volume caps, etc.\n */\n policy?: IPolicyEngine;\n\n /** Session store — optional, defaults to `MemorySessionStore` (dev/test only). */\n sessionStore?: ISessionStore;\n\n // -- Optional API collaborators ----------------------------------------\n\n /**\n * Fee management config. If omitted the `handleGasFee` endpoint will\n * throw \"not configured\" at request time.\n */\n fee?: Omit<FeeManagerConfig, \"provider\">;\n\n /**\n * Pool discovery function for `handlePools`. If omitted the endpoint\n * throws \"not configured\" at request time.\n */\n poolsProvider?: PoolsProvider;\n\n // Note: legacy `claim?: {...}` config (used by removed `handleClaim`)\n // dropped in 0.5.43. Wire `PTClaimHandler` directly or via\n // `IssuerApiAdapter` instead.\n\n // -- Indexer config (optional) -----------------------------------------\n\n indexer?: {\n fromBlock?: bigint;\n cursorStore?: IIndexerCursorStore;\n confirmations?: number;\n batchSize?: number;\n pollIntervalMs?: number;\n /**\n * If `true`, the factory calls `indexer.start()` before returning.\n * Default: `false` — the caller decides when to begin polling.\n */\n autoStart?: boolean;\n /**\n * v1.6 — override the MintFeeWrapper address used by the indexer.\n * When omitted, the factory auto-resolves from\n * `getContractAddresses(chainId).mintFeeWrapper`. Pass the\n * dead-zero address (`0x...dEaD`) to force direct-Transfer mode\n * (legacy v1.5, useful for local fork tests).\n */\n mintFeeWrapperAddress?: Address;\n };\n\n /**\n * Redemption restriction config. When provided, the SDK fetches the\n * per-issuer policy from PAFI issuer-api (with 5min cache + default\n * fallback) and exposes preview/evaluate via `service.redemption`.\n * The handler endpoints `handleRedemptionPreview` / `handleRedemptionEvaluate`\n * are wired only when this is configured.\n *\n * `chainId` is taken from the top-level config; the issuer-api URL\n * is looked up via `getPafiServiceUrls(chainId)`. Only credentials\n * + the history store are required here.\n */\n redemption?: {\n issuerId: string;\n apiKey: string;\n historyStore: IRedemptionHistoryStore;\n /** Override fetch (testing). */\n fetchImpl?: typeof fetch;\n /** Per-fetch timeout in ms. Default 1000. */\n fetchTimeoutMs?: number;\n /** Cache TTL in ms. Default 5 * 60 * 1000. */\n cacheTtlMs?: number;\n };\n}\n\n// ---------------------------------------------------------------------------\n// Output shape — everything the factory wires\n// ---------------------------------------------------------------------------\n\nexport interface IssuerService {\n /** AuthService — login, logout, nonce management. */\n auth: AuthService;\n /** Session store — nonce + JWT session persistence. */\n session: ISessionStore;\n ledger: IPointLedger;\n policy: IPolicyEngine;\n /** RelayService — prepareMint / prepareBurn UserOp builders. */\n relay: RelayService;\n /** FeeManager — gas fee estimation. Undefined if not configured. */\n fee: FeeManager | undefined;\n /** All indexers keyed by PointToken address. */\n indexers: Map<Address, PointIndexer>;\n /**\n * First indexer. Kept for backward compat with 0.1.x callers.\n * @deprecated use `indexers.get(tokenAddress)` for multi-token.\n */\n indexer: PointIndexer;\n /** Framework-agnostic HTTP handlers — wire into Express / Fastify / Hono. */\n api: IssuerApiHandlers;\n /**\n * Redemption restriction service. Undefined when `redemption` is not\n * configured — the corresponding handlers throw \"not configured\" at\n * request time.\n */\n redemption: RedemptionService | undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Wire a fully-functional issuer service from a single config object.\n *\n * Defaults:\n * - `sessionStore` → `MemorySessionStore` (dev/test only — replace in prod)\n * - `policy` → `DefaultPolicyEngine({ ledger })`\n * - `feeManager` → undefined (handleGasFee throws until configured)\n * - `poolsProvider` → undefined (handlePools throws until configured)\n * - `indexer.autoStart` → false\n *\n * Throws synchronously if any required field is missing.\n */\nexport function createIssuerService(\n config: IssuerServiceConfig,\n): IssuerService {\n if (!config.provider) {\n throw new Error(\"createIssuerService: provider is required\");\n }\n if (!config.ledger) {\n throw new Error(\"createIssuerService: ledger is required\");\n }\n if (!config.auth?.jwtSecret) {\n throw new Error(\"createIssuerService: auth.jwtSecret is required\");\n }\n if (!config.auth?.domain) {\n throw new Error(\"createIssuerService: auth.domain is required\");\n }\n\n const rawAddresses =\n config.pointTokenAddresses && config.pointTokenAddresses.length > 0\n ? config.pointTokenAddresses\n : config.pointTokenAddress\n ? [config.pointTokenAddress]\n : [];\n if (rawAddresses.length === 0) {\n throw new Error(\n \"createIssuerService: at least one of pointTokenAddress / pointTokenAddresses is required\",\n );\n }\n const tokenAddresses: Address[] = rawAddresses.map((a) => getAddress(a));\n\n const ledger: IPointLedger = config.ledger;\n const sessionStore: ISessionStore =\n config.sessionStore ?? new MemorySessionStore();\n const policy: IPolicyEngine =\n config.policy ?? new DefaultPolicyEngine({ ledger });\n\n const authServiceConfig: ConstructorParameters<typeof AuthService>[0] = {\n sessionStore,\n jwtSecret: config.auth.jwtSecret,\n domain: config.auth.domain,\n chainId: config.chainId,\n };\n if (config.auth.jwtExpiresIn) {\n authServiceConfig.jwtExpiresIn = config.auth.jwtExpiresIn;\n }\n const authService = new AuthService(authServiceConfig);\n\n // RelayService in v1.4 only wraps `prepareMint` / `prepareBurn` —\n // no operator wallet, no broadcasting. Pass `provider + chainId` so\n // the service can auto-quote the operator fee + auto-resolve the\n // PAFI fee recipient when callers don't pass them. Issuer can still\n // override per-call by passing `feeAmount` / `feeRecipient`\n // explicitly.\n const relayService = new RelayService({\n provider: config.provider,\n chainId: config.chainId,\n });\n\n let feeManager: FeeManager | undefined;\n if (config.fee) {\n feeManager = new FeeManager({\n ...config.fee,\n provider: config.provider,\n });\n }\n\n // v1.6 — auto-resolve MintFeeWrapper from chainId so the indexer\n // listens to `MintWithFee` events (where `to` = end user) instead of\n // `Transfer(0x0)` (where `to` = wrapper). Override via\n // `config.indexer.mintFeeWrapperAddress` for local fork tests; pass\n // the dead-zero address there to force direct-Transfer mode.\n const sdkWrapperAddress = getContractAddresses(config.chainId).mintFeeWrapper;\n const wrapperOverride = config.indexer?.mintFeeWrapperAddress;\n const resolvedWrapperAddress: Address | undefined =\n wrapperOverride !== undefined ? wrapperOverride : sdkWrapperAddress;\n\n const indexers = new Map<Address, PointIndexer>();\n for (const tokenAddress of tokenAddresses) {\n const indexerConfig: ConstructorParameters<typeof PointIndexer>[0] = {\n provider: config.provider,\n pointTokenAddress: tokenAddress,\n ledger,\n };\n if (resolvedWrapperAddress !== undefined) {\n indexerConfig.mintFeeWrapperAddress = resolvedWrapperAddress;\n }\n if (config.indexer?.fromBlock !== undefined) {\n indexerConfig.fromBlock = config.indexer.fromBlock;\n }\n if (config.indexer?.cursorStore) {\n indexerConfig.cursorStore = config.indexer.cursorStore;\n }\n if (config.indexer?.confirmations !== undefined) {\n indexerConfig.confirmations = config.indexer.confirmations;\n }\n if (config.indexer?.batchSize !== undefined) {\n indexerConfig.batchSize = config.indexer.batchSize;\n }\n if (config.indexer?.pollIntervalMs !== undefined) {\n indexerConfig.pollIntervalMs = config.indexer.pollIntervalMs;\n }\n indexers.set(tokenAddress, new PointIndexer(indexerConfig));\n }\n const firstIndexer = indexers.get(tokenAddresses[0]!)!;\n\n const chainAddresses = getContractAddresses(config.chainId);\n const resolvedContracts: ApiConfigResponse[\"contracts\"] = {\n batchExecutor: chainAddresses.batchExecutor,\n usdt: chainAddresses.usdt,\n issuerRegistry: chainAddresses.issuerRegistry,\n mintingOracle: chainAddresses.mintingOracle,\n pafiHook: chainAddresses.pafiHook,\n ...config.contracts,\n };\n if (resolvedWrapperAddress !== undefined) {\n resolvedContracts.mintFeeWrapper = resolvedWrapperAddress;\n }\n\n let redemption: RedemptionService | undefined;\n if (config.redemption) {\n const policyConfig: PolicyProviderConfig = {\n chainId: config.chainId,\n issuerId: config.redemption.issuerId,\n apiKey: config.redemption.apiKey,\n };\n if (config.redemption.fetchImpl) policyConfig.fetchImpl = config.redemption.fetchImpl;\n if (config.redemption.fetchTimeoutMs !== undefined) {\n policyConfig.fetchTimeoutMs = config.redemption.fetchTimeoutMs;\n }\n if (config.redemption.cacheTtlMs !== undefined) {\n policyConfig.cacheTtlMs = config.redemption.cacheTtlMs;\n }\n redemption = new RedemptionService({\n policyProvider: new PolicyProvider(policyConfig),\n historyStore: config.redemption.historyStore,\n });\n }\n\n const handlersConfig: ConstructorParameters<typeof IssuerApiHandlers>[0] = {\n authService,\n ledger,\n provider: config.provider,\n pointTokenAddresses: tokenAddresses,\n chainId: config.chainId,\n contracts: resolvedContracts,\n };\n if (feeManager) handlersConfig.feeManager = feeManager;\n if (config.poolsProvider) handlersConfig.poolsProvider = config.poolsProvider;\n if (redemption) handlersConfig.redemption = redemption;\n if (resolvedWrapperAddress !== undefined) {\n handlersConfig.mintFeeWrapperAddress = resolvedWrapperAddress;\n }\n const handlers = new IssuerApiHandlers(handlersConfig);\n\n if (config.indexer?.autoStart) {\n for (const idx of indexers.values()) {\n idx.start();\n }\n }\n\n return {\n auth: authService,\n session: sessionStore,\n ledger,\n policy,\n relay: relayService,\n fee: feeManager,\n indexers,\n indexer: firstIndexer,\n api: handlers,\n redemption,\n };\n}\n","import type {\n BlackoutWindow,\n RedemptionDecision,\n RedemptionDenial,\n RedemptionPolicy,\n RedemptionPolicySource,\n RedemptionPreview,\n} from \"@pafi-dev/core\";\n\nconst SECONDS_PER_DAY = 24 * 60 * 60;\n\nexport interface UserHistory {\n /** Total PT redeemed by user in the rolling 24h window. */\n redeemedLast24hPt: bigint;\n /** Last redemption timestamp (unix seconds), or null if never. */\n lastRedeemedAtUnixSec: number | null;\n}\n\nexport interface EvaluateInput {\n policy: RedemptionPolicy;\n policySource: RedemptionPolicySource;\n history: UserHistory;\n /** Amount being requested. Pass 0n for a pure preview. */\n amountPt: bigint;\n /** Current unix time in seconds (caller-controlled for testability). */\n nowUnixSec: number;\n}\n\n/**\n * Pure evaluator. Given a policy + user history snapshot + requested\n * amount, returns either ALLOW + a preview of the user's remaining\n * headroom, or DENY + the first failing rule.\n *\n * Preview is always populated, even on denial — UI uses it to render\n * \"X PT redeemable now\" / \"next available at HH:MM\" regardless.\n */\nexport function evaluateRedemption(input: EvaluateInput): RedemptionDecision {\n const { policy, history, amountPt, nowUnixSec, policySource } = input;\n\n const dailyRemaining =\n policy.dailyLimitPt > history.redeemedLast24hPt\n ? policy.dailyLimitPt - history.redeemedLast24hPt\n : 0n;\n\n const cooldownUntilUnixSec =\n history.lastRedeemedAtUnixSec !== null\n ? history.lastRedeemedAtUnixSec + policy.cooldownSec\n : null;\n const inCooldown =\n cooldownUntilUnixSec !== null && cooldownUntilUnixSec > nowUnixSec;\n\n const activeBlackout = findActiveBlackout(policy.blackoutWindows, nowUnixSec);\n const nextBlackoutEnd = nextBlackoutEndAfter(\n policy.blackoutWindows,\n nowUnixSec,\n );\n\n // availableAmountPt: largest single redemption that would pass right now\n // ignoring perTxMin (amount=0 case is \"preview\" not \"request\").\n let availableAmountPt: bigint = 0n;\n if (!inCooldown && !activeBlackout) {\n const headroom = dailyRemaining < policy.perTxMaxPt\n ? dailyRemaining\n : policy.perTxMaxPt;\n availableAmountPt = headroom >= policy.perTxMinPt ? headroom : 0n;\n }\n\n const preview: RedemptionPreview = {\n availableAmountPt,\n dailyRemainingPt: dailyRemaining,\n cooldownUntilUnixSec: inCooldown ? cooldownUntilUnixSec : null,\n nextBlackoutEndsAtUnixSec: activeBlackout\n ? activeBlackout.endUnixSec\n : nextBlackoutEnd,\n perTxMinPt: policy.perTxMinPt,\n perTxMaxPt: policy.perTxMaxPt,\n policyVersion: policy.version,\n policySource,\n };\n\n if (amountPt <= 0n) {\n return { allowed: false, preview, denial: rejectAmountBelowMin(policy) };\n }\n\n const denial = firstDenial({\n amountPt,\n policy,\n dailyRemaining,\n inCooldown,\n cooldownUntilUnixSec,\n activeBlackout,\n });\n if (denial) return { allowed: false, denial, preview };\n\n return { allowed: true, preview };\n}\n\nfunction firstDenial(args: {\n amountPt: bigint;\n policy: RedemptionPolicy;\n dailyRemaining: bigint;\n inCooldown: boolean;\n cooldownUntilUnixSec: number | null;\n activeBlackout: BlackoutWindow | null;\n}): RedemptionDenial | null {\n const { amountPt, policy, dailyRemaining, inCooldown, cooldownUntilUnixSec, activeBlackout } = args;\n\n if (activeBlackout) {\n return {\n code: \"BLACKOUT_WINDOW\",\n message: `Redemption is blocked until ${new Date(\n activeBlackout.endUnixSec * 1000,\n ).toISOString()}${activeBlackout.reason ? ` (${activeBlackout.reason})` : \"\"}`,\n };\n }\n if (inCooldown && cooldownUntilUnixSec !== null) {\n return {\n code: \"COOLDOWN_ACTIVE\",\n message: `Cooldown active until ${new Date(\n cooldownUntilUnixSec * 1000,\n ).toISOString()}`,\n };\n }\n if (amountPt < policy.perTxMinPt) {\n return {\n code: \"AMOUNT_BELOW_MIN\",\n message: `amount ${amountPt} below per-tx minimum ${policy.perTxMinPt}`,\n };\n }\n if (amountPt > policy.perTxMaxPt) {\n return {\n code: \"AMOUNT_ABOVE_MAX\",\n message: `amount ${amountPt} above per-tx maximum ${policy.perTxMaxPt}`,\n };\n }\n if (amountPt > dailyRemaining) {\n return {\n code: \"DAILY_LIMIT_EXCEEDED\",\n message: `amount ${amountPt} exceeds daily remaining ${dailyRemaining}`,\n };\n }\n return null;\n}\n\nfunction rejectAmountBelowMin(policy: RedemptionPolicy): RedemptionDenial {\n return {\n code: \"AMOUNT_BELOW_MIN\",\n message: `amount must be >= ${policy.perTxMinPt}`,\n };\n}\n\nfunction findActiveBlackout(\n windows: BlackoutWindow[],\n nowUnixSec: number,\n): BlackoutWindow | null {\n for (const w of windows) {\n if (w.startUnixSec <= nowUnixSec && nowUnixSec < w.endUnixSec) return w;\n }\n return null;\n}\n\nfunction nextBlackoutEndAfter(\n windows: BlackoutWindow[],\n nowUnixSec: number,\n): number | null {\n let earliest: number | null = null;\n for (const w of windows) {\n if (w.endUnixSec > nowUnixSec) {\n if (earliest === null || w.endUnixSec < earliest) earliest = w.endUnixSec;\n }\n }\n return earliest;\n}\n\nexport const REDEMPTION_HISTORY_WINDOW_SEC = SECONDS_PER_DAY;\n","import {\n getPafiServiceUrls,\n type BlackoutWindow,\n type RedemptionPolicy,\n} from \"@pafi-dev/core\";\nimport type { SettlementClientConfig } from \"./types\";\n\nconst DEFAULT_TIMEOUT_MS = 1_000;\n\n/**\n * Either a successful policy fetch or a structured failure. We never\n * throw from `fetchPolicy()` — callers fall back to cache/default on\n * any failure mode, so a thrown error would just force every caller\n * to wrap in try/catch.\n */\nexport type FetchResult =\n | { ok: true; policy: RedemptionPolicy }\n | { ok: false; reason: FetchFailureReason; status?: number };\n\nexport type FetchFailureReason =\n | \"TIMEOUT\"\n | \"NETWORK\"\n | \"NOT_FOUND\"\n | \"UNAUTHORIZED\"\n | \"SERVER_ERROR\"\n | \"INVALID_RESPONSE\";\n\ninterface RawPolicyDto {\n issuerId: string;\n dailyLimitPt: string;\n cooldownSec: number;\n perTxMinPt: string;\n perTxMaxPt: string;\n blackoutWindows: BlackoutWindow[];\n version: string;\n}\n\nexport class SettlementClient {\n private readonly config: {\n baseUrl: string;\n issuerId: string;\n apiKey: string;\n fetchTimeoutMs: number;\n fetchImpl?: typeof fetch;\n };\n\n constructor(config: SettlementClientConfig) {\n if (!config.chainId) throw new Error(\"SettlementClient: chainId is required\");\n if (!config.issuerId) throw new Error(\"SettlementClient: issuerId is required\");\n if (!config.apiKey) throw new Error(\"SettlementClient: apiKey is required\");\n this.config = {\n baseUrl: getPafiServiceUrls(config.chainId).issuerApi.replace(/\\/+$/, \"\"),\n issuerId: config.issuerId,\n apiKey: config.apiKey,\n fetchTimeoutMs: config.fetchTimeoutMs ?? DEFAULT_TIMEOUT_MS,\n fetchImpl: config.fetchImpl,\n };\n }\n\n async fetchPolicy(): Promise<FetchResult> {\n const fetchFn = this.config.fetchImpl ?? fetch;\n const url = `${this.config.baseUrl}/issuers/${encodeURIComponent(this.config.issuerId)}/redemption-policy`;\n\n const controller = new AbortController();\n const timer = setTimeout(\n () => controller.abort(),\n this.config.fetchTimeoutMs,\n );\n\n let response: Response;\n try {\n response = await fetchFn(url, {\n method: \"GET\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.config.apiKey}`,\n \"X-Issuer-Id\": this.config.issuerId,\n },\n signal: controller.signal,\n });\n } catch (err: unknown) {\n const isAbort =\n err instanceof Error &&\n (err.name === \"AbortError\" ||\n /aborted|timeout/i.test(err.message ?? \"\"));\n return { ok: false, reason: isAbort ? \"TIMEOUT\" : \"NETWORK\" };\n } finally {\n clearTimeout(timer);\n }\n\n if (response.status === 404) {\n return { ok: false, reason: \"NOT_FOUND\", status: 404 };\n }\n if (response.status === 401 || response.status === 403) {\n return { ok: false, reason: \"UNAUTHORIZED\", status: response.status };\n }\n if (!response.ok) {\n return { ok: false, reason: \"SERVER_ERROR\", status: response.status };\n }\n\n let raw: unknown;\n try {\n raw = await response.json();\n } catch {\n return { ok: false, reason: \"INVALID_RESPONSE\", status: response.status };\n }\n\n const parsed = parsePolicyDto(raw);\n if (!parsed) {\n return { ok: false, reason: \"INVALID_RESPONSE\", status: response.status };\n }\n return { ok: true, policy: parsed };\n }\n}\n\nfunction parsePolicyDto(raw: unknown): RedemptionPolicy | null {\n if (!raw || typeof raw !== \"object\") return null;\n const dto = raw as Partial<RawPolicyDto>;\n if (\n typeof dto.issuerId !== \"string\" ||\n typeof dto.dailyLimitPt !== \"string\" ||\n typeof dto.cooldownSec !== \"number\" ||\n typeof dto.perTxMinPt !== \"string\" ||\n typeof dto.perTxMaxPt !== \"string\" ||\n typeof dto.version !== \"string\" ||\n !Array.isArray(dto.blackoutWindows)\n ) {\n return null;\n }\n try {\n return {\n issuerId: dto.issuerId,\n dailyLimitPt: BigInt(dto.dailyLimitPt),\n cooldownSec: dto.cooldownSec,\n perTxMinPt: BigInt(dto.perTxMinPt),\n perTxMaxPt: BigInt(dto.perTxMaxPt),\n blackoutWindows: dto.blackoutWindows\n .map(normalizeBlackout)\n .filter((b): b is BlackoutWindow => b !== null),\n version: dto.version,\n };\n } catch {\n return null;\n }\n}\n\nfunction normalizeBlackout(raw: unknown): BlackoutWindow | null {\n if (!raw || typeof raw !== \"object\") return null;\n const win = raw as Partial<BlackoutWindow>;\n if (\n typeof win.startUnixSec !== \"number\" ||\n typeof win.endUnixSec !== \"number\" ||\n win.startUnixSec >= win.endUnixSec\n ) {\n return null;\n }\n return {\n startUnixSec: win.startUnixSec,\n endUnixSec: win.endUnixSec,\n reason: typeof win.reason === \"string\" ? win.reason : undefined,\n };\n}\n","import type { RedemptionPolicy } from \"@pafi-dev/core\";\n\nconst PT_DECIMALS = 10n ** 18n;\n\n/**\n * SDK-side fallback used when settlement-api is unreachable, returns\n * 404, or returns 5xx. Keep it permissive enough that an outage doesn't\n * lock all users out, but tight enough that it's not an abuse vector.\n */\nexport const DEFAULT_REDEMPTION_POLICY: RedemptionPolicy = {\n issuerId: \"default\",\n dailyLimitPt: 1_000n * PT_DECIMALS,\n cooldownSec: 60,\n perTxMinPt: 1n * PT_DECIMALS,\n perTxMaxPt: 500n * PT_DECIMALS,\n blackoutWindows: [],\n version: \"default-v1\",\n};\n\nexport function defaultPolicyFor(issuerId: string): RedemptionPolicy {\n return { ...DEFAULT_REDEMPTION_POLICY, issuerId };\n}\n","import type { RedemptionPolicy, RedemptionPolicySource } from \"@pafi-dev/core\";\nimport { SettlementClient } from \"./settlementClient\";\nimport { defaultPolicyFor } from \"./defaults\";\nimport type { PolicyProviderConfig } from \"./types\";\n\nconst DEFAULT_CACHE_TTL_MS = 5 * 60 * 1000;\n\ninterface CacheEntry {\n policy: RedemptionPolicy;\n expiresAtMs: number;\n}\n\nexport interface ResolvedPolicy {\n policy: RedemptionPolicy;\n source: RedemptionPolicySource;\n}\n\n/**\n * Wraps SettlementClient with a 5-minute TTL cache and a hardcoded\n * default fallback. Single-flight: concurrent getPolicy() calls during\n * a cache miss share the same in-flight request.\n *\n * Resolution order:\n * 1. fresh cache hit → return cached\n * 2. cache miss → fetch ok → cache + return (source=settlement)\n * 3. cache miss → fetch failed → DEFAULT_REDEMPTION_POLICY (source=default)\n *\n * Stale cache is NEVER returned — once the TTL expires we either fetch\n * fresh data or fall through to default. This keeps behavior predictable:\n * \"default\" means \"we couldn't talk to settlement RIGHT NOW\", not \"we got\n * lucky with a stale entry\".\n */\nexport class PolicyProvider {\n private readonly client: SettlementClient;\n private readonly issuerId: string;\n private readonly cacheTtlMs: number;\n private readonly now: () => number;\n\n private cache: CacheEntry | null = null;\n private inflight: Promise<ResolvedPolicy> | null = null;\n\n constructor(config: PolicyProviderConfig) {\n this.client = new SettlementClient(config);\n this.issuerId = config.issuerId;\n this.cacheTtlMs = config.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;\n this.now = config.now ?? (() => Date.now());\n }\n\n async getPolicy(): Promise<ResolvedPolicy> {\n const fresh = this.readCache();\n if (fresh) return { policy: fresh, source: \"cache\" };\n\n if (this.inflight) return this.inflight;\n\n this.inflight = this.fetchAndStore().finally(() => {\n this.inflight = null;\n });\n return this.inflight;\n }\n\n /** Drop cached policy. Next getPolicy() will refetch. */\n invalidate(): void {\n this.cache = null;\n }\n\n private readCache(): RedemptionPolicy | null {\n if (!this.cache) return null;\n if (this.cache.expiresAtMs <= this.now()) {\n this.cache = null;\n return null;\n }\n return this.cache.policy;\n }\n\n private async fetchAndStore(): Promise<ResolvedPolicy> {\n const result = await this.client.fetchPolicy();\n if (result.ok) {\n this.cache = {\n policy: result.policy,\n expiresAtMs: this.now() + this.cacheTtlMs,\n };\n return { policy: result.policy, source: \"settlement\" };\n }\n return { policy: defaultPolicyFor(this.issuerId), source: \"default\" };\n }\n}\n","import type { Address } from \"viem\";\nimport type { RedemptionDecision, RedemptionPreview } from \"@pafi-dev/core\";\nimport { evaluateRedemption, REDEMPTION_HISTORY_WINDOW_SEC } from \"./evaluator\";\nimport { PolicyProvider } from \"./policyProvider\";\nimport type {\n IRedemptionHistoryStore,\n PolicyProviderConfig,\n} from \"./types\";\n\nexport interface RedemptionServiceConfig {\n policyProvider: PolicyProvider | PolicyProviderConfig;\n historyStore: IRedemptionHistoryStore;\n /** Defaults to () => Math.floor(Date.now() / 1000). */\n nowUnixSec?: () => number;\n}\n\n/**\n * High-level facade used by HTTP handlers. Combines PolicyProvider +\n * IRedemptionHistoryStore into preview/evaluate operations.\n *\n * preview(user) → RedemptionPreview (no amount required)\n * evaluate(user, amount) → RedemptionDecision\n * recordSuccessfulInitiate(..) → call AFTER signing the BurnRequest\n *\n * Note: this service does NOT mutate anything during evaluate(). The\n * caller must call recordSuccessfulInitiate() after the BurnRequest is\n * signed and the pending credit is reserved on the ledger. Splitting\n * \"decide\" from \"record\" lets the handler atomically reserve + record\n * under one DB transaction if it wants.\n */\nexport class RedemptionService {\n private readonly policyProvider: PolicyProvider;\n private readonly historyStore: IRedemptionHistoryStore;\n private readonly nowUnixSec: () => number;\n\n constructor(config: RedemptionServiceConfig) {\n this.policyProvider =\n config.policyProvider instanceof PolicyProvider\n ? config.policyProvider\n : new PolicyProvider(config.policyProvider);\n this.historyStore = config.historyStore;\n this.nowUnixSec =\n config.nowUnixSec ?? (() => Math.floor(Date.now() / 1000));\n }\n\n async preview(\n user: Address,\n pointTokenAddress?: Address,\n ): Promise<RedemptionPreview> {\n const decision = await this.evaluate(user, 0n, pointTokenAddress);\n return decision.preview;\n }\n\n async evaluate(\n user: Address,\n amountPt: bigint,\n pointTokenAddress?: Address,\n ): Promise<RedemptionDecision> {\n const { policy, source } = await this.policyProvider.getPolicy();\n const now = this.nowUnixSec();\n\n const [redeemedLast24hPt, lastRedeemedAtUnixSec] = await Promise.all([\n this.historyStore.sumRedeemedSince(\n user,\n now - REDEMPTION_HISTORY_WINDOW_SEC,\n pointTokenAddress,\n ),\n this.historyStore.getLastRedeemedAtUnixSec(user, pointTokenAddress),\n ]);\n\n return evaluateRedemption({\n policy,\n policySource: source,\n history: { redeemedLast24hPt, lastRedeemedAtUnixSec },\n amountPt,\n nowUnixSec: now,\n });\n }\n\n async recordSuccessfulInitiate(entry: {\n user: Address;\n amountPt: bigint;\n pointTokenAddress?: Address;\n reservationId?: string;\n }): Promise<void> {\n await this.historyStore.recordRedemption({\n ...entry,\n unixSec: this.nowUnixSec(),\n });\n }\n}\n","import { getAddress } from \"viem\";\nimport type { Address, PublicClient } from \"viem\";\nimport {\n POINT_TOKEN_V2_ABI,\n issuerRegistryAbi,\n getContractAddresses,\n getTokenCap,\n} from \"@pafi-dev/core\";\nimport {\n IssuerStateError,\n type IssuerRegistryRecord,\n type PreValidateMintResult,\n type TokenCapRecord,\n} from \"./types\";\n\nconst ISSUER_RECORD_TTL_MS = 30_000;\n\ninterface CachedState {\n value: PreValidateMintResult;\n expiresAt: number;\n}\n\n/**\n * Pure (framework-agnostic) validator for issuer state.\n *\n * Reads IssuerRegistry + PointToken on-chain state and pre-validates\n * mint requests before the user submits a UserOp. Catching these\n * off-chain lets issuers fail fast with a clear error rather than\n * wasting gas on a revert.\n *\n * Caching:\n * - `PointToken.issuer()` — memoized for the process lifetime (immutable)\n * - Full state (registry + totalSupply) — 30s TTL per PointToken\n * - Burst calls while a fetch is in-flight share the same Promise\n * (thundering-herd protection)\n *\n * Usage in NestJS: wrap this in an `@Injectable()` service; pass\n * `PublicClient` and `registryAddress` from your DI container.\n */\nexport class IssuerStateValidator {\n private readonly pointTokenIssuerCache = new Map<Address, Address>();\n private readonly stateCache = new Map<Address, CachedState>();\n private readonly inflight = new Map<Address, Promise<PreValidateMintResult>>();\n\n constructor(\n private readonly provider: PublicClient,\n private readonly registryAddress: Address,\n ) {}\n\n /**\n * Convenience factory — reads `registryAddress` from the SDK\n * `CONTRACT_ADDRESSES` map for the given chain.\n */\n static forChain(provider: PublicClient, chainId: number): IssuerStateValidator {\n const { issuerRegistry } = getContractAddresses(chainId);\n return new IssuerStateValidator(provider, issuerRegistry);\n }\n\n /**\n * Invalidate cached state for one PointToken, or everything if omitted.\n * Call after admin txs that change registry or cap settings.\n */\n invalidate(pointToken?: Address): void {\n if (pointToken) {\n const key = getAddress(pointToken);\n this.pointTokenIssuerCache.delete(key);\n this.stateCache.delete(key);\n this.inflight.delete(key);\n } else {\n this.pointTokenIssuerCache.clear();\n this.stateCache.clear();\n this.inflight.clear();\n }\n }\n\n /**\n * Resolve `PointToken.issuer()` once per token and memoize.\n * The issuer field is set at `initialize()` and never changes.\n */\n async getIssuerAddressForPointToken(pointToken: Address): Promise<Address> {\n const key = getAddress(pointToken);\n const cached = this.pointTokenIssuerCache.get(key);\n if (cached) return cached;\n\n const issuer = (await this.provider.readContract({\n address: key,\n abi: POINT_TOKEN_V2_ABI,\n functionName: \"issuer\",\n })) as Address;\n this.pointTokenIssuerCache.set(key, getAddress(issuer));\n return getAddress(issuer);\n }\n\n /**\n * Read registry record + totalSupply, with 30s cache and in-flight\n * deduplication. Does NOT throw on inactive/missing — returns raw state.\n */\n async getIssuerState(pointToken: Address): Promise<PreValidateMintResult> {\n const tokenAddr = getAddress(pointToken);\n const now = Date.now();\n\n const cached = this.stateCache.get(tokenAddr);\n if (cached && cached.expiresAt > now) return cached.value;\n\n const existing = this.inflight.get(tokenAddr);\n if (existing) return existing;\n\n const promise = this.fetchIssuerState(tokenAddr)\n .then((state) => {\n this.stateCache.set(tokenAddr, {\n value: state,\n expiresAt: Date.now() + ISSUER_RECORD_TTL_MS,\n });\n return state;\n })\n .finally(() => {\n this.inflight.delete(tokenAddr);\n });\n\n this.inflight.set(tokenAddr, promise);\n return promise;\n }\n\n /**\n * Validate that `amount` PT can be minted on `pointToken` right now.\n *\n * Throws `IssuerStateError` with:\n * - `ISSUER_NOT_REGISTERED` — registry has no record for this issuer\n * - `ISSUER_INACTIVE` — issuer.active is false\n * - `MINT_CAP_EXCEEDED` — totalSupply + amount would exceed hardCap\n *\n * Returns the fetched state on success so callers can log without a\n * second RPC round-trip.\n */\n async preValidateMint(\n pointToken: Address,\n amount: bigint,\n ): Promise<PreValidateMintResult> {\n let state: PreValidateMintResult;\n try {\n state = await this.getIssuerState(pointToken);\n } catch (err) {\n if ((err as Error).message.includes(\"IssuerNotFound\")) {\n throw new IssuerStateError(\n \"ISSUER_NOT_REGISTERED\",\n `IssuerRegistry has no record for PointToken ${pointToken}`,\n { pointToken },\n );\n }\n throw err;\n }\n\n const { issuer, totalSupply, hardCap, remaining } = state;\n\n if (!issuer.active) {\n throw new IssuerStateError(\n \"ISSUER_INACTIVE\",\n `Issuer ${issuer.issuerAddress} is deactivated on IssuerRegistry`,\n { issuer: issuer.issuerAddress, pointToken: issuer.pointToken },\n );\n }\n\n if (totalSupply + amount > hardCap) {\n throw new IssuerStateError(\n \"MINT_CAP_EXCEEDED\",\n `Requested ${amount} PT would exceed mint cap. ` +\n `Cap=${hardCap}, minted=${totalSupply}, remaining=${remaining}`,\n {\n requested: amount.toString(),\n cap: hardCap.toString(),\n minted: totalSupply.toString(),\n remaining: remaining.toString(),\n },\n );\n }\n\n return state;\n }\n\n private async fetchIssuerState(tokenAddr: Address): Promise<PreValidateMintResult> {\n const issuerAddr = await this.getIssuerAddressForPointToken(tokenAddr);\n\n // First read the issuer record to learn which oracle owns this token's\n // cap. Cap config moved off the issuer in v1.6 (per-token, not\n // per-issuer) — `MintingOracle.tokenCaps(pointToken)` is the source.\n //\n // Use the auto-generated struct ABI directly. Earlier code used a\n // flat-output workaround for a viem ≤2.48 decoding bug, but that\n // workaround stopped matching the on-chain wire format once v1.6\n // added a second `string` (symbol) to the struct — the function\n // now returns a dynamic tuple with an outer 0x20 offset that flat\n // ABI mis-decodes (manifests as \"Number ... not in safe integer\n // range\"). Modern viem (≥2.47.x) decodes struct returns correctly.\n const issuerStruct = (await this.provider.readContract({\n address: this.registryAddress,\n abi: issuerRegistryAbi,\n functionName: \"getIssuer\",\n args: [issuerAddr],\n })) as {\n issuerAddress: Address;\n signerAddress: Address;\n name: string;\n symbol: string;\n active: boolean;\n pointToken: Address;\n mintingOracle: Address;\n };\n\n const issuer: IssuerRegistryRecord = {\n issuerAddress: issuerStruct.issuerAddress,\n signerAddress: issuerStruct.signerAddress,\n name: issuerStruct.name,\n symbol: issuerStruct.symbol,\n active: issuerStruct.active,\n pointToken: issuerStruct.pointToken,\n mintingOracle: issuerStruct.mintingOracle,\n };\n\n const [tokenCap, totalSupply] = await Promise.all([\n getTokenCap(this.provider, issuer.mintingOracle, tokenAddr),\n this.provider.readContract({\n address: tokenAddr,\n abi: POINT_TOKEN_V2_ABI,\n functionName: \"totalSupply\",\n }) as Promise<bigint>,\n ]);\n\n const tokenCapRecord: TokenCapRecord = {\n declaredTotalSupply: tokenCap.declaredTotalSupply,\n capBasisPoints: tokenCap.capBasisPoints,\n };\n\n const hardCap =\n (tokenCapRecord.declaredTotalSupply *\n BigInt(tokenCapRecord.capBasisPoints)) /\n 10000n;\n const remaining = hardCap > totalSupply ? hardCap - totalSupply : 0n;\n\n return {\n issuer,\n tokenCap: tokenCapRecord,\n totalSupply,\n hardCap,\n remaining,\n };\n }\n}\n","import type { Address } from \"viem\";\nimport type { IRedemptionHistoryStore } from \"./types\";\n\ninterface Entry {\n user: string;\n amountPt: bigint;\n pointTokenAddress: string | null;\n unixSec: number;\n}\n\n/**\n * In-memory IRedemptionHistoryStore for tests + the bundled NestJS\n * example. Production issuers should implement this against their\n * existing burn/audit table — sumRedeemedSince is hot path on every\n * redemption preview.\n */\nexport class MemoryRedemptionHistoryStore implements IRedemptionHistoryStore {\n private readonly entries: Entry[] = [];\n\n async sumRedeemedSince(\n user: Address,\n sinceUnixSec: number,\n pointTokenAddress?: Address,\n ): Promise<bigint> {\n const userKey = user.toLowerCase();\n const tokenKey = pointTokenAddress?.toLowerCase() ?? null;\n let total = 0n;\n for (const e of this.entries) {\n if (e.user !== userKey) continue;\n if (e.unixSec < sinceUnixSec) continue;\n if (tokenKey !== null && e.pointTokenAddress !== tokenKey) continue;\n total += e.amountPt;\n }\n return total;\n }\n\n async getLastRedeemedAtUnixSec(\n user: Address,\n pointTokenAddress?: Address,\n ): Promise<number | null> {\n const userKey = user.toLowerCase();\n const tokenKey = pointTokenAddress?.toLowerCase() ?? null;\n let latest: number | null = null;\n for (const e of this.entries) {\n if (e.user !== userKey) continue;\n if (tokenKey !== null && e.pointTokenAddress !== tokenKey) continue;\n if (latest === null || e.unixSec > latest) latest = e.unixSec;\n }\n return latest;\n }\n\n async recordRedemption(entry: {\n user: Address;\n amountPt: bigint;\n pointTokenAddress?: Address;\n unixSec: number;\n }): Promise<void> {\n this.entries.push({\n user: entry.user.toLowerCase(),\n amountPt: entry.amountPt,\n pointTokenAddress: entry.pointTokenAddress?.toLowerCase() ?? null,\n unixSec: entry.unixSec,\n });\n }\n}\n","// @pafi/issuer — Issuer backend boilerplate built on top of @pafi/core.\n//\n// Public API surface is exported in phases (see TASK_TRACKER §1.5):\n// Phase 1 — ledger / policy / signer / session store (interfaces + defaults)\n// Phase 2 — AuthService + JWT middleware\n// Phase 3 — RelayService + FeeManager\n// Phase 4 — MintingGateway orchestrator\n// Phase 5 — PointIndexer\n// Phase 6 — IssuerApiHandlers\n// Phase 7 — createIssuerService factory\n\ndeclare const __PAFI_ISSUER_SDK_VERSION__: string | undefined;\nexport const PAFI_ISSUER_SDK_VERSION: string =\n typeof __PAFI_ISSUER_SDK_VERSION__ === \"string\"\n ? __PAFI_ISSUER_SDK_VERSION__\n : \"dev\";\n\n// -----------------------------------------------------------------------------\n// SDK error base — every typed error inherits from this\n// -----------------------------------------------------------------------------\nexport {\n PafiSdkError,\n ValidationError,\n ConfigurationError,\n SDK_ERROR_HTTP_STATUS_CODE,\n defaultErrorTypeForStatus,\n type SdkErrorHttpStatus,\n type PafiErrorType,\n} from \"./errors\";\n\n// -----------------------------------------------------------------------------\n// HTTP — framework-agnostic error envelope normalizer\n// -----------------------------------------------------------------------------\nexport * from \"./http\";\n\n// -----------------------------------------------------------------------------\n// Phase 1 — core interfaces + default in-memory implementations\n// -----------------------------------------------------------------------------\nexport * from \"./ledger\";\nexport * from \"./policy\";\n\n// -----------------------------------------------------------------------------\n// Phase 2 — Auth\n// (includes ISessionStore, MemorySessionStore, AuthService, AuthError,\n// NonceManager, authenticateRequest)\n// -----------------------------------------------------------------------------\nexport * from \"./auth\";\n\n// -----------------------------------------------------------------------------\n// Phase 3 — RelayService (prepareMint / prepareBurn) + FeeManager\n// -----------------------------------------------------------------------------\nexport * from \"./relay\";\n\n// -----------------------------------------------------------------------------\n// Phase 5 — On-chain mint event indexer\n// -----------------------------------------------------------------------------\nexport * from \"./indexer\";\n\n// -----------------------------------------------------------------------------\n// Phase 6 — Framework-agnostic HTTP handlers\n// -----------------------------------------------------------------------------\nexport * from \"./api\";\n\n// -----------------------------------------------------------------------------\n// Pools — optional subgraph-backed PoolsProvider helper\n// -----------------------------------------------------------------------------\nexport * from \"./pools\";\n\n// -----------------------------------------------------------------------------\n// v1.4 — Balance aggregation (off-chain ledger + on-chain balanceOf)\n// -----------------------------------------------------------------------------\nexport * from \"./balance\";\n\n// -----------------------------------------------------------------------------\n// v1.4 — PAFI Backend paymaster proxy client (replaces direct Coinbase)\n// -----------------------------------------------------------------------------\nexport * from \"./pafi-backend\";\n\n// -----------------------------------------------------------------------------\n// Phase 7 — Top-level factory + IssuerService type\n// -----------------------------------------------------------------------------\nexport {\n createIssuerService,\n type IssuerServiceConfig,\n type IssuerService,\n} from \"./config\";\n\n// -----------------------------------------------------------------------------\n// v1.4 — Mobile prepare/submit pattern: pending UserOp storage types\n// -----------------------------------------------------------------------------\nexport * from \"./userop-store\";\n\n// -----------------------------------------------------------------------------\n// v1.4 — IssuerStateValidator: pre-validate mint cap + active status off-chain\n// -----------------------------------------------------------------------------\nexport * from \"./issuer-state\";\n\n// -----------------------------------------------------------------------------\n// Redemption restriction (settlement-fed policy + per-user history)\n// -----------------------------------------------------------------------------\nexport * from \"./redemption\";\n"],"mappings":"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","names":["randomBytes","getAddress","getAddress","randomBytes","getAddress","getAddress","getAddress","parseAbiItem","TRANSFER_EVENT","parseAbiItem","ZERO_ADDRESS","DEFAULT_CONFIRMATIONS","DEFAULT_BATCH_SIZE","DEFAULT_POLL_INTERVAL_MS","getAddress","getAddress","getAddress","result","getAddress","POINT_TOKEN_V2_ABI","getPointTokenBalance","getContractAddresses","getAddress","POINT_TOKEN_V2_ABI","getContractAddresses","getPointTokenBalance","getAddress","getAddress","getAddress","getContractAddresses","isNoWrapper","DEFAULT_SIG_DEADLINE_SEC","getAddress","getContractAddresses","decodeBatchExecuteCalls","getContractAddresses","getContractAddresses","decodeBatchExecuteCalls","ENTRY_POINT_V08","computeUserOpHash","buildUserOpTypedData","getContractAddresses","serializeUserOpToJsonRpc","getAddress","getContractAddresses","computeUserOpHash","buildUserOpTypedData","ENTRY_POINT_V08","getAddress","decodeBatchExecuteCalls","ENTRY_POINT_V08","getContractAddresses","parseEip7702DelegatedAddress","getAddress","decodeBatchExecuteCalls","getContractAddresses","parseEip7702DelegatedAddress","DEFAULT_CACHE_TTL_MS","getPointTokenBalance","getAddress","getContractAddresses","getPafiServiceUrls","DEFAULT_CACHE_TTL_MS","getAddress","getContractAddresses","getAddress","POINT_TOKEN_V2_ABI","getContractAddresses","getContractAddresses","getAddress","POINT_TOKEN_V2_ABI"]}
|
|
1
|
+
{"version":3,"sources":["../src/policy/defaultPolicy.ts","../src/auth/memorySessionStore.ts","../src/auth/nonceManager.ts","../src/auth/loginVerifier.ts","../src/auth/errors.ts","../src/auth/jwtMiddleware.ts","../src/auth/rateLimiter.ts","../src/relay/types.ts","../src/relay/relayService.ts","../src/relay/feeManager.ts","../src/indexer/types.ts","../src/indexer/pointIndexer.ts","../src/indexer/burnIndexer.ts","../src/api/handlers.ts","../src/api/handlers/ptRedeemHandler.ts","../src/api/statusHandlers.ts","../src/api/mobileHandlers.ts","../src/userop-store/serialize.ts","../src/userop-store/memoryStore.ts","../src/userop-store/prepareUserOp.ts","../src/pafi-backend/types.ts","../src/pafi-backend/helpers.ts","../src/api/handlers/ptClaimHandler.ts","../src/issuer-state/types.ts","../src/api/handlers/perpDepositHandler.ts","../src/api/delegateHandler.ts","../src/api/issuerApiAdapter.ts","../src/pools/subgraphPoolsProvider.ts","../src/pools/subgraphNativeUsdtQuoter.ts","../src/pools/nativePtQuoter.ts","../src/balance/balanceAggregator.ts","../src/pafi-backend/client.ts","../src/config.ts","../src/redemption/evaluator.ts","../src/redemption/settlementClient.ts","../src/redemption/defaults.ts","../src/redemption/policyProvider.ts","../src/redemption/service.ts","../src/issuer-state/validator.ts","../src/redemption/memoryHistoryStore.ts","../src/index.ts"],"sourcesContent":["import type { Address, PublicClient } from \"viem\";\nimport type { IPointLedger } from \"../ledger/types\";\nimport type { IPolicyEngine, PolicyDecision, PolicyEvalRequest } from \"./types\";\n\n/**\n * Options for constructing a DefaultPolicyEngine.\n *\n * `mintingOracleAddress` and `provider` are optional — if omitted, the\n * engine skips the on-chain cap check (useful for dev/testing).\n *\n * `verifyMintCap` is injectable so tests can simulate cap rejections\n * without needing a real contract.\n */\nexport interface DefaultPolicyEngineOptions {\n ledger: IPointLedger;\n provider?: PublicClient;\n mintingOracleAddress?: Address;\n /**\n * Override the on-chain cap check. Defaults to `@pafi/core`'s\n * `verifyMintCap`, which reverts if the issuer's declared supply would\n * be exceeded. A rejected check should throw; returning normally is pass.\n */\n verifyMintCap?: (\n client: PublicClient,\n oracle: Address,\n issuer: Address,\n amount: bigint,\n ) => Promise<void>;\n /**\n * Resolve a point-token address to the issuer address for the cap check.\n * Required iff `mintingOracleAddress + provider` are supplied.\n */\n resolveIssuer?: (pointToken: Address) => Promise<Address>;\n}\n\n/**\n * Default policy engine — performs two checks:\n *\n * 1. **Off-chain balance** — the user must have at least `amount` of\n * unlocked point balance in the issuer ledger.\n * 2. **On-chain cap** — (optional) calls the MintingOracle via\n * `verifyMintCap` to confirm that minting this amount would not exceed\n * the issuer's declared total supply.\n *\n * Issuers extend this class (or implement `IPolicyEngine` directly) to add\n * KYC, volume caps, claim budgets, or anti-abuse rules.\n */\nexport class DefaultPolicyEngine implements IPolicyEngine {\n private readonly ledger: IPointLedger;\n private readonly provider?: PublicClient;\n private readonly mintingOracleAddress?: Address;\n private readonly verifyMintCap?: DefaultPolicyEngineOptions[\"verifyMintCap\"];\n private readonly resolveIssuer?: DefaultPolicyEngineOptions[\"resolveIssuer\"];\n\n constructor(opts: DefaultPolicyEngineOptions) {\n this.ledger = opts.ledger;\n if (opts.provider) this.provider = opts.provider;\n if (opts.mintingOracleAddress) {\n this.mintingOracleAddress = opts.mintingOracleAddress;\n }\n if (opts.verifyMintCap) this.verifyMintCap = opts.verifyMintCap;\n if (opts.resolveIssuer) this.resolveIssuer = opts.resolveIssuer;\n\n if (!opts.mintingOracleAddress || !opts.provider || !opts.verifyMintCap || !opts.resolveIssuer) {\n if (process.env.NODE_ENV === 'production') {\n throw new Error(\n '[PAFI] DefaultPolicyEngine: on-chain MintingOracle cap check is required in production. ' +\n 'Configure mintingOracleAddress, provider, verifyMintCap, and resolveIssuer.',\n );\n }\n }\n }\n\n async evaluate(request: PolicyEvalRequest): Promise<PolicyDecision> {\n if (request.amount <= 0n) {\n return { approved: false, reason: \"Amount must be positive\" };\n }\n\n // 1. Off-chain balance check — scoped to the requested token so\n // multi-token issuers don't leak balance across tokens.\n const available = await this.ledger.getBalance(\n request.userAddress,\n request.pointTokenAddress,\n );\n if (available < request.amount) {\n return {\n approved: false,\n reason: `Insufficient balance (available=${available}, requested=${request.amount})`,\n };\n }\n\n // 2. On-chain cap check — only runs if fully configured\n if (\n this.mintingOracleAddress &&\n this.provider &&\n this.verifyMintCap &&\n this.resolveIssuer\n ) {\n try {\n const issuer = await this.resolveIssuer(request.pointTokenAddress);\n await this.verifyMintCap(\n this.provider,\n this.mintingOracleAddress,\n issuer,\n request.amount,\n );\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n approved: false,\n reason: `Minting cap check failed: ${msg}`,\n };\n }\n }\n\n return { approved: true };\n }\n}\n","import { randomBytes } from \"node:crypto\";\nimport { getAddress } from \"viem\";\nimport type { Address } from \"viem\";\nimport type { ISessionStore, Session } from \"./types\";\n\nexport interface MemorySessionStoreOptions {\n /**\n * How long a login nonce is valid before auto-expiry. Defaults to 5 min,\n * matching typical SIWE recommendations.\n */\n nonceTtlMs?: number;\n /** Clock override for tests. */\n now?: () => number;\n /**\n * Bypass the production-safety guard. By default, instantiating\n * `MemorySessionStore` when `process.env.NODE_ENV === \"production\"`\n * THROWS — multi-pod K8s deploys do not share Map state, so sessions\n * are not revocable across replicas, and tokens remain valid on pod\n * B after `logout` on pod A. Only set this `true` for single-pod\n * deployments where you've consciously accepted the trade-off.\n */\n dangerouslyAllowMemoryStoreInProduction?: boolean;\n}\n\nconst DEFAULT_NONCE_TTL_MS = 5 * 60 * 1000;\n\n/**\n * In-memory `ISessionStore` — Map-backed nonces and sessions with lazy\n * expiry sweeps on every read/write.\n *\n * Not safe across processes or restarts; intended for dev/test only.\n */\nexport class MemorySessionStore implements ISessionStore {\n private nonces = new Map<string, number>(); // nonce → expiresAt\n private sessions = new Map<string, Session>(); // tokenId → session\n private readonly nonceTtlMs: number;\n private readonly now: () => number;\n\n constructor(opts: MemorySessionStoreOptions = {}) {\n if (\n process.env.NODE_ENV === \"production\" &&\n !opts.dangerouslyAllowMemoryStoreInProduction\n ) {\n throw new Error(\n \"[PAFI] MemorySessionStore refuses to start in production \" +\n \"(NODE_ENV=production). Multi-pod K8s deploys do not share Map \" +\n \"state, so sessions are not revocable across replicas — `logout` \" +\n \"on pod A leaves the token valid on pod B until expiry. Use a \" +\n \"Redis-backed session store (see RedisSessionStoreService in \" +\n \"gg56-backend or implement your own ISessionStore). To bypass \" +\n \"for a single-pod deploy, pass \" +\n \"`dangerouslyAllowMemoryStoreInProduction: true`.\",\n );\n }\n this.nonceTtlMs = opts.nonceTtlMs ?? DEFAULT_NONCE_TTL_MS;\n this.now = opts.now ?? (() => Date.now());\n }\n\n // -------------------------------------------------------------------------\n // Nonces\n // -------------------------------------------------------------------------\n\n async createNonce(): Promise<string> {\n this.purgeExpiredNonces();\n const nonce = randomBytes(16).toString(\"hex\");\n this.nonces.set(nonce, this.now() + this.nonceTtlMs);\n return nonce;\n }\n\n async consumeNonce(nonce: string): Promise<boolean> {\n this.purgeExpiredNonces();\n const expiresAt = this.nonces.get(nonce);\n if (expiresAt === undefined) return false;\n // One-time use — always delete regardless of expiry result.\n this.nonces.delete(nonce);\n return expiresAt > this.now();\n }\n\n // -------------------------------------------------------------------------\n // Sessions\n // -------------------------------------------------------------------------\n\n async createSession(session: Session): Promise<void> {\n this.purgeExpiredSessions();\n const normalized: Session = {\n ...session,\n userAddress: getAddress(session.userAddress),\n };\n this.sessions.set(session.tokenId, normalized);\n }\n\n async getSession(tokenId: string): Promise<Session | null> {\n this.purgeExpiredSessions();\n const session = this.sessions.get(tokenId);\n if (!session) return null;\n if (session.expiresAt.getTime() <= this.now()) {\n this.sessions.delete(tokenId);\n return null;\n }\n return { ...session };\n }\n\n async revokeSession(tokenId: string): Promise<void> {\n this.sessions.delete(tokenId);\n }\n\n async revokeAllSessions(userAddress: Address): Promise<void> {\n const key = getAddress(userAddress);\n for (const [tokenId, session] of this.sessions.entries()) {\n if (session.userAddress === key) {\n this.sessions.delete(tokenId);\n }\n }\n }\n\n // -------------------------------------------------------------------------\n // Housekeeping\n // -------------------------------------------------------------------------\n\n private purgeExpiredNonces(): void {\n const now = this.now();\n for (const [nonce, expiresAt] of this.nonces.entries()) {\n if (expiresAt <= now) this.nonces.delete(nonce);\n }\n }\n\n private purgeExpiredSessions(): void {\n const now = this.now();\n for (const [tokenId, session] of this.sessions.entries()) {\n if (session.expiresAt.getTime() <= now) this.sessions.delete(tokenId);\n }\n }\n}\n","import type { ISessionStore } from \"./types\";\n\n/**\n * Thin wrapper around an `ISessionStore` exposing nonce generation and\n * single-use consumption as its own named surface. AuthService uses it\n * internally, and issuers can also use it directly for other flows that\n * need replay protection (e.g. a CSRF-style challenge).\n */\nexport class NonceManager {\n constructor(private readonly store: ISessionStore) {}\n\n /** Generate a fresh login nonce. The store is responsible for TTL. */\n async generate(): Promise<string> {\n return this.store.createNonce();\n }\n\n /**\n * Atomically validate + consume a nonce. Returns `true` iff the nonce\n * was known and unexpired (and is now removed from the store).\n */\n async consume(nonce: string): Promise<boolean> {\n return this.store.consumeNonce(nonce);\n }\n}\n","import { randomBytes } from \"node:crypto\";\nimport { SignJWT, jwtVerify, errors as joseErrors } from \"jose\";\nimport { getAddress } from \"viem\";\nimport type { Address, Hex } from \"viem\";\nimport { parseLoginMessage, verifyLoginMessage } from \"@pafi-dev/core\";\nimport { AuthError } from \"./errors\";\nimport { NonceManager } from \"./nonceManager\";\nimport type { ISessionStore, Session } from \"./types\";\n\n/**\n * Validate that the JWT signing secret has enough entropy that HS256\n * brute-force is infeasible. A length-only check (`secret.length >= 32`)\n * is insufficient — trivially weak secrets like `\"a\".repeat(32)` or\n * `\"password12345password12345password\"` pass length but have\n * near-zero search space against an attacker.\n *\n * Heuristic (conservative — rejects only obvious weak secrets, never\n * false-positives on real `crypto.randomBytes(32).toString(\"hex\")`):\n *\n * 1. Length ≥ 32 chars.\n * 2. ≥ 16 unique characters (a 32-byte random hex always has 16+).\n * 3. Shannon entropy ≥ 3.5 bits/char (random hex ≈ 4.0).\n * 4. Reject simple repeating patterns (e.g. \"abcabcabc...\").\n *\n * Recommended secret generation:\n * `node -e \"console.log(crypto.randomBytes(32).toString('hex'))\"`\n *\n * Throws on any failure with a descriptive message.\n */\nexport function assertJwtSecretStrength(secret: string | undefined): void {\n if (!secret) {\n throw new Error(\n \"AuthService: jwtSecret is required. Generate via \" +\n \"`node -e \\\"console.log(require('crypto').randomBytes(32).toString('hex'))\\\"`\",\n );\n }\n if (secret.length < 32) {\n throw new Error(\n `AuthService: jwtSecret too short (${secret.length} chars; need ≥ 32). ` +\n \"HS256 brute-force becomes feasible below this threshold.\",\n );\n }\n const uniqueChars = new Set(secret).size;\n if (uniqueChars < 16) {\n throw new Error(\n `AuthService: jwtSecret has only ${uniqueChars} unique characters; need ≥ 16. ` +\n \"A trivially weak secret (e.g. \\\"aaaaa...\\\") will pass length but offer almost no entropy. \" +\n \"Use `crypto.randomBytes(32).toString('hex')`.\",\n );\n }\n const entropy = shannonEntropyBitsPerChar(secret);\n if (entropy < 3.5) {\n throw new Error(\n `AuthService: jwtSecret entropy too low (${entropy.toFixed(2)} bits/char; need ≥ 3.5). ` +\n \"Use `crypto.randomBytes(32).toString('hex')` (≈ 4.0 bits/char).\",\n );\n }\n // Catch repeating patterns that pass uniqueness + entropy but are\n // structurally weak (e.g. \"abcdefgh\".repeat(8) has 8 unique chars but\n // repeats 8 times — already caught by uniqueChars < 16, but defensive).\n for (let period = 1; period <= secret.length / 4; period++) {\n if (secret.length % period !== 0) continue;\n const head = secret.slice(0, period);\n if (secret === head.repeat(secret.length / period)) {\n throw new Error(\n `AuthService: jwtSecret is a repeating pattern of period ${period}. ` +\n \"Use `crypto.randomBytes(32).toString('hex')`.\",\n );\n }\n }\n}\n\nfunction shannonEntropyBitsPerChar(s: string): number {\n const counts = new Map<string, number>();\n for (const c of s) counts.set(c, (counts.get(c) ?? 0) + 1);\n const len = s.length;\n let h = 0;\n for (const n of counts.values()) {\n const p = n / len;\n h -= p * Math.log2(p);\n }\n return h;\n}\n\n/**\n * Decode the `jti` claim from an expired JWT WITHOUT signature verification.\n * Used solely by `logout()` to revoke the session of a token that's already\n * past `exp` — signature must already have been validated upstream when the\n * token was issued; the only attack surface here is a forged token leaking\n * a valid `jti` to revoke someone else's session, which is impossible because\n * `jti` values are 16-byte secrets generated by `randomBytes`.\n *\n * Returns `{ jti: undefined }` on any parse failure.\n */\nfunction decodeExpiredJwtJti(token: string): { jti?: string } {\n try {\n const parts = token.split(\".\");\n if (parts.length !== 3) return {};\n const payloadB64 = parts[1];\n if (!payloadB64) return {};\n // Re-pad base64url\n const padded =\n payloadB64.replace(/-/g, \"+\").replace(/_/g, \"/\") +\n \"===\".slice((payloadB64.length + 3) % 4);\n const json = Buffer.from(padded, \"base64\").toString(\"utf-8\");\n const claims = JSON.parse(json) as { jti?: unknown };\n return typeof claims.jti === \"string\" ? { jti: claims.jti } : {};\n } catch {\n return {};\n }\n}\n\nexport interface AuthServiceConfig {\n sessionStore: ISessionStore;\n /** HMAC secret for signing JWTs (HS256). At least 32 bytes recommended. */\n jwtSecret: string;\n /**\n * JWT lifetime passed to `jose`. Accepts any of `jose`'s time strings\n * (`\"24h\"`, `\"7d\"`, `\"45m\"`, …). Defaults to `\"24h\"`.\n */\n jwtExpiresIn?: string;\n /**\n * Expected domain in the login message, e.g. `\"app.example.com\"`. The\n * SIWE spec requires the frontend to build the message with this exact\n * domain; the backend rejects any mismatch.\n */\n domain: string;\n /** Expected chain id. */\n chainId: number;\n /**\n * Optional `iss` (issuer) claim baked into JWTs and validated on\n * verify. Recommended for multi-issuer deployments to prevent\n * cross-validation if a JWT secret is accidentally shared. Examples:\n * `\"gg56-issuer\"`, `\"issuer.gg56.com\"`. When unset, no `iss` is\n * written or required (back-compat).\n */\n issuer?: string;\n /**\n * Optional `aud` (audience) claim baked into JWTs and validated on\n * verify. Recommended to bind tokens to a specific consumer, e.g.\n * `\"gg56-mobile\"` or `\"gg56-web\"`. When unset, no `aud` is written\n * or required (back-compat).\n */\n audience?: string;\n /** Clock override for tests. */\n now?: () => Date;\n}\n\nexport interface LoginResult {\n token: string;\n userAddress: Address;\n tokenId: string;\n expiresAt: Date;\n}\n\nexport interface AuthContext {\n userAddress: Address;\n chainId: number;\n tokenId: string;\n}\n\ninterface JwtPayload {\n userAddress: Address;\n chainId: number;\n}\n\nconst DEFAULT_EXPIRES_IN = \"24h\";\n\n/**\n * Wallet-based authentication service implementing the EIP-4361 (Sign-In\n * With Ethereum) login flow with server-issued JWTs.\n *\n * Flow:\n * 1. `getNonce()` — client fetches a nonce, stores it locally\n * 2. Client constructs a login message with the nonce + signs with wallet\n * 3. `login(message, signature)` — server validates fields + signature,\n * consumes the nonce, persists a session, returns a JWT\n * 4. Protected endpoints call `verifyToken(token)` to resolve the user\n * context; the session is re-checked on every call, so `logout()`\n * revokes access immediately.\n */\nexport class AuthService {\n private readonly sessionStore: ISessionStore;\n private readonly jwtSecret: Uint8Array;\n private readonly jwtExpiresIn: string;\n private readonly domain: string;\n private readonly chainId: number;\n private readonly issuer?: string;\n private readonly audience?: string;\n private readonly nonceManager: NonceManager;\n private readonly now: () => Date;\n\n constructor(config: AuthServiceConfig) {\n assertJwtSecretStrength(config.jwtSecret);\n this.sessionStore = config.sessionStore;\n this.jwtSecret = new TextEncoder().encode(config.jwtSecret);\n this.jwtExpiresIn = config.jwtExpiresIn ?? DEFAULT_EXPIRES_IN;\n this.domain = config.domain;\n this.chainId = config.chainId;\n this.issuer = config.issuer;\n this.audience = config.audience;\n this.nonceManager = new NonceManager(config.sessionStore);\n this.now = config.now ?? (() => new Date());\n }\n\n // -------------------------------------------------------------------------\n // Public API\n // -------------------------------------------------------------------------\n\n /** Generate a fresh login nonce. */\n async getNonce(): Promise<string> {\n return this.nonceManager.generate();\n }\n\n /**\n * Verify a signed login message and issue a JWT on success.\n * Throws an `AuthError` on any validation failure.\n */\n async login(message: string, signature: Hex): Promise<LoginResult> {\n // 1. Parse the message\n let parsed;\n try {\n parsed = parseLoginMessage(message);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new AuthError(\"INVALID_MESSAGE\", `Could not parse login message: ${msg}`);\n }\n\n // 2. Field checks (before doing any crypto work)\n if (parsed.expirationTime == null) {\n throw new AuthError(\n \"INVALID_MESSAGE\",\n \"login message must include expirationTime\",\n );\n }\n if (parsed.domain !== this.domain) {\n throw new AuthError(\n \"DOMAIN_MISMATCH\",\n `Expected domain \"${this.domain}\", got \"${parsed.domain}\"`,\n );\n }\n if (parsed.chainId !== this.chainId) {\n throw new AuthError(\n \"CHAIN_MISMATCH\",\n `Expected chainId ${this.chainId}, got ${parsed.chainId}`,\n );\n }\n\n const now = this.now();\n if (parsed.notBefore && parsed.notBefore.getTime() > now.getTime()) {\n throw new AuthError(\n \"MESSAGE_NOT_YET_VALID\",\n \"Login message is not yet valid\",\n );\n }\n if (\n parsed.expirationTime &&\n parsed.expirationTime.getTime() <= now.getTime()\n ) {\n throw new AuthError(\"MESSAGE_EXPIRED\", \"Login message has expired\");\n }\n\n // 3. Signature verification\n const verifyResult = await verifyLoginMessage(message, signature);\n if (!verifyResult.valid) {\n throw new AuthError(\n \"SIGNATURE_INVALID\",\n \"Signature does not match the address in the login message\",\n );\n }\n\n // 4. Consume the nonce (one-time use). Order matters: do this AFTER\n // signature verification so a failed signature does not burn a\n // nonce the user can still legitimately spend on a retry.\n const nonceOk = await this.nonceManager.consume(parsed.nonce);\n if (!nonceOk) {\n throw new AuthError(\n \"NONCE_INVALID\",\n \"Nonce is unknown, expired, or already used\",\n );\n }\n\n // 5. Create session + mint JWT\n const userAddress = getAddress(verifyResult.address);\n const tokenId = randomBytes(16).toString(\"hex\");\n const issuedAt = now;\n const expiresAt = parseExpiry(issuedAt, this.jwtExpiresIn);\n\n const session: Session = {\n tokenId,\n userAddress,\n chainId: this.chainId,\n issuedAt,\n expiresAt,\n };\n await this.sessionStore.createSession(session);\n\n let signer = new SignJWT({\n userAddress,\n chainId: this.chainId,\n } satisfies JwtPayload)\n .setProtectedHeader({ alg: \"HS256\" })\n .setJti(tokenId)\n .setIssuedAt(Math.floor(issuedAt.getTime() / 1000))\n .setExpirationTime(Math.floor(expiresAt.getTime() / 1000));\n if (this.issuer) signer = signer.setIssuer(this.issuer);\n if (this.audience) signer = signer.setAudience(this.audience);\n const token = await signer.sign(this.jwtSecret);\n\n return { token, userAddress, tokenId, expiresAt };\n }\n\n /** Revoke the session backing the given JWT (logout). */\n async logout(token: string): Promise<void> {\n // STEP 1 — verify the JWT signature. We tolerate expiry (logout\n // immediately after expiry should still revoke the session-store\n // entry), but signature failure / malformed tokens MUST throw —\n // accepting them would let attackers issue 200 responses against\n // forged tokens and hide signature errors from monitoring.\n let payload;\n try {\n const result = await jwtVerify(token, this.jwtSecret, {\n clockTolerance: 60, // allow logout right after expiry\n ...(this.issuer ? { issuer: this.issuer } : {}),\n ...(this.audience ? { audience: this.audience } : {}),\n });\n payload = result.payload;\n } catch (err) {\n // Expired tokens: still allow revocation (decode the jti without\n // verification — `jose` v5 rejects expired tokens via JWTExpired\n // even with clockTolerance if the gap is large).\n if (err instanceof joseErrors.JWTExpired) {\n const decoded = decodeExpiredJwtJti(token);\n if (decoded.jti) {\n try {\n await this.sessionStore.revokeSession(decoded.jti);\n } catch (storeErr) {\n // Storage error during revocation — log only; logout is\n // idempotent so a missing session row is fine.\n this.logSessionStoreError(storeErr);\n }\n }\n return;\n }\n // ANY other JWT failure (signature verification, malformed,\n // missing claims) → throw. Forged tokens MUST NOT receive a 200.\n if (\n err instanceof joseErrors.JWSSignatureVerificationFailed ||\n err instanceof joseErrors.JWSInvalid ||\n err instanceof joseErrors.JWTInvalid\n ) {\n throw new AuthError(\"TOKEN_INVALID\", \"JWT verification failed\");\n }\n // Unknown class — be conservative and reject.\n throw new AuthError(\n \"TOKEN_INVALID\",\n `JWT verification failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n // STEP 2 — verified payload, now revoke the session entry. Storage\n // errors here are swallowed (logout idempotent).\n if (payload.jti) {\n try {\n await this.sessionStore.revokeSession(payload.jti);\n } catch (storeErr) {\n this.logSessionStoreError(storeErr);\n }\n }\n }\n\n private logSessionStoreError(err: unknown): void {\n const msg = err instanceof Error ? err.message : String(err);\n // \"not found\" is benign — session already revoked or expired.\n if (msg.includes(\"not found\")) return;\n console.error(\"[PAFI] AuthService logout: session store error\", err);\n }\n\n /**\n * Verify a JWT and return the authenticated user context. Throws an\n * `AuthError` if the token is missing, malformed, expired, revoked, or\n * signed by a different key.\n */\n async verifyToken(token: string): Promise<AuthContext> {\n let payload;\n try {\n const result = await jwtVerify(token, this.jwtSecret, {\n ...(this.issuer ? { issuer: this.issuer } : {}),\n ...(this.audience ? { audience: this.audience } : {}),\n });\n payload = result.payload;\n } catch (err) {\n if (err instanceof joseErrors.JWTExpired) {\n throw new AuthError(\"TOKEN_EXPIRED\", \"JWT has expired\");\n }\n if (\n err instanceof joseErrors.JWTInvalid ||\n err instanceof joseErrors.JWSInvalid ||\n err instanceof joseErrors.JWSSignatureVerificationFailed\n ) {\n throw new AuthError(\"TOKEN_INVALID\", \"JWT is invalid\");\n }\n throw new AuthError(\"TOKEN_INVALID\", \"JWT verification failed\");\n }\n\n const tokenId = payload.jti;\n if (!tokenId) {\n throw new AuthError(\"TOKEN_INVALID\", \"JWT is missing jti claim\");\n }\n\n const session = await this.sessionStore.getSession(tokenId);\n if (!session) {\n throw new AuthError(\n \"SESSION_REVOKED\",\n \"Session is no longer active (revoked or expired)\",\n );\n }\n\n const userAddress = (payload as unknown as JwtPayload).userAddress;\n const chainId = (payload as unknown as JwtPayload).chainId;\n if (!userAddress || typeof chainId !== \"number\") {\n throw new AuthError(\"TOKEN_INVALID\", \"JWT payload is malformed\");\n }\n\n return {\n userAddress: getAddress(userAddress),\n chainId,\n tokenId,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Parse a duration string in the minimal form `<number><unit>` where unit is\n * one of `s`, `m`, `h`, `d`. Matches the subset of `jose`'s time-string\n * format that AuthService needs to compute the absolute expiry.\n */\nfunction parseExpiry(from: Date, expiresIn: string): Date {\n const match = expiresIn.match(/^(\\d+)\\s*(s|m|h|d)$/i);\n if (!match) {\n throw new Error(\n `AuthService: unsupported jwtExpiresIn \"${expiresIn}\" — use e.g. \"24h\"`,\n );\n }\n const n = Number(match[1]);\n const unit = match[2]!.toLowerCase();\n const multiplier =\n unit === \"s\" ? 1000 : unit === \"m\" ? 60_000 : unit === \"h\" ? 3_600_000 : 86_400_000;\n return new Date(from.getTime() + n * multiplier);\n}\n","import { PafiSdkError, type SdkErrorHttpStatus } from \"../errors\";\n\n/**\n * Error codes surfaced by the auth subsystem. Issuers can pattern-match on\n * `err.code` to translate into framework-specific HTTP responses.\n */\nexport type AuthErrorCode =\n | \"INVALID_MESSAGE\" // Could not parse the login message\n | \"DOMAIN_MISMATCH\" // Message domain does not match expected\n | \"CHAIN_MISMATCH\" // Message chainId does not match expected\n | \"NONCE_INVALID\" // Unknown, expired, or already-consumed nonce\n | \"MESSAGE_EXPIRED\" // Login message past its expirationTime\n | \"MESSAGE_NOT_YET_VALID\" // Login message before its notBefore\n | \"SIGNATURE_INVALID\" // Signature did not recover to the claimed address\n | \"MISSING_TOKEN\" // No Authorization header\n | \"MALFORMED_TOKEN\" // Header present but not \"Bearer <token>\"\n | \"TOKEN_INVALID\" // JWT verification failed (bad sig, tampered)\n | \"TOKEN_EXPIRED\" // JWT past its exp claim\n | \"SESSION_REVOKED\"; // JWT ok but backing session is gone\n\n/**\n * Map auth error codes → HTTP status the issuer's controller should\n * surface. `forbidden` for any signature / token integrity failure\n * (caller proved nothing); `unprocessable` for content/format errors\n * (caller's payload didn't validate); `not_found` for missing session.\n */\nfunction statusForCode(code: AuthErrorCode): SdkErrorHttpStatus {\n switch (code) {\n case \"INVALID_MESSAGE\":\n case \"DOMAIN_MISMATCH\":\n case \"CHAIN_MISMATCH\":\n case \"MESSAGE_EXPIRED\":\n case \"MESSAGE_NOT_YET_VALID\":\n case \"MALFORMED_TOKEN\":\n return \"unprocessable\";\n case \"NONCE_INVALID\":\n case \"SIGNATURE_INVALID\":\n case \"MISSING_TOKEN\":\n case \"TOKEN_INVALID\":\n case \"TOKEN_EXPIRED\":\n return \"forbidden\";\n case \"SESSION_REVOKED\":\n return \"not_found\";\n }\n}\n\n/**\n * Extends `PafiSdkError` so issuer controllers can route auth failures\n * through the same `createSdkErrorMapper` as everything else (no\n * separate `instanceof AuthError` check needed).\n */\nexport class AuthError extends PafiSdkError {\n readonly code: AuthErrorCode;\n readonly httpStatus: SdkErrorHttpStatus;\n\n constructor(code: AuthErrorCode, message: string) {\n super(message);\n this.code = code;\n this.httpStatus = statusForCode(code);\n }\n}\n","import { AuthError } from \"./errors\";\nimport type { AuthContext, AuthService } from \"./loginVerifier\";\n\n/**\n * Extracts a Bearer token from an `Authorization` header value and verifies\n * it via the supplied `AuthService`. Framework-agnostic — issuers wrap this\n * in their framework's middleware pattern (Express, Fastify, Hono, …).\n *\n * Throws an `AuthError` if the header is missing, malformed, or the token\n * fails verification. Callers should translate the `err.code` into an\n * appropriate HTTP status (401 for auth failures, 500 for anything else).\n */\nexport async function authenticateRequest(\n authHeader: string | undefined,\n authService: AuthService,\n): Promise<AuthContext> {\n if (!authHeader) {\n throw new AuthError(\n \"MISSING_TOKEN\",\n \"Authorization header is required\",\n );\n }\n\n const match = authHeader.match(/^Bearer\\s+(\\S+)\\s*$/i);\n if (!match) {\n throw new AuthError(\n \"MALFORMED_TOKEN\",\n \"Authorization header must be in the form 'Bearer <token>'\",\n );\n }\n\n const token = match[1]!;\n return authService.verifyToken(token);\n}\n","/**\n * Per-key rate limiting for unauthenticated public endpoints.\n *\n * `/auth/nonce` and `/auth/login` are unauthenticated and CPU-bound\n * (SIWE message parsing + ECDSA `recover`), so without rate limiting\n * an attacker can saturate the issuer at low cost. Worse, on\n * `/auth/nonce` they can flood the session store with single-use\n * nonces; with `MemorySessionStore` this is a pure DoS.\n *\n * Issuers wire a `IRateLimiter` impl in `IssuerApiHandlersConfig` —\n * `MemoryRateLimiter` is the default reference impl (single-pod, dev).\n * Production deployments use a Redis-backed impl that shares state\n * across replicas (similar pattern to `ISessionStore`).\n *\n * The \"key\" is up to the caller — typically the client IP, but the\n * issuer's HTTP layer chooses (could be userAddress on POST /login).\n */\n\nexport interface IRateLimiter {\n /**\n * Check + increment a counter under `key`. Returns `{ allowed: false,\n * retryAfterMs }` when the caller has exceeded its quota for this\n * window. Default config is action-specific (see\n * `RateLimiterConfig`).\n *\n * Idempotent in the sense that successive calls with the same `key`\n * within the same window count toward the limit; a separate window\n * starts after `windowMs` elapses.\n */\n consume(\n key: string,\n action: RateLimitAction,\n ): Promise<{ allowed: boolean; retryAfterMs?: number }>;\n}\n\nexport type RateLimitAction = \"auth_nonce\" | \"auth_login\";\n\nexport interface RateLimiterConfig {\n /**\n * Per-key max requests per `windowMs`. Defaults:\n * - auth_nonce: 1 per 2s (30/min)\n * - auth_login: 5 per minute\n *\n * Issuers can tune per their threat model.\n */\n limits?: Partial<\n Record<RateLimitAction, { max: number; windowMs: number }>\n >;\n /** Clock override for tests. */\n now?: () => number;\n}\n\nconst DEFAULT_LIMITS: Record<\n RateLimitAction,\n { max: number; windowMs: number }\n> = {\n auth_nonce: { max: 30, windowMs: 60_000 }, // 30 nonces/min ≈ 1 per 2s\n auth_login: { max: 5, windowMs: 60_000 }, // 5 logins/min\n};\n\n/**\n * In-memory `IRateLimiter` — Map of `{key, action}` → count + window\n * start. Lazy expiry on read. Single-process: NOT safe for multi-pod\n * deployments. Use a Redis impl in production.\n */\nexport class MemoryRateLimiter implements IRateLimiter {\n private buckets = new Map<\n string,\n { count: number; windowStartedAt: number }\n >();\n private readonly limits: Record<\n RateLimitAction,\n { max: number; windowMs: number }\n >;\n private readonly now: () => number;\n\n constructor(config: RateLimiterConfig = {}) {\n if (\n process.env.NODE_ENV === \"production\" &&\n !process.env.PAFI_ALLOW_MEMORY_RATE_LIMITER_IN_PROD\n ) {\n // eslint-disable-next-line no-console\n console.warn(\n \"[PAFI] MemoryRateLimiter not safe for multi-pod K8s deploys — \" +\n \"rate counters are NOT shared across replicas, allowing \" +\n \"round-robin bypass. Use a Redis-backed IRateLimiter in production.\",\n );\n }\n this.limits = {\n ...DEFAULT_LIMITS,\n ...(config.limits ?? {}),\n } as typeof DEFAULT_LIMITS;\n this.now = config.now ?? (() => Date.now());\n }\n\n async consume(\n key: string,\n action: RateLimitAction,\n ): Promise<{ allowed: boolean; retryAfterMs?: number }> {\n const limit = this.limits[action];\n if (!limit) return { allowed: true };\n\n const bucketKey = `${action}:${key}`;\n const now = this.now();\n const bucket = this.buckets.get(bucketKey);\n\n // Fresh bucket OR window expired → reset.\n if (!bucket || now - bucket.windowStartedAt >= limit.windowMs) {\n this.buckets.set(bucketKey, { count: 1, windowStartedAt: now });\n return { allowed: true };\n }\n\n if (bucket.count < limit.max) {\n bucket.count += 1;\n return { allowed: true };\n }\n\n const retryAfterMs = Math.max(\n 0,\n bucket.windowStartedAt + limit.windowMs - now,\n );\n return { allowed: false, retryAfterMs };\n }\n\n /**\n * Test helper — clear all buckets. Not part of `IRateLimiter`; only\n * exposed on the in-memory impl for unit tests.\n */\n reset(): void {\n this.buckets.clear();\n }\n}\n\n/**\n * Convenience: a no-op `IRateLimiter` that lets every request through.\n * Useful as a default in `IssuerApiHandlersConfig` when the issuer\n * hasn't wired a rate limiter yet (back-compat) — emits a one-time\n * warning at construction so consumers notice.\n */\nexport class NoopRateLimiter implements IRateLimiter {\n private warned = false;\n\n async consume(): Promise<{ allowed: boolean }> {\n if (!this.warned && process.env.NODE_ENV === \"production\") {\n // eslint-disable-next-line no-console\n console.warn(\n \"[PAFI] NoopRateLimiter active — `/auth/nonce` and `/auth/login` \" +\n \"are NOT throttled. Wire a `MemoryRateLimiter` (dev) or Redis-backed \" +\n \"impl (prod) via `IssuerApiHandlersConfig.rateLimiter`.\",\n );\n this.warned = true;\n }\n return { allowed: true };\n }\n}\n","import { PafiSdkError } from \"../errors\";\n\nexport type RelayErrorCode = \"ENCODE_FAILED\"; // calldata encoding / signing threw\n\nexport class RelayError extends PafiSdkError {\n readonly httpStatus = \"unprocessable\" as const;\n readonly code: RelayErrorCode;\n\n constructor(code: RelayErrorCode, message: string, cause?: unknown) {\n super(message);\n this.code = code;\n if (cause !== undefined) {\n // Attach via assignment — `Error.cause` is ES2022 standard but the\n // base class doesn't take it via constructor. Cast to unknown so\n // TS doesn't complain about widening readonly.\n (this as { cause?: unknown }).cause = cause;\n }\n }\n}\n","import {\n encodeFunctionData,\n erc20Abi,\n type Address,\n type Hex,\n type WalletClient,\n} from \"viem\";\nimport type { PublicClient } from \"viem\";\nimport {\n POINT_TOKEN_V2_ABI,\n mintFeeWrapperAbi,\n buildPartialUserOperation,\n signMintRequest,\n getContractAddresses,\n quoteOperatorFeePt,\n type Operation,\n type PartialUserOperation,\n type PointTokenDomainConfig,\n type BurnRequest,\n} from \"@pafi-dev/core\";\nimport { RelayError } from \"./types\";\n\n/**\n * Optional config that lets `RelayService` auto-quote the operator fee\n * + auto-resolve the recipient when callers don't pass `feeAmount` /\n * `feeRecipient`. When both `provider` + `chainId` are set, callers can\n * omit the fee params entirely; the service will:\n *\n * 1. Resolve `feeRecipient = getContractAddresses(chainId).pafiFeeRecipient`\n * 2. If `feeAmount` is undefined → run `quoteOperatorFeePt(...)` against\n * Chainlink + V4 subgraph to compute the PT amount.\n *\n * When unset, the legacy \"caller passes feeAmount + feeRecipient or no\n * fee\" behavior applies, so existing integrations keep working.\n */\nexport interface RelayServiceConfig {\n provider?: PublicClient;\n chainId?: number;\n}\n\n/**\n * Builds unsigned `PartialUserOperation` payloads for the v1.4 sponsored\n * flow. The service is stateless and HTTP-client-free:\n *\n * - `prepareMint` signs a `MintRequest` EIP-712 with the caller-supplied\n * issuer signer wallet, then encodes `PointToken.mint(to, amount,\n * deadline, minterSig)` into a UserOp the frontend submits via\n * EIP-7702 + Paymaster.\n * - `prepareBurn` mirrors the above on the burn side using a\n * pre-signed `BurnRequest` + `PointToken.burn(from, amount,\n * deadline, burnerSig)`.\n *\n * There is no broadcasting, no operator wallet, no simulation — those\n * concerns moved to the Bundler + Paymaster in v1.4.\n */\nexport class RelayService {\n private readonly provider: PublicClient | undefined;\n private readonly chainId: number | undefined;\n\n constructor(config: RelayServiceConfig = {}) {\n this.provider = config.provider;\n this.chainId = config.chainId;\n }\n\n /**\n * Resolve the fee recipient + amount applied to the next UserOp:\n *\n * - If caller passed an explicit `feeRecipient`, use it (testing\n * only — sponsor-relayer's L1 will reject any non-canonical\n * recipient with `INSUFFICIENT_FEE`). Otherwise, default to\n * `getContractAddresses(chainId).pafiFeeRecipient` when the\n * service has a `chainId` configured.\n * - If caller passed `feeAmount`, use it. Otherwise, when the\n * service has both `provider` + `chainId`, auto-quote via\n * `quoteOperatorFeePt`.\n * - When the service is unconfigured AND caller passed nothing,\n * return `{ feeAmount: 0n, feeRecipient: undefined }` — legacy\n * \"no fee\" behavior, caller must opt in for the gas-reimbursement\n * transfer to be added to the batch.\n */\n private async resolveFee(params: {\n feeAmount?: bigint;\n feeRecipient?: Address;\n pointTokenAddress: Address;\n }): Promise<{ feeAmount: bigint; feeRecipient: Address | undefined }> {\n const feeRecipient =\n params.feeRecipient ??\n (this.chainId !== undefined\n ? getContractAddresses(this.chainId).pafiFeeRecipient\n : undefined);\n\n if (params.feeAmount !== undefined) {\n return { feeAmount: params.feeAmount, feeRecipient };\n }\n\n if (this.provider && this.chainId !== undefined) {\n // Enable stale fallback: if the subgraph has no pool for this PT yet\n // (e.g. brand-new clone before any liquidity is added) the quoter\n // would otherwise throw `OracleStaleError(\"subgraph\", ...)` which\n // surfaces as \"pafiToken or pool not found\" and blocks every mint\n // until ops deploys + seeds the V4 pool. With fallback enabled the\n // quoter uses the default 0.1 USDT/PT price (and the onWarning\n // hook on `IssuerApiAdapter` logs the fallback for ops visibility).\n const feeAmount = await quoteOperatorFeePt({\n provider: this.provider,\n chainId: this.chainId,\n pointTokenAddress: params.pointTokenAddress,\n allowStaleFallback: true,\n onFallback: (info) => {\n // eslint-disable-next-line no-console\n console.warn(\n `[RelayService] operatorFeeQuoter fallback (${info.source}): ${info.reason} → using ${info.fallbackValue}`,\n );\n },\n });\n return { feeAmount, feeRecipient };\n }\n\n return { feeAmount: 0n, feeRecipient };\n }\n\n /**\n * Build an unsigned UserOp for Scenario 1 (Mint) — sig-gated\n * `PointToken.mint(to, amount, deadline, minterSig)`.\n */\n async prepareMint(params: PrepareMintParams): Promise<PartialUserOperation> {\n if (!params.batchExecutorAddress) {\n throw new RelayError(\n \"ENCODE_FAILED\",\n \"prepareMint: batchExecutorAddress required\",\n );\n }\n if (!params.userAddress) {\n throw new RelayError(\"ENCODE_FAILED\", \"prepareMint: userAddress required\");\n }\n if (!params.pointTokenAddress) {\n throw new RelayError(\n \"ENCODE_FAILED\",\n \"prepareMint: pointTokenAddress required\",\n );\n }\n if (params.amount <= 0n) {\n throw new RelayError(\"ENCODE_FAILED\", \"prepareMint: amount must be positive\");\n }\n if (!params.issuerSignerWallet) {\n throw new RelayError(\n \"ENCODE_FAILED\",\n \"prepareMint: issuerSignerWallet required (for MintRequest EIP-712 signature)\",\n );\n }\n if (params.deadline <= 0n) {\n throw new RelayError(\"ENCODE_FAILED\", \"prepareMint: deadline must be positive\");\n }\n const nowSecs = BigInt(Math.floor(Date.now() / 1000));\n if (params.deadline <= nowSecs) {\n throw new RelayError(\"ENCODE_FAILED\", \"prepareMint: deadline is in the past\");\n }\n const MAX_DEADLINE_WINDOW = 3600n; // 1 hour\n if (params.deadline > nowSecs + MAX_DEADLINE_WINDOW) {\n throw new RelayError(\n \"ENCODE_FAILED\",\n \"prepareMint: deadline exceeds maximum allowed window (1 hour)\",\n );\n }\n\n // 1. Sign MintForRequest with the issuer minter signer.\n //\n // v1.6 typehash: MintForRequest(user, receiver, amount, nonce, deadline)\n // - `user` = the off-chain spender (drives nonce stream).\n // - `receiver` = on-chain msg.sender of `PointToken.mint(...)`.\n //\n // Direct path: receiver = user (user calls PointToken.mint themselves)\n // Wrapper-mediated path: receiver = wrapper, user = end-user\n //\n // The `mintFeeWrapperAddress` param flips between the two; when unset\n // the path defaults to direct mint for backward compat with deployments\n // that haven't onboarded the wrapper yet.\n const useWrapper = params.mintFeeWrapperAddress !== undefined;\n const receiverForSig: Address = useWrapper\n ? params.mintFeeWrapperAddress!\n : params.userAddress;\n\n let minterSig: Hex;\n try {\n const sig = await signMintRequest(\n params.issuerSignerWallet,\n params.domain,\n {\n user: params.userAddress,\n receiver: receiverForSig,\n amount: params.amount,\n nonce: params.mintRequestNonce,\n deadline: params.deadline,\n },\n );\n minterSig = sig.serialized;\n } catch (err) {\n throw new RelayError(\n \"ENCODE_FAILED\",\n `prepareMint: failed to sign MintForRequest: ${errorMessage(err)}`,\n err,\n );\n }\n\n // 2. Encode the mint call.\n // - Direct: PointToken.mint(user, amount, deadline, sig)\n // - Wrapper-mediated: MintFeeWrapper.mintWithFee(pointToken, user, gross, deadline, sig)\n let mintCallData: Hex;\n let mintTarget: Address;\n try {\n if (useWrapper) {\n mintCallData = encodeFunctionData({\n abi: mintFeeWrapperAbi,\n functionName: \"mintWithFee\",\n args: [\n params.pointTokenAddress,\n params.userAddress,\n params.amount,\n params.deadline,\n minterSig,\n ],\n });\n mintTarget = params.mintFeeWrapperAddress!;\n } else {\n mintCallData = encodeFunctionData({\n abi: POINT_TOKEN_V2_ABI,\n functionName: \"mint\",\n args: [params.userAddress, params.amount, params.deadline, minterSig],\n });\n mintTarget = params.pointTokenAddress;\n }\n } catch (err) {\n throw new RelayError(\n \"ENCODE_FAILED\",\n `prepareMint: failed to encode mint call: ${errorMessage(err)}`,\n err,\n );\n }\n\n const operations: Operation[] = [\n {\n target: mintTarget,\n value: 0n,\n data: mintCallData,\n },\n ];\n\n // 3. Operator fee transfer (PT reimbursement to PAFI for sponsored\n // gas). Resolved via `resolveFee` — auto-quotes when the service\n // was constructed with provider+chainId and the caller didn't pass\n // an explicit `feeAmount`. Pass `feeAmount: 0n` to opt out.\n const { feeAmount, feeRecipient } = await this.resolveFee({\n feeAmount: params.feeAmount,\n feeRecipient: params.feeRecipient,\n pointTokenAddress: params.pointTokenAddress,\n });\n if (feeAmount > 0n) {\n if (!feeRecipient) {\n throw new RelayError(\n \"ENCODE_FAILED\",\n \"prepareMint: feeRecipient could not be resolved — pass `feeRecipient` explicitly or construct RelayService with a `chainId`.\",\n );\n }\n if (feeRecipient === \"0x0000000000000000000000000000000000000000\") {\n throw new RelayError(\n \"ENCODE_FAILED\",\n \"prepareMint: feeRecipient must not be zero address\",\n );\n }\n operations.push({\n target: params.pointTokenAddress,\n value: 0n,\n data: encodeFunctionData({\n abi: erc20Abi,\n functionName: \"transfer\",\n args: [feeRecipient, feeAmount],\n }),\n });\n }\n\n return buildPartialUserOperation({\n sender: params.userAddress,\n nonce: params.aaNonce,\n operations,\n gasLimits: {\n callGasLimit: params.callGasLimit ?? 300_000n,\n verificationGasLimit: params.verificationGasLimit ?? 150_000n,\n preVerificationGas: params.preVerificationGas ?? 50_000n,\n },\n });\n }\n\n /**\n * Build an unsigned UserOp for Scenario 2 (Burn/Redeem) — sig-gated\n * `PointToken.burn(from, amount, deadline, burnerSig)`. Caller\n * provides a pre-signed `BurnRequest` + sig bytes (typically from\n * `PTRedeemHandler`).\n *\n * Direct burn (no sig) was dropped in v1.4 — every burn now goes\n * through the issuer-signed `BurnRequest` path.\n */\n async prepareBurn(params: PrepareBurnParams): Promise<PartialUserOperation> {\n if (!params.pointTokenAddress) {\n throw new RelayError(\"ENCODE_FAILED\", \"prepareBurn: pointTokenAddress required\");\n }\n if (!params.batchExecutorAddress) {\n throw new RelayError(\n \"ENCODE_FAILED\",\n \"prepareBurn: batchExecutorAddress required\",\n );\n }\n if (!params.burnRequest || !params.burnerSignature) {\n throw new RelayError(\n \"ENCODE_FAILED\",\n \"prepareBurn: burnRequest + burnerSignature required\",\n );\n }\n\n let burnCallData: Hex;\n try {\n burnCallData = encodeFunctionData({\n abi: POINT_TOKEN_V2_ABI,\n functionName: \"burn\",\n args: [\n params.burnRequest.from,\n params.burnRequest.amount,\n params.burnRequest.deadline,\n params.burnerSignature,\n ],\n });\n } catch (err) {\n throw new RelayError(\n \"ENCODE_FAILED\",\n `prepareBurn: failed to encode burn call: ${errorMessage(err)}`,\n err,\n );\n }\n\n // Burn ops: fee transfer (if any) FIRST, then burn.\n // Fee comes out of the user's `amount` PT before the burn, so a user\n // holding exactly `amount` can complete the flow. BurnRequest is\n // signed for `amount - fee` upstream.\n const operations: Operation[] = [];\n\n const { feeAmount, feeRecipient } = await this.resolveFee({\n feeAmount: params.feeAmount,\n feeRecipient: params.feeRecipient,\n pointTokenAddress: params.pointTokenAddress,\n });\n if (feeAmount > 0n) {\n if (!feeRecipient) {\n throw new RelayError(\n \"ENCODE_FAILED\",\n \"prepareBurn: feeRecipient could not be resolved — pass `feeRecipient` explicitly or construct RelayService with a `chainId`.\",\n );\n }\n if (feeRecipient === \"0x0000000000000000000000000000000000000000\") {\n throw new RelayError(\n \"ENCODE_FAILED\",\n \"prepareBurn: feeRecipient must not be zero address\",\n );\n }\n operations.push({\n target: params.pointTokenAddress,\n value: 0n,\n data: encodeFunctionData({\n abi: erc20Abi,\n functionName: \"transfer\",\n args: [feeRecipient, feeAmount],\n }),\n });\n }\n\n operations.push({\n target: params.pointTokenAddress,\n value: 0n,\n data: burnCallData,\n });\n\n return buildPartialUserOperation({\n sender: params.userAddress,\n nonce: params.aaNonce,\n operations,\n gasLimits: {\n callGasLimit: params.callGasLimit ?? 300_000n,\n verificationGasLimit: params.verificationGasLimit ?? 150_000n,\n preVerificationGas: params.preVerificationGas ?? 50_000n,\n },\n });\n }\n}\n\n// ===========================================================================\n// Parameter types\n// ===========================================================================\n\n/**\n * v1.4 — sig-gated `PointToken.mint(to, amount, deadline, minterSig)`.\n *\n * The issuer backend validates off-chain (balance, policy, KYC), signs\n * a `MintRequest` EIP-712 with its minter signer, and packages the\n * whole thing into a UserOp for the user to submit via EIP-7702 +\n * Paymaster. On confirmation, PointIndexer watches `Transfer(0x0, user,\n * amount)` and resolves the ledger lock.\n */\nexport interface PrepareMintParams {\n /** User EOA that will send the UserOp (via EIP-7702 delegation). */\n userAddress: Address;\n /** ERC-4337 account nonce. Caller fetches from EntryPoint v0.7. */\n aaNonce: bigint;\n /** BatchExecutor delegation target (chain-specific). */\n batchExecutorAddress: Address;\n /** PointToken contract — the call target + EIP-712 verifying contract. */\n pointTokenAddress: Address;\n /** PT amount to mint to `userAddress`. */\n amount: bigint;\n /**\n * Issuer minter signer wallet — signs the `MintRequest` EIP-712.\n * Must be added to `PointToken.minters[]` via `addMinter(signerAddr)`\n * at provisioning time. Typically HSM/KMS-backed in prod.\n */\n issuerSignerWallet: WalletClient;\n /** EIP-712 domain for MintRequest. */\n domain: PointTokenDomainConfig;\n /** Current `mintRequestNonces[userAddress]` — caller reads from contract. */\n mintRequestNonce: bigint;\n /** Unix timestamp after which the signature expires. */\n deadline: bigint;\n /**\n * Optional v1.6+ — when set, mint is routed through MintFeeWrapper:\n * - sig.receiver = wrapper address (vs `userAddress` for direct path)\n * - calldata target = wrapper.mintWithFee(pointToken, user, gross, ...)\n * - the wrapper then mints `amount` to itself, splits per recipient list,\n * and forwards net to the user.\n *\n * Leave undefined to keep the v1.5 direct-mint behavior (no fee skim).\n * Wrapper must be registered on the PointToken via\n * `IssuerRegistry.addIssuer` cascade (or owner-only `wrapper.registerToken`)\n * before this path works on-chain.\n */\n mintFeeWrapperAddress?: Address;\n /**\n * Optional — application-level fee transfer appended after mint.\n * Set both `feeAmount` and `feeRecipient` together.\n */\n feeAmount?: bigint;\n feeRecipient?: Address;\n /** Gas limits — defaults are conservative; caller can tighten. */\n callGasLimit?: bigint;\n verificationGasLimit?: bigint;\n preVerificationGas?: bigint;\n}\n\n/**\n * v1.4 — sig-gated burn only. Direct (no-sig) burn was dropped because\n * every burn now goes through `BurnRequest` EIP-712 signed by the\n * issuer burner. The `mode: 'burnWithSig'` discriminant is preserved\n * for backwards-compat with existing callers but is no longer\n * branched on internally.\n */\nexport interface PrepareBurnParams {\n userAddress: Address;\n aaNonce: bigint;\n pointTokenAddress: Address;\n batchExecutorAddress: Address;\n /** Discriminant kept for backwards compat. Always `'burnWithSig'`. */\n mode?: \"burnWithSig\";\n /** BurnRequest message the issuer burner signer signed. */\n burnRequest: BurnRequest;\n /** Serialized EIP-712 signature (bytes) over `burnRequest`. */\n burnerSignature: Hex;\n /**\n * Optional — application-level PT fee transfer appended after burn.\n * Used for gas reimbursement on the sponsored path. Set both\n * `feeAmount` and `feeRecipient` together. User must hold\n * `burnAmount + feeAmount` PT.\n */\n feeAmount?: bigint;\n feeRecipient?: Address;\n callGasLimit?: bigint;\n verificationGasLimit?: bigint;\n preVerificationGas?: bigint;\n}\n\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n","import type { PublicClient } from \"viem\";\n\nexport interface FeeManagerConfig {\n /** Provider used for gas price reads. */\n provider: PublicClient;\n /**\n * Typical gas used by a single sponsored UserOp. Default: 500_000.\n * The manager multiplies this by current gas price to get native\n * cost, then converts via the injected `quoteNativeToFee`.\n */\n gasUnits?: bigint;\n /**\n * Safety margin applied before charging the user, as basis points.\n * 12_000 = 120%. Default: 12_000.\n */\n gasPremiumBps?: number;\n /**\n * Quote function — given an amount of native wei, return the\n * equivalent amount in the fee currency (PT raw units in v1.4,\n * USDT 6-decimal in legacy v1.2 flows).\n *\n * Injected so the manager stays chain- and token-agnostic. Issuers\n * wire it to `@pafi-dev/core` V4 quoting, a subgraph query, or an\n * oracle feed.\n */\n quoteNativeToFee: (amountNative: bigint) => Promise<bigint>;\n}\n\nconst DEFAULT_GAS_UNITS = 500_000n;\nconst DEFAULT_PREMIUM_BPS = 12_000; // 120%\n\n/**\n * Computes how much fee to collect from the user to cover the gas cost\n * of a sponsored UserOp.\n *\n * ## v1.4 scope change\n *\n * The fee is now expressed in the **fee currency** chosen by the caller\n * (PT for mint/burn, USDT for swap/perp_deposit) — not hardcoded to USDT.\n *\n * **Operator rebalancing is gone.** In v1.4 the operator no longer holds\n * ETH directly — gas is paid by PAFI sponsor-relayer via the paymaster-proxy\n * (see [SPONSORED_PATH_FLOW.md]). The fee collected here is an\n * application-level ERC-20 transfer inside the same UserOp batch, not a\n * reimbursement to a wallet that needs topping up.\n *\n * `rebalanceIfNeeded()` and `swapUsdtToNative` were removed in 0.3.0.\n */\nexport class FeeManager {\n private readonly provider: PublicClient;\n private readonly gasUnits: bigint;\n private readonly gasPremiumBps: number;\n private readonly quoteNativeToFee: FeeManagerConfig[\"quoteNativeToFee\"];\n private cachedFee: bigint | null = null;\n private cacheExpiresAt = 0;\n private static readonly CACHE_TTL_MS = 10_000;\n\n constructor(config: FeeManagerConfig) {\n if (!config.provider) throw new Error(\"FeeManager: provider required\");\n if (!config.quoteNativeToFee)\n throw new Error(\"FeeManager: quoteNativeToFee required\");\n\n this.provider = config.provider;\n this.gasUnits = config.gasUnits ?? DEFAULT_GAS_UNITS;\n this.gasPremiumBps = config.gasPremiumBps ?? DEFAULT_PREMIUM_BPS;\n this.quoteNativeToFee = config.quoteNativeToFee;\n }\n\n /**\n * Estimate the fee (in the caller's fee currency) to charge for the\n * next sponsored UserOp:\n *\n * nativeCost = gasUnits × gasPrice\n * withPremium = nativeCost × premiumBps / 10_000\n * fee = quoteNativeToFee(withPremium)\n *\n * For backward compatibility with v0.2.x code that reads `gasFeeUsdt`\n * from the response, the name `estimateGasFee` is kept — but the\n * currency depends on how the caller wired `quoteNativeToFee`.\n */\n async estimateGasFee(): Promise<bigint> {\n const now = Date.now();\n if (this.cachedFee !== null && now < this.cacheExpiresAt) {\n return this.cachedFee;\n }\n const gasPrice = await this.provider.getGasPrice();\n const nativeCost = gasPrice * this.gasUnits;\n const withPremium = (nativeCost * BigInt(this.gasPremiumBps)) / 10_000n;\n const fee = await this.quoteNativeToFee(withPremium);\n this.cachedFee = fee;\n this.cacheExpiresAt = now + FeeManager.CACHE_TTL_MS;\n return fee;\n }\n}\n","import type { Address, Hex } from \"viem\";\n\n/** Decoded Transfer(from=0x0 → to) event used to finalize a mint. */\nexport interface MintEvent {\n /** Destination address (the user who received the minted points) */\n to: Address;\n /** Amount minted (in wei / base units) */\n amount: bigint;\n /** Block number the mint was included in */\n blockNumber: bigint;\n /** Transaction hash that emitted the event */\n txHash: Hex;\n /** Log index within the tx, for deterministic ordering */\n logIndex: number;\n}\n\n/** Decoded Transfer(from=user → 0x0) event used to finalize a burn-for-credit. */\nexport interface BurnEvent {\n /** The burner — user whose PT was burned. */\n from: Address;\n /** Amount burned. */\n amount: bigint;\n blockNumber: bigint;\n txHash: Hex;\n logIndex: number;\n}\n\n/**\n * Cursor persistence interface — the indexer reports the next block\n * number it is about to process so the caller can write it to Redis /\n * Postgres / a file. The SDK does not own persistence because every\n * issuer has their own storage stack.\n */\nexport interface IIndexerCursorStore {\n /** Return the last persisted cursor (`undefined` on first run). */\n load(): Promise<bigint | undefined>;\n /** Persist a new cursor value. Called after each successful batch. */\n save(blockNumber: bigint): Promise<void>;\n}\n\n/**\n * No-op cursor store. Useful when the caller wants to drive the cursor\n * entirely via `processBlockRange()` and doesn't need persistence.\n */\nexport class InMemoryCursorStore implements IIndexerCursorStore {\n private cursor: bigint | undefined;\n async load(): Promise<bigint | undefined> {\n return this.cursor;\n }\n async save(blockNumber: bigint): Promise<void> {\n this.cursor = blockNumber;\n }\n}\n","import { getAddress, parseAbiItem } from \"viem\";\nimport type { Address, Log, PublicClient } from \"viem\";\nimport type { IPointLedger, LockedMintRequest } from \"../ledger/types\";\nimport type { IIndexerCursorStore, MintEvent } from \"./types\";\nimport { InMemoryCursorStore } from \"./types\";\n\nexport interface PointIndexerConfig {\n provider: PublicClient;\n pointTokenAddress: Address;\n ledger: IPointLedger;\n /**\n * v1.6 — when set, indexer listens to `MintFeeWrapper.MintWithFee`\n * (filtered by this `pointToken`) instead of `PointToken.Transfer(0x0)`.\n *\n * Required for wrapper-mediated mints because the on-chain Transfer\n * destination is the wrapper itself (not the end user), so a naive\n * `Transfer(0x0 → user)` watch would leave PENDING locks unresolved.\n *\n * `MintWithFee(indexed pointToken, indexed to, grossAmount, netAmount,\n * feeAmount)` carries the actual recipient + the gross amount that\n * matches the off-chain lock.\n *\n * Pass `undefined` (or the dead-zero address) for direct-mint chains\n * without a wrapper — indexer falls back to legacy Transfer mode.\n */\n mintFeeWrapperAddress?: Address;\n /**\n * Block to start from on first run. Ignored if the cursor store already\n * has a value. Defaults to `0n`.\n */\n fromBlock?: bigint;\n /**\n * Persistent cursor store. Defaults to an `InMemoryCursorStore` which\n * only survives inside the current process.\n */\n cursorStore?: IIndexerCursorStore;\n /**\n * Reorg safety: only treat events as final after this many confirmations.\n * The indexer reads `latestBlock - confirmations` as its high-water mark.\n * Default: 3 (a conservative number for Base L2).\n */\n confirmations?: number;\n /**\n * How many blocks to scan per `getLogs` call. Keeps RPC pages bounded.\n * Default: 2000 (comfortably under most provider page limits).\n */\n batchSize?: number;\n /**\n * Polling interval (ms) used by `start()`. Default: 5000 (5s).\n */\n pollIntervalMs?: number;\n /** Clock override for tests. */\n now?: () => number;\n /**\n * Observability hook fired on EVERY tick error (RPC failure, ledger\n * write failure, etc). Issuers MUST wire this to their alert\n * pipeline (Sentry / Datadog / PagerDuty) — without it, the indexer\n * silently swallows errors via `console.error`, and indexer outage\n * means PENDING mint locks never resolve (user balances stuck).\n *\n * When omitted, falls back to `console.error` for back-compat.\n */\n onTickError?: (err: unknown) => void;\n}\n\nconst TRANSFER_EVENT = parseAbiItem(\n \"event Transfer(address indexed from, address indexed to, uint256 value)\",\n);\n\nconst MINT_WITH_FEE_EVENT = parseAbiItem(\n \"event MintWithFee(address indexed pointToken, address indexed to, uint256 grossAmount, uint256 netAmount, uint256 feeAmount)\",\n);\n\nconst ZERO_ADDRESS: Address = \"0x0000000000000000000000000000000000000000\";\nconst DEAD_ADDRESS: Address = \"0x000000000000000000000000000000000000dEaD\";\n\nconst DEFAULT_CONFIRMATIONS = 3;\nconst DEFAULT_BATCH_SIZE = 2000n;\nconst DEFAULT_POLL_INTERVAL_MS = 5000;\n\nfunction isNoWrapper(addr: Address | undefined): boolean {\n if (!addr) return true;\n const checksummed = getAddress(addr);\n return checksummed === ZERO_ADDRESS || checksummed === DEAD_ADDRESS;\n}\n\n/**\n * Watches mint events on PointToken and finalizes the issuer ledger when\n * a confirmed mint matches a PENDING lock.\n *\n * **Two modes** — selected at construction by `mintFeeWrapperAddress`:\n *\n * 1. **Wrapper mode (v1.6, recommended for mainnet)**\n * Listens to `MintFeeWrapper.MintWithFee(indexed pointToken, indexed to,\n * grossAmount, netAmount, feeAmount)` filtered by `pointToken`. The\n * `to` field is the actual end-user (not the wrapper), and `grossAmount`\n * matches the off-chain lock's amount.\n *\n * 2. **Direct mode (legacy / chains without wrapper)**\n * Listens to `PointToken.Transfer(from=0x0 → to)` — the original v1.5\n * behavior. Used when the wrapper address is undefined / zero / dead.\n *\n * Finalization strategy (per event):\n * 1. Find a PENDING locked mint request for `to` with matching amount.\n * 2. Call `ledger.deductBalance(to, amount, txHash)` — this is idempotent\n * in the default `MemoryPointLedger` because deducting also resolves\n * the matching lock.\n * 3. If no matching lock is found (e.g. a manual `PointToken.mint()` call\n * or a race where the lock already expired), log and skip — this\n * intentionally does NOT credit the ledger, because the gateway is\n * the only sanctioned way to spawn a mint.\n *\n * Reorg safety: events are only processed when they are at least\n * `confirmations` blocks deep. A new `getLogs` call on every poll picks\n * up finalized blocks from the persisted cursor.\n *\n * Pure-polling design (rather than `watchContractEvent`) keeps the SDK\n * compatible with HTTP-only providers and with RPC rotation.\n */\nexport class PointIndexer {\n private readonly provider: PublicClient;\n private readonly pointTokenAddress: Address;\n private readonly mintFeeWrapperAddress: Address | undefined;\n private readonly ledger: IPointLedger;\n private readonly cursorStore: IIndexerCursorStore;\n private readonly startBlock: bigint;\n private readonly confirmations: bigint;\n private readonly batchSize: bigint;\n private readonly pollIntervalMs: number;\n private readonly onTickError?: (err: unknown) => void;\n\n private running = false;\n private timer: ReturnType<typeof setTimeout> | undefined;\n\n constructor(config: PointIndexerConfig) {\n if (!config.provider) throw new Error(\"PointIndexer: provider required\");\n if (!config.pointTokenAddress)\n throw new Error(\"PointIndexer: pointTokenAddress required\");\n if (!config.ledger) throw new Error(\"PointIndexer: ledger required\");\n\n this.provider = config.provider;\n this.pointTokenAddress = getAddress(config.pointTokenAddress);\n this.mintFeeWrapperAddress = isNoWrapper(config.mintFeeWrapperAddress)\n ? undefined\n : getAddress(config.mintFeeWrapperAddress as Address);\n this.ledger = config.ledger;\n this.cursorStore = config.cursorStore ?? new InMemoryCursorStore();\n this.startBlock = config.fromBlock ?? 0n;\n this.confirmations = BigInt(config.confirmations ?? DEFAULT_CONFIRMATIONS);\n this.batchSize = BigInt(config.batchSize ?? Number(DEFAULT_BATCH_SIZE));\n this.pollIntervalMs = config.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n if (config.onTickError) this.onTickError = config.onTickError;\n }\n\n // -------------------------------------------------------------------------\n // Lifecycle\n // -------------------------------------------------------------------------\n\n /** Begin polling. Schedules `tick()` on a loop. */\n start(): void {\n if (this.running) return;\n this.running = true;\n // Kick off the first tick immediately instead of waiting for the\n // first interval. `tick()` has its own try/catch so it never\n // produces an unhandled rejection, but we attach a defensive\n // `.catch` here so any future refactor that removes the inner\n // catch still surfaces failures via `onTickError`.\n this.tick().catch((err) => this.handleTickError(err));\n }\n\n /** Stop polling. Safe to call multiple times. */\n stop(): void {\n this.running = false;\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = undefined;\n }\n }\n\n /**\n * Run one poll cycle: load cursor → scan [cursor, safeHead] in\n * `batchSize` chunks → persist new cursor. Swallows any error and\n * schedules the next tick. Visible for test harnesses via a public name.\n */\n async tick(): Promise<void> {\n if (!this.running) return;\n try {\n const latest = await this.provider.getBlockNumber();\n const safeHead = latest - this.confirmations;\n if (safeHead < 0n) {\n this.scheduleNext();\n return;\n }\n\n const stored = await this.cursorStore.load();\n const from = stored ?? this.startBlock;\n if (from > safeHead) {\n this.scheduleNext();\n return;\n }\n\n await this.processBlockRange(from, safeHead);\n } catch (err) {\n this.handleTickError(err);\n }\n this.scheduleNext();\n }\n\n private handleTickError(err: unknown): void {\n if (this.onTickError) {\n try {\n this.onTickError(err);\n } catch {\n // onTickError must never throw — fall back to console.error to\n // surface the meta-failure.\n // eslint-disable-next-line no-console\n console.error(\"[PAFI] PointIndexer onTickError threw:\", err);\n }\n } else {\n // eslint-disable-next-line no-console\n console.error(\"[PAFI] PointIndexer tick error:\", err);\n }\n }\n\n private scheduleNext(): void {\n if (!this.running) return;\n this.timer = setTimeout(\n () => this.tick().catch((err) => this.handleTickError(err)),\n this.pollIntervalMs,\n );\n }\n\n // -------------------------------------------------------------------------\n // Block scanning\n // -------------------------------------------------------------------------\n\n /**\n * Scan `[from, to]` inclusive for mint events in `batchSize` chunks.\n * Callers can use this directly to backfill a specific range without\n * engaging `start()`. On completion, the cursor is advanced to `to + 1`.\n */\n async processBlockRange(from: bigint, to: bigint): Promise<void> {\n if (from > to) return;\n\n let cursor = from;\n while (cursor <= to) {\n const chunkEnd =\n cursor + this.batchSize - 1n > to ? to : cursor + this.batchSize - 1n;\n\n const events = this.mintFeeWrapperAddress\n ? await this.fetchWrapperMintEvents(cursor, chunkEnd)\n : await this.fetchTransferMintEvents(cursor, chunkEnd);\n\n // Process in deterministic order so txHash matches the first lock we\n // pick when multiple mints land in the same block.\n events.sort((a, b) => {\n if (a.blockNumber !== b.blockNumber) {\n return a.blockNumber < b.blockNumber ? -1 : 1;\n }\n return a.logIndex - b.logIndex;\n });\n\n for (const evt of events) {\n await this.finalize(evt);\n }\n\n await this.cursorStore.save(chunkEnd + 1n);\n cursor = chunkEnd + 1n;\n }\n }\n\n // -------------------------------------------------------------------------\n // Event fetching — two modes (wrapper vs direct)\n // -------------------------------------------------------------------------\n\n /**\n * Wrapper mode (v1.6): listen for `MintWithFee` on the wrapper,\n * filtered to events for THIS pointToken only. The event's `to` field\n * is the actual end user, and `grossAmount` matches the lock amount.\n */\n private async fetchWrapperMintEvents(\n fromBlock: bigint,\n toBlock: bigint,\n ): Promise<MintEvent[]> {\n const logs = await this.provider.getLogs({\n address: this.mintFeeWrapperAddress!,\n event: MINT_WITH_FEE_EVENT,\n args: { pointToken: this.pointTokenAddress },\n fromBlock,\n toBlock,\n });\n\n const out: MintEvent[] = [];\n for (const log of logs) {\n const args = log.args;\n if (\n !args.pointToken ||\n !args.to ||\n args.grossAmount === undefined ||\n log.blockNumber === null ||\n log.transactionHash === null\n ) {\n continue;\n }\n // Defensive: provider may ignore the indexed filter.\n if (getAddress(args.pointToken) !== this.pointTokenAddress) continue;\n out.push({\n to: getAddress(args.to),\n amount: args.grossAmount,\n blockNumber: log.blockNumber,\n txHash: log.transactionHash,\n logIndex: log.logIndex ?? 0,\n });\n }\n return out;\n }\n\n /**\n * Direct mode (legacy / chains without wrapper): listen for\n * `Transfer(from=0x0 → to)` on the PointToken itself.\n */\n private async fetchTransferMintEvents(\n fromBlock: bigint,\n toBlock: bigint,\n ): Promise<MintEvent[]> {\n const logs = await this.provider.getLogs({\n address: this.pointTokenAddress,\n event: TRANSFER_EVENT,\n args: { from: ZERO_ADDRESS },\n fromBlock,\n toBlock,\n });\n\n return this.decodeTransferMintEvents(logs);\n }\n\n private decodeTransferMintEvents(\n logs: Log<bigint, number, false, typeof TRANSFER_EVENT>[],\n ): MintEvent[] {\n const out: MintEvent[] = [];\n for (const log of logs) {\n // Filter narrowed on `from=0x0` via getLogs `args`, but be defensive —\n // a provider may ignore the filter.\n const args = log.args;\n if (!args.from || !args.to || args.value === undefined) continue;\n if (getAddress(args.from) !== ZERO_ADDRESS) continue;\n if (log.blockNumber === null || log.transactionHash === null) continue;\n out.push({\n to: getAddress(args.to),\n amount: args.value,\n blockNumber: log.blockNumber,\n txHash: log.transactionHash,\n logIndex: log.logIndex ?? 0,\n });\n }\n return out;\n }\n\n // -------------------------------------------------------------------------\n // Finalization\n // -------------------------------------------------------------------------\n\n /**\n * Finalize a single mint event: match it to a PENDING lock in the\n * ledger, then call `deductBalance` (which also resolves the lock in\n * the default `MemoryPointLedger`).\n *\n * No-matching-lock is a valid state: it means either the lock already\n * expired, or the mint was authorized out-of-band (e.g. a direct\n * `PointToken.mint()` from an EOA minter for testing). In that case we\n * do NOT touch the ledger — crediting here would silently allow the\n * issuer to mint without going through the gateway.\n */\n private async finalize(evt: MintEvent): Promise<void> {\n const locks = await this.ledger.getLockedRequests(\n evt.to,\n this.pointTokenAddress,\n );\n const match = pickMatchingLock(locks, evt.amount);\n if (!match) return;\n\n try {\n await this.ledger.deductBalance(\n evt.to,\n evt.amount,\n evt.txHash,\n this.pointTokenAddress,\n );\n } catch {\n // If deduct fails (e.g. race with manual edit), leave the lock and\n // let the caller reconcile. We intentionally do not retry here —\n // the next indexer tick will re-encounter the same event and try\n // again until the cursor is advanced.\n return;\n }\n\n // Best-effort MINTED status update. Already resolved by the default\n // `MemoryPointLedger` via `deductBalance`, but custom ledgers may\n // need the explicit call.\n try {\n await this.ledger.updateMintStatus(match.lockId, \"MINTED\", evt.txHash);\n } catch {\n // Swallow — deductBalance already succeeded, which is the source\n // of truth for the user's balance.\n }\n }\n}\n\n/**\n * Pick the oldest PENDING lock whose amount matches the mint event. The\n * \"oldest\" tie-breaker makes behavior deterministic when a user has\n * multiple concurrent mints of the same size.\n */\nfunction pickMatchingLock(\n locks: LockedMintRequest[],\n amount: bigint,\n): LockedMintRequest | undefined {\n let best: LockedMintRequest | undefined;\n for (const lock of locks) {\n if (lock.status !== \"PENDING\") continue;\n if (lock.amount !== amount) continue;\n if (!best || lock.createdAt < best.createdAt) {\n best = lock;\n }\n }\n return best;\n}\n","import { getAddress, parseAbiItem } from \"viem\";\nimport type { Address, Log, PublicClient } from \"viem\";\nimport type { IPointLedger } from \"../ledger/types\";\nimport type { BurnEvent, IIndexerCursorStore } from \"./types\";\nimport { InMemoryCursorStore } from \"./types\";\n\nexport interface BurnIndexerConfig {\n provider: PublicClient;\n pointTokenAddress: Address;\n ledger: IPointLedger;\n /** Block to start from on first run. Ignored if cursor store has value. */\n fromBlock?: bigint;\n cursorStore?: IIndexerCursorStore;\n /**\n * Reorg safety — only treat events as final after this many\n * confirmations. Default: 3.\n */\n confirmations?: number;\n /** Blocks per getLogs call. Default: 2000. */\n batchSize?: number;\n /** Polling interval (ms). Default: 5000. */\n pollIntervalMs?: number;\n now?: () => number;\n /**\n * Map a burn event to the pending credit lockId that should be resolved.\n * Return `undefined` to skip this burn event (no credit granted).\n *\n * REQUIRED — there is no default implementation. Issuers with a Postgres\n * ledger typically JOIN on `(from, amount, status=PENDING)`. The in-memory\n * ledger uses a lookup by lockId supplied out-of-band from the claim flow.\n */\n matchLockId: (event: BurnEvent) => Promise<string | undefined>;\n /**\n * Observability hook — see PointIndexerConfig.onTickError for full\n * rationale. Critical for burn indexer too: silent failure means\n * pending credits never resolve, user reports \"I burned my PT but\n * never got my off-chain credit\".\n */\n onTickError?: (err: unknown) => void;\n}\n\nconst TRANSFER_EVENT = parseAbiItem(\n \"event Transfer(address indexed from, address indexed to, uint256 value)\",\n);\n\nconst ZERO_ADDRESS: Address = \"0x0000000000000000000000000000000000000000\";\n\nconst DEFAULT_CONFIRMATIONS = 3;\nconst DEFAULT_BATCH_SIZE = 2000n;\nconst DEFAULT_POLL_INTERVAL_MS = 5000;\n\n/**\n * Mirror of `PointIndexer` for the reverse direction — watches\n * `Transfer(user → 0x0)` events (ERC-20 burns) on the PointToken\n * contract and finalizes pending off-chain credits.\n *\n * Finalization flow:\n * 1. For each Burn event at `{from, amount, txHash}`:\n * 2. Call `ledger.resolveCreditByBurnTx(lockId, txHash)` where `lockId`\n * is resolved by the caller's `onMatchCredit` hook or a\n * ledger-specific lookup. The SDK does not prescribe the matching\n * strategy — issuers with a Postgres ledger can JOIN by\n * `(from, amount, status=PENDING)`; the in-memory ledger matches\n * by `lockId` supplied out-of-band.\n *\n * When no pending credit matches an observed Burn event, the indexer\n * logs + skips — **it never credits off-chain** from a Burn that was\n * not first reserved via `reservePendingCredit()`. This prevents\n * spurious credits from one-off admin burns or direct burn calls\n * outside the issuer SDK.\n */\nexport class BurnIndexer {\n private readonly provider: PublicClient;\n private readonly pointTokenAddress: Address;\n private readonly ledger: IPointLedger;\n private readonly cursorStore: IIndexerCursorStore;\n private readonly startBlock: bigint;\n private readonly confirmations: bigint;\n private readonly batchSize: bigint;\n private readonly pollIntervalMs: number;\n private readonly onTickError?: (err: unknown) => void;\n\n matchLockId: (event: BurnEvent) => Promise<string | undefined>;\n\n private running = false;\n private timer: ReturnType<typeof setTimeout> | undefined;\n\n constructor(config: BurnIndexerConfig) {\n if (!config.provider) throw new Error(\"BurnIndexer: provider required\");\n if (!config.pointTokenAddress)\n throw new Error(\"BurnIndexer: pointTokenAddress required\");\n if (!config.ledger) throw new Error(\"BurnIndexer: ledger required\");\n\n this.provider = config.provider;\n this.pointTokenAddress = config.pointTokenAddress;\n this.ledger = config.ledger;\n this.cursorStore = config.cursorStore ?? new InMemoryCursorStore();\n this.startBlock = config.fromBlock ?? 0n;\n this.confirmations = BigInt(\n config.confirmations ?? DEFAULT_CONFIRMATIONS,\n );\n this.batchSize = BigInt(config.batchSize ?? Number(DEFAULT_BATCH_SIZE));\n this.pollIntervalMs = config.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n if (config.onTickError) this.onTickError = config.onTickError;\n\n if (!config.matchLockId) {\n throw new Error(\n \"BurnIndexer: matchLockId is required. \" +\n \"Provide a function that maps a burn event to its pending credit lockId. \" +\n \"Without it, no on-chain burns will ever grant off-chain credits.\",\n );\n }\n this.matchLockId = config.matchLockId;\n }\n\n start(): void {\n if (this.running) return;\n this.running = true;\n // First-tick safety: any unhandled rejection (vs `void this.tick()`)\n // routes to onTickError instead of disappearing as an unhandled rejection.\n this.tick().catch((err) => this.handleTickError(err));\n }\n\n stop(): void {\n this.running = false;\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = undefined;\n }\n }\n\n async tick(): Promise<void> {\n if (!this.running) return;\n try {\n const latest = await this.provider.getBlockNumber();\n const safeHead = latest - this.confirmations;\n if (safeHead < 0n) {\n this.scheduleNext();\n return;\n }\n\n const stored = await this.cursorStore.load();\n const from = stored ?? this.startBlock;\n if (from > safeHead) {\n this.scheduleNext();\n return;\n }\n\n await this.processBlockRange(from, safeHead);\n } catch (err) {\n this.handleTickError(err);\n }\n this.scheduleNext();\n }\n\n private handleTickError(err: unknown): void {\n if (this.onTickError) {\n try {\n this.onTickError(err);\n } catch {\n // eslint-disable-next-line no-console\n console.error(\"[PAFI] BurnIndexer onTickError threw:\", err);\n }\n } else {\n // eslint-disable-next-line no-console\n console.error(\"[PAFI] BurnIndexer tick error:\", err);\n }\n }\n\n private scheduleNext(): void {\n if (!this.running) return;\n this.timer = setTimeout(\n () => this.tick().catch((err) => this.handleTickError(err)),\n this.pollIntervalMs,\n );\n }\n\n /**\n * Scan `[from, to]` inclusive for burn events. Callers can drive this\n * directly to backfill a specific range without `start()`. Cursor is\n * advanced to `to + 1` on completion.\n */\n async processBlockRange(from: bigint, to: bigint): Promise<void> {\n if (from > to) return;\n\n let cursor = from;\n while (cursor <= to) {\n const chunkEnd =\n cursor + this.batchSize - 1n > to ? to : cursor + this.batchSize - 1n;\n\n const logs = await this.provider.getLogs({\n address: this.pointTokenAddress,\n event: TRANSFER_EVENT,\n args: { to: ZERO_ADDRESS }, // filter: burn = transfer to zero\n fromBlock: cursor,\n toBlock: chunkEnd,\n });\n\n const events = this.decodeBurnEvents(logs);\n events.sort((a, b) => {\n if (a.blockNumber !== b.blockNumber) {\n return a.blockNumber < b.blockNumber ? -1 : 1;\n }\n return a.logIndex - b.logIndex;\n });\n\n for (const evt of events) {\n await this.finalize(evt);\n }\n\n await this.cursorStore.save(chunkEnd + 1n);\n cursor = chunkEnd + 1n;\n }\n }\n\n private decodeBurnEvents(\n logs: Log<bigint, number, false, typeof TRANSFER_EVENT>[],\n ): BurnEvent[] {\n const out: BurnEvent[] = [];\n for (const log of logs) {\n const args = log.args;\n if (!args.from || !args.to || args.value === undefined) continue;\n if (getAddress(args.to) !== ZERO_ADDRESS) continue;\n if (log.blockNumber === null || log.transactionHash === null) continue;\n out.push({\n from: getAddress(args.from),\n amount: args.value,\n blockNumber: log.blockNumber,\n txHash: log.transactionHash,\n logIndex: log.logIndex ?? 0,\n });\n }\n return out;\n }\n\n /**\n * Resolve a matching pending credit for this burn event and call\n * `ledger.resolveCreditByBurnTx(lockId, txHash)`. If no match found,\n * log + skip.\n */\n private async finalize(evt: BurnEvent): Promise<void> {\n const txHash = evt.txHash;\n const lockId = await this.matchLockId(evt);\n if (lockId === undefined) {\n console.warn(\n '[PAFI] BurnIndexer: matchLockId returned undefined for burn tx ' + txHash +\n '. This burn will NOT be credited. Implement matchLockId to map burn events to lock IDs.'\n );\n return;\n }\n\n if (!this.ledger.resolveCreditByBurnTx) {\n // Legacy ledger doesn't support reverse flow; skip.\n return;\n }\n\n try {\n await this.ledger.resolveCreditByBurnTx(lockId, evt.txHash);\n } catch (err) {\n console.error('[PAFI] BurnIndexer finalize error — credit may be lost:', err);\n }\n }\n}\n","import { getAddress } from \"viem\";\nimport type { Address, PublicClient } from \"viem\";\nimport {\n getMintFeeBps,\n getMintRequestNonce,\n getPointTokenBalance,\n getReceiverConsentNonce,\n isMinter,\n} from \"@pafi-dev/core\";\nimport type { AuthService } from \"../auth/loginVerifier\";\nimport {\n NoopRateLimiter,\n type IRateLimiter,\n} from \"../auth/rateLimiter\";\nimport type { IPointLedger } from \"../ledger/types\";\nimport type { FeeManager } from \"../relay/feeManager\";\nimport type { RedemptionService } from \"../redemption/service\";\nimport { ConfigurationError, ValidationError } from \"../errors\";\nimport type {\n ApiConfigResponse,\n ApiGasFeeResponse,\n ApiLoginRequest,\n ApiLoginResponse,\n ApiNonceResponse,\n ApiPoolsRequest,\n ApiPoolsResponse,\n ApiRedemptionEvaluateRequest,\n ApiRedemptionEvaluateResponse,\n ApiRedemptionPreviewRequest,\n ApiRedemptionPreviewResponse,\n ApiUserRequest,\n ApiUserResponse,\n PoolsProvider,\n} from \"./types\";\n\nexport interface IssuerApiHandlersConfig {\n // -- Auth ---------------------------------------------------------------\n authService: AuthService;\n\n // -- Data sources -------------------------------------------------------\n ledger: IPointLedger;\n /** Used by `handleUser` to read on-chain nonces and minter status. */\n provider: PublicClient;\n\n // -- Point token addresses ---------------------------------------------\n /**\n * Legacy single-token config. Prefer `pointTokenAddresses` for multi-token\n * issuers (0.2.0+).\n */\n pointTokenAddress?: Address;\n /**\n * All supported PointToken addresses. Handlers accept any address in this\n * list; others are rejected with \"unsupported pointToken\".\n */\n pointTokenAddresses?: Address[];\n\n // -- Chain config (surfaced verbatim by `handleConfig`) ----------------\n chainId: number;\n contracts: ApiConfigResponse[\"contracts\"];\n /**\n * Optional — URL that the Issuer App opens for PT→USDT swap or perp\n * deposit after a successful claim. Surfaced in `/config` response.\n * See [MOBILE_SDK_INTEGRATION.md] \"PAFI Web Handoff\".\n */\n pafiWebUrl?: string;\n /**\n * v1.6 — MintFeeWrapper address used to read per-PointToken fee\n * basis points for `/config`. When omitted, the response will not\n * include `mintFeeBpsByToken` and FE must assume zero fee. Wrapper\n * is shared across PointTokens; per-token recipients live inside it.\n */\n mintFeeWrapperAddress?: Address;\n\n // -- Optional / pluggable -----------------------------------------------\n /** Required by `handleGasFee`; omit to disable the endpoint. */\n feeManager?: FeeManager;\n /** Required by `handlePools`; omit to disable the endpoint. */\n poolsProvider?: PoolsProvider;\n /**\n * Required by `handleRedemptionPreview` / `handleRedemptionEvaluate`;\n * omit to disable both. Wired by createIssuerService when the top-level\n * `redemption` config is provided.\n */\n redemption?: RedemptionService;\n /**\n * Rate limiter for `/auth/nonce` and `/auth/login` (both unauthenticated +\n * CPU-bound). When omitted, defaults to a `NoopRateLimiter` that lets\n * every request through (with a one-time prod warning) — issuers MUST\n * wire a real impl in production.\n */\n rateLimiter?: IRateLimiter;\n}\n\n/**\n * Framework-agnostic HTTP handlers that match the endpoints a `PafiSDK`\n * frontend expects to call. Issuers wrap these in Express / Fastify /\n * Hono / whatever — the handlers take plain inputs and return plain\n * outputs, with `AuthError` surfaced as typed exceptions.\n *\n * Every protected handler takes a pre-verified `userAddress` as its first\n * argument. The issuer's middleware wraps `authenticateRequest()` from\n * `@pafi/issuer` to extract that address from the Bearer token before\n * routing.\n */\nexport class IssuerApiHandlers {\n private readonly authService: AuthService;\n private readonly ledger: IPointLedger;\n private readonly provider: PublicClient;\n /**\n * Set of supported PointToken addresses (checksum-normalized). Handlers\n * validate the request's `pointTokenAddress` against this set.\n */\n private readonly supportedTokens: Set<Address>;\n private readonly chainId: number;\n private readonly contracts: ApiConfigResponse[\"contracts\"];\n private readonly pafiWebUrl?: string;\n private readonly feeManager?: FeeManager;\n private readonly poolsProvider?: PoolsProvider;\n private readonly redemption?: RedemptionService;\n private readonly rateLimiter: IRateLimiter;\n private readonly mintFeeWrapperAddress?: Address;\n /**\n * Per-token feeBps cache. Refreshed on /config when stale. feeBps\n * changes only when ops calls `wrapper.setRecipients`, so 5-min TTL\n * is more than safe for FE display purposes.\n */\n private readonly feeBpsCache = new Map<\n Address,\n { value: number; expiresAt: number }\n >();\n private static readonly FEE_BPS_TTL_MS = 5 * 60 * 1000;\n\n constructor(config: IssuerApiHandlersConfig) {\n this.authService = config.authService;\n this.ledger = config.ledger;\n this.provider = config.provider;\n this.rateLimiter = config.rateLimiter ?? new NoopRateLimiter();\n\n const raw =\n config.pointTokenAddresses && config.pointTokenAddresses.length > 0\n ? config.pointTokenAddresses\n : config.pointTokenAddress\n ? [config.pointTokenAddress]\n : [];\n if (raw.length === 0) {\n // Constructor-time misconfiguration — surface as a plain Error\n // so issuer's bootstrap code fails immediately. Don't wrap in\n // PafiSdkError because there is no controller to map this to;\n // boot-time errors should crash the process.\n throw new Error(\n \"IssuerApiHandlers: pointTokenAddress or pointTokenAddresses required\",\n );\n }\n const normalized = raw.map((a) => getAddress(a));\n this.supportedTokens = new Set(normalized);\n\n this.chainId = config.chainId;\n this.contracts = config.contracts;\n if (config.pafiWebUrl) this.pafiWebUrl = config.pafiWebUrl;\n if (config.feeManager) this.feeManager = config.feeManager;\n if (config.poolsProvider) this.poolsProvider = config.poolsProvider;\n if (config.redemption) this.redemption = config.redemption;\n if (config.mintFeeWrapperAddress) {\n this.mintFeeWrapperAddress = getAddress(config.mintFeeWrapperAddress);\n }\n }\n\n // =========================================================================\n // Public handlers (no auth required)\n // =========================================================================\n\n /**\n * `GET /auth/nonce`\n *\n * @param rateLimitKey Caller-side rate-limit key (typically client IP).\n * The HTTP layer (controller/middleware) extracts\n * this from the request and passes it through.\n * When omitted, no rate limit applies — production\n * callers SHOULD always pass a key.\n */\n async handleGetNonce(rateLimitKey?: string): Promise<ApiNonceResponse> {\n if (rateLimitKey) {\n const result = await this.rateLimiter.consume(\n rateLimitKey,\n \"auth_nonce\",\n );\n if (!result.allowed) {\n throw new ValidationError(\n \"RATE_LIMIT_EXCEEDED\",\n \"handleGetNonce: too many requests\",\n {\n retryAfterMs: result.retryAfterMs ?? 0,\n action: \"auth_nonce\",\n },\n );\n }\n }\n const nonce = await this.authService.getNonce();\n return { nonce };\n }\n\n /**\n * `POST /auth/login`\n *\n * @param body Login message + signature.\n * @param rateLimitKey Caller-side rate-limit key (typically client IP\n * or `body.userAddress` if known). See `handleGetNonce`.\n */\n async handleLogin(\n body: ApiLoginRequest,\n rateLimitKey?: string,\n ): Promise<ApiLoginResponse> {\n // Rate-limit BEFORE field validation: even malformed bodies that fail\n // shape checks consume CPU on parsing; throttle attackers that spam\n // such requests.\n if (rateLimitKey) {\n const result = await this.rateLimiter.consume(\n rateLimitKey,\n \"auth_login\",\n );\n if (!result.allowed) {\n throw new ValidationError(\n \"RATE_LIMIT_EXCEEDED\",\n \"handleLogin: too many requests\",\n {\n retryAfterMs: result.retryAfterMs ?? 0,\n action: \"auth_login\",\n },\n );\n }\n }\n if (\n !body ||\n typeof body.message !== \"string\" ||\n body.message.length === 0 ||\n typeof body.signature !== \"string\" ||\n body.signature.length <= 2\n ) {\n throw new ValidationError(\n \"INVALID_LOGIN_BODY\",\n \"handleLogin: message and signature are required\",\n );\n }\n if (body.message.length > 4096) {\n throw new ValidationError(\"MESSAGE_TOO_LONG\", \"message too long\");\n }\n if (body.signature.length > 260) {\n throw new ValidationError(\"SIGNATURE_TOO_LONG\", \"signature too long\");\n }\n const result = await this.authService.login(body.message, body.signature);\n return {\n token: result.token,\n userAddress: result.userAddress,\n expiresAt: result.expiresAt.getTime(),\n };\n }\n\n /**\n * `GET /config?chainId=<id>`\n *\n * Returns the contract addresses and chain id that the frontend SDK\n * needs to build EIP-712 messages and interact with on-chain.\n */\n async handleConfig(chainId: number): Promise<ApiConfigResponse> {\n if (!Number.isInteger(chainId) || chainId <= 0) {\n throw new ValidationError(\"INVALID_CHAIN_ID\", \"invalid chainId\", {\n chainId,\n });\n }\n if (chainId !== this.chainId) {\n throw new ValidationError(\n \"UNSUPPORTED_CHAIN_ID\",\n `handleConfig: unsupported chainId ${chainId}`,\n { requested: chainId, supported: this.chainId },\n );\n }\n const contracts: ApiConfigResponse[\"contracts\"] = {\n ...this.contracts,\n pointTokens: Array.from(this.supportedTokens),\n };\n if (this.mintFeeWrapperAddress) {\n contracts.mintFeeWrapper = this.mintFeeWrapperAddress;\n }\n const response: ApiConfigResponse = {\n chainId: this.chainId,\n contracts,\n };\n if (this.mintFeeWrapperAddress) {\n const byToken = await this.resolveFeeBpsByToken(\n this.mintFeeWrapperAddress,\n );\n if (byToken) response.mintFeeBpsByToken = byToken;\n }\n if (this.pafiWebUrl) response.pafiWebUrl = this.pafiWebUrl;\n return response;\n }\n\n /**\n * Read `totalFeeBps(pointToken)` for every supported PointToken from\n * the wrapper. Cached per-token for 5 minutes. Returns `undefined`\n * (caller drops the field) if every read fails — FE must treat\n * \"field missing\" as \"fee unknown, do not display\".\n */\n private async resolveFeeBpsByToken(\n wrapper: Address,\n ): Promise<Record<string, number> | undefined> {\n const now = Date.now();\n const out: Record<string, number> = {};\n let anyFresh = false;\n await Promise.all(\n Array.from(this.supportedTokens).map(async (token) => {\n const cached = this.feeBpsCache.get(token);\n if (cached && cached.expiresAt > now) {\n out[token.toLowerCase()] = cached.value;\n anyFresh = true;\n return;\n }\n try {\n const bps = await getMintFeeBps(this.provider, wrapper, token);\n this.feeBpsCache.set(token, {\n value: bps,\n expiresAt: now + IssuerApiHandlers.FEE_BPS_TTL_MS,\n });\n out[token.toLowerCase()] = bps;\n anyFresh = true;\n } catch {\n // RPC failure — fall back to last cached value if we have one\n // (better stale than missing). Drop the entry only when we\n // have nothing.\n if (cached) {\n out[token.toLowerCase()] = cached.value;\n anyFresh = true;\n }\n }\n }),\n );\n return anyFresh ? out : undefined;\n }\n\n /** `GET /gas-fee` — quoted in USDT (6-decimal base units). */\n async handleGasFee(): Promise<ApiGasFeeResponse> {\n if (!this.feeManager) {\n throw new ConfigurationError(\n \"FEE_MANAGER_NOT_CONFIGURED\",\n \"handleGasFee: feeManager is not configured on this issuer\",\n );\n }\n const gasFeeUsdt = await this.feeManager.estimateGasFee();\n return { gasFeeUsdt };\n }\n\n // =========================================================================\n // Protected handlers (JWT required — userAddress extracted by middleware)\n // =========================================================================\n\n /** `POST /auth/logout` */\n async handleLogout(token: string): Promise<void> {\n await this.authService.logout(token);\n }\n\n /**\n * `GET /pools?chainId=<id>&pointToken=<addr>`\n *\n * Delegates to the injected `PoolsProvider`. The handler itself does\n * not know where pools come from — that's an issuer decision.\n */\n async handlePools(\n _userAddress: Address,\n request: ApiPoolsRequest,\n ): Promise<ApiPoolsResponse> {\n if (!this.poolsProvider) {\n throw new ConfigurationError(\n \"POOLS_PROVIDER_NOT_CONFIGURED\",\n \"handlePools: poolsProvider is not configured on this issuer\",\n );\n }\n if (request.chainId !== this.chainId) {\n throw new ValidationError(\n \"UNSUPPORTED_CHAIN_ID\",\n `handlePools: unsupported chainId ${request.chainId}`,\n { requested: request.chainId, supported: this.chainId },\n );\n }\n return this.poolsProvider(request);\n }\n\n /**\n * `GET /user?chainId=<id>&user=<addr>&pointToken=<addr>`\n *\n * Returns per-user state the frontend needs to build a fresh\n * `ReceiverConsent`: on-chain nonces + minter status + off-chain\n * balance.\n */\n async handleUser(\n userAddress: Address,\n request: ApiUserRequest,\n ): Promise<ApiUserResponse> {\n if (request.chainId !== this.chainId) {\n throw new ValidationError(\n \"UNSUPPORTED_CHAIN_ID\",\n `handleUser: unsupported chainId ${request.chainId}`,\n { requested: request.chainId, supported: this.chainId },\n );\n }\n const normalizedAuthed = getAddress(userAddress);\n const normalizedRequest = getAddress(request.userAddress);\n if (normalizedAuthed !== normalizedRequest) {\n throw new ValidationError(\n \"USER_ADDRESS_MISMATCH\",\n \"handleUser: request userAddress must match authenticated user\",\n { authenticated: normalizedAuthed, requested: normalizedRequest },\n );\n }\n const pointToken = getAddress(request.pointTokenAddress);\n if (!this.supportedTokens.has(pointToken)) {\n throw new ValidationError(\n \"UNSUPPORTED_POINT_TOKEN\",\n `handleUser: unsupported pointToken ${pointToken}`,\n { requested: pointToken },\n );\n }\n\n const [mintRequestNonce, receiverConsentNonce, offChainBalance, onChainBalance, minter] =\n await Promise.all([\n getMintRequestNonce(this.provider, pointToken, normalizedAuthed),\n getReceiverConsentNonce(this.provider, pointToken, normalizedAuthed),\n this.ledger.getBalance(normalizedAuthed, pointToken),\n getPointTokenBalance(this.provider, pointToken, normalizedAuthed),\n isMinter(this.provider, pointToken, normalizedAuthed),\n ]);\n\n return {\n mintRequestNonce,\n receiverConsentNonce,\n offChainBalance,\n onChainBalance,\n totalBalance: offChainBalance + onChainBalance,\n balance: offChainBalance, // deprecated alias\n isMinter: minter,\n };\n }\n\n // Note: legacy `handleClaim` (sync sponsored-claim returning calls[]) was\n // removed in 0.5.43 — callers should use `PTClaimHandler` directly or\n // wire `IssuerApiAdapter.claim()` which composes the full flow.\n\n /**\n * `GET /redemption/preview?pointToken=<addr>`\n *\n * Returns the headroom currently available to `userAddress` under the\n * configured RedemptionPolicy. Pure read — does not record anything.\n * Use this for UI to render \"X PT redeemable now / next available at …\".\n */\n async handleRedemptionPreview(\n userAddress: Address,\n request: ApiRedemptionPreviewRequest,\n ): Promise<ApiRedemptionPreviewResponse> {\n if (!this.redemption) {\n throw new ConfigurationError(\n \"REDEMPTION_NOT_CONFIGURED\",\n \"handleRedemptionPreview: redemption is not configured on this issuer\",\n );\n }\n const tokenAddress = request.pointTokenAddress\n ? this.requireSupportedToken(getAddress(request.pointTokenAddress), \"handleRedemptionPreview\")\n : undefined;\n const preview = await this.redemption.preview(\n getAddress(userAddress),\n tokenAddress,\n );\n return preview;\n }\n\n /**\n * `POST /redemption/evaluate`\n *\n * Pre-flight check before the issuer signs a BurnRequest. Returns\n * { allowed, denial?, preview }. Caller (the burn-orchestrator) MUST\n * re-check on the actual initiate path — evaluate is read-only and a\n * caller could race two requests under the same headroom. The intended\n * write path is: evaluate → sign BurnRequest → reserve pending credit\n * → call `service.redemption.recordSuccessfulInitiate()`.\n */\n async handleRedemptionEvaluate(\n userAddress: Address,\n request: ApiRedemptionEvaluateRequest,\n ): Promise<ApiRedemptionEvaluateResponse> {\n if (!this.redemption) {\n throw new ConfigurationError(\n \"REDEMPTION_NOT_CONFIGURED\",\n \"handleRedemptionEvaluate: redemption is not configured on this issuer\",\n );\n }\n if (request.amountPt <= 0n) {\n throw new ValidationError(\n \"INVALID_AMOUNT\",\n \"handleRedemptionEvaluate: amountPt must be positive\",\n { amountPt: request.amountPt.toString() },\n );\n }\n const tokenAddress = request.pointTokenAddress\n ? this.requireSupportedToken(getAddress(request.pointTokenAddress), \"handleRedemptionEvaluate\")\n : undefined;\n const decision = await this.redemption.evaluate(\n getAddress(userAddress),\n request.amountPt,\n tokenAddress,\n );\n const response: ApiRedemptionEvaluateResponse = {\n allowed: decision.allowed,\n preview: decision.preview,\n };\n if (decision.denial) response.denial = decision.denial;\n return response;\n }\n\n private requireSupportedToken(\n pointToken: Address,\n handler: string,\n ): Address {\n if (!this.supportedTokens.has(pointToken)) {\n throw new ValidationError(\n \"UNSUPPORTED_POINT_TOKEN\",\n `${handler}: unsupported pointToken ${pointToken}`,\n { requested: pointToken },\n );\n }\n return pointToken;\n }\n}\n","import type { Address, Hex, PublicClient, WalletClient } from \"viem\";\nimport { getAddress } from \"viem\";\nimport type {\n BurnRequest,\n PartialUserOperation,\n PointTokenDomainConfig,\n} from \"@pafi-dev/core\";\nimport {\n signBurnRequest,\n POINT_TOKEN_V2_ABI,\n getPointTokenBalance,\n getContractAddresses,\n} from \"@pafi-dev/core\";\nimport type { IPointLedger } from \"../../ledger/types\";\nimport type { RelayService } from \"../../relay/relayService\";\nimport type { FeeManager } from \"../../relay/feeManager\";\nimport type { RedemptionService } from \"../../redemption/service\";\nimport type { RedemptionDenialCode } from \"@pafi-dev/core\";\nimport { PafiSdkError } from \"../../errors\";\n\n/**\n * v1.4 reverse flow — user-initiated PT redeem.\n *\n * User has on-chain PT, wants to convert back to off-chain points. The\n * issuer backend signs a `BurnRequest` with its burner signer, reserves\n * an off-chain pending credit, and returns an unsigned UserOp. The FE\n * submits the UserOp via EIP-7702 + PAFI sponsor-relayer. On confirmation,\n * `Transfer(user → 0x0)` is emitted; `BurnIndexer` resolves the pending\n * credit to a real off-chain credit.\n *\n * Contract path (mock ABI — matches deployed PointToken):\n *\n * burn(address from, uint256 amount, uint256 deadline, bytes burnerSig)\n *\n * On-chain checks:\n * - msg.sender == from (enforced via EIP-7702 delegation on user EOA)\n * - burnerSig signer ∈ burners[]\n * - nonce == burnRequestNonces[from]\n * - now <= deadline\n *\n * The user never signs an EIP-712 message in this flow. Their only\n * signature is the ERC-4337 UserOp signature, which the AA wallet\n * handles. Consent is implicit: by submitting the UserOp they authorize\n * the burn.\n */\nexport interface PTRedeemHandlerConfig {\n ledger: IPointLedger;\n relayService: RelayService;\n provider: PublicClient;\n\n /** PointToken contract address (chain-specific). */\n pointTokenAddress: Address;\n /** BatchExecutor delegation target (chain-specific). */\n batchExecutorAddress: Address;\n\n /** Chain id — used for the BurnRequest EIP-712 domain. */\n chainId: number;\n /**\n * EIP-712 domain fields. Must match the on-chain PointToken's domain\n * separator, or on-chain signature recovery fails. `name` is\n * typically the PointToken's ERC-20 name. `verifyingContract`\n * defaults to `pointTokenAddress`.\n */\n domain: {\n name: string;\n verifyingContract?: Address;\n };\n\n /**\n * Issuer burner signer wallet — signs the `BurnRequest` EIP-712.\n * Must be whitelisted via `PointToken.addBurner(signerAddr)` at\n * provisioning time. Typically HSM/KMS-backed in prod.\n */\n burnerSignerWallet: WalletClient;\n\n /**\n * Optional — when wired, used to estimate the PT gas-reimbursement\n * fee. The handler self-computes `feeAmount` + `feeRecipient` so the\n * caller doesn't repeat the boilerplate at each endpoint. Pass\n * `feeAmount` on `PTRedeemRequest` to override.\n */\n feeService?: FeeManager;\n\n /**\n * How long the pending credit stays reserved if the burn never lands.\n * Default: 15 min — long enough for a bundler submission + confirmation.\n */\n redeemLockDurationMs?: number;\n\n /**\n * How far ahead of `now` to set the BurnRequest deadline. Default:\n * 15 min. Must be long enough for Bundler + EntryPoint to execute;\n * short enough to prevent replay if the UserOp is abandoned.\n */\n signatureDeadlineSeconds?: number;\n\n /** Clock injection for tests; defaults to `Date.now`. */\n now?: () => number;\n\n /**\n * Optional — when wired, the handler enforces the per-issuer\n * RedemptionPolicy (daily limit / cooldown / blackout / per-tx range)\n * BEFORE signing the BurnRequest. On denial it throws\n * PTRedeemError(\"REDEMPTION_POLICY_DENIED\", ...) with the policy\n * denial code in `policyDenialCode`. After a successful sign+reserve,\n * the handler records the redemption against the user's history so\n * the next preview reflects the spend.\n */\n redemptionService?: RedemptionService;\n}\n\nexport interface PTRedeemRequest {\n /** Address extracted from the verified JWT — must match `userAddress`. */\n authenticatedAddress: Address;\n userAddress: Address;\n amount: bigint;\n /** ERC-4337 account nonce for the user's EOA. */\n aaNonce: bigint;\n /**\n * Optional override — explicit PT fee transfer appended after the\n * burn (sponsored path). When omitted, the handler auto-computes\n * via `feeService.estimateGasFee()` (if configured) and resolves\n * the recipient from `getContractAddresses(chainId).pafiFeeRecipient`.\n */\n feeAmount?: bigint;\n feeRecipient?: Address;\n /**\n * Optional — required only when the handler self-computes the\n * recipient. When the caller passes an explicit `feeRecipient`, this\n * is ignored.\n */\n chainId?: number;\n}\n\nexport interface PTRedeemResponse {\n /**\n * Sponsored path — UserOp with PT fee transfer. BurnRequest signed for\n * `request.amount - feeAmount`. User holds exactly `request.amount` PT\n * and the fee comes out of the redeem amount, NOT on top.\n */\n lockId: string;\n userOp: PartialUserOperation;\n /** = request.amount - feeAmount. What BurnIndexer credits off-chain on sponsored fire. */\n netCreditAmount: bigint;\n /**\n * Resolved fee — computed via `feeService.estimateGasFee()` when the\n * caller didn't override on the request, else echoed back from the\n * request override. Useful for the FE confirmation screen.\n */\n feeAmount: bigint;\n\n /**\n * Fallback path — UserOp with NO fee transfer. BurnRequest signed for\n * full `request.amount`. User pays gas in ETH directly. Present only\n * when `feeAmount > 0`.\n *\n * Same nonce as sponsored: only one of the two can fire on-chain;\n * the other lock auto-expires after `expiresInSeconds`. BurnIndexer\n * matches by burn amount and resolves the correct lock.\n */\n fallback?: {\n lockId: string;\n userOp: PartialUserOperation;\n /** = request.amount. Full credit when fallback fires. */\n netCreditAmount: bigint;\n };\n\n /** Seconds until the lock expires if the burn doesn't land. */\n expiresInSeconds: number;\n /** The BurnRequest deadline (unix seconds) — FE uses this to surface a countdown. */\n signatureDeadline: bigint;\n}\n\nconst DEFAULT_REDEEM_LOCK_MS = 15 * 60 * 1000;\nconst DEFAULT_SIG_DEADLINE_SEC = 15 * 60;\n\nexport type PTRedeemErrorCode =\n | \"UNAUTHORIZED\"\n | \"INVALID_AMOUNT\"\n | \"NONCE_READ_FAILED\"\n | \"NONCE_IN_FLIGHT\"\n | \"LEDGER_NOT_SUPPORTED\"\n | \"SIGNING_FAILED\"\n | \"REDEMPTION_POLICY_DENIED\";\n\nexport class PTRedeemError extends PafiSdkError {\n readonly httpStatus = \"unprocessable\" as const;\n readonly code: PTRedeemErrorCode;\n readonly policyDenialCode?: RedemptionDenialCode;\n\n constructor(\n code: PTRedeemErrorCode,\n message: string,\n options?: { policyDenialCode?: RedemptionDenialCode },\n ) {\n super(message);\n this.code = code;\n if (options?.policyDenialCode) {\n this.policyDenialCode = options.policyDenialCode;\n }\n }\n}\n\nexport class PTRedeemHandler {\n private readonly ledger: IPointLedger;\n private readonly relayService: RelayService;\n private readonly provider: PublicClient;\n private readonly feeService?: FeeManager;\n private readonly pointTokenAddress: Address;\n private readonly batchExecutorAddress: Address;\n private readonly chainId: number;\n private readonly domain: PTRedeemHandlerConfig[\"domain\"];\n private readonly burnerSignerWallet: WalletClient;\n private readonly redeemLockDurationMs: number;\n private readonly signatureDeadlineSeconds: number;\n private readonly now: () => number;\n private readonly redemptionService?: RedemptionService;\n\n /**\n * Per-user in-flight nonce guard (single-process only).\n *\n * Prevents two concurrent requests from reading the same on-chain\n * burnRequestNonce before either has completed, which would produce two\n * signed UserOps with the same nonce — only one succeeds on-chain; the\n * other leaves an orphaned pending credit and a wasted signer call.\n *\n * NOTE: This guard is effective only within a single Node.js process. For\n * multi-instance deployments (k8s, PM2 cluster), enforce mutual exclusion\n * via a distributed lock (Redis SETNX / Postgres advisory lock) keyed on\n * `(userAddress, pointTokenAddress)` BEFORE calling `handle()`.\n */\n private readonly inFlightNonces = new Map<string, Set<bigint>>();\n\n constructor(config: PTRedeemHandlerConfig) {\n if (!config.ledger.reservePendingCredit) {\n throw new PTRedeemError(\n \"LEDGER_NOT_SUPPORTED\",\n \"PTRedeemHandler requires a ledger that implements reservePendingCredit() (v0.3.0+)\",\n );\n }\n if (!config.burnerSignerWallet) {\n throw new PTRedeemError(\n \"SIGNING_FAILED\",\n \"PTRedeemHandler requires burnerSignerWallet (issuer burner signer)\",\n );\n }\n this.ledger = config.ledger;\n this.relayService = config.relayService;\n this.provider = config.provider;\n this.feeService = config.feeService;\n this.pointTokenAddress = getAddress(config.pointTokenAddress);\n this.batchExecutorAddress = getAddress(config.batchExecutorAddress);\n this.chainId = config.chainId;\n this.domain = config.domain;\n this.burnerSignerWallet = config.burnerSignerWallet;\n // Warn if WalletClient is backed by a private key account (PrivateKeyAccount type check)\n if (this.burnerSignerWallet?.account?.type === 'local') {\n console.warn('[PAFI] PTRedeemHandler: burnerSignerWallet uses a local (private key) account. Use a KMS-backed signer in production.');\n }\n this.redeemLockDurationMs =\n config.redeemLockDurationMs ?? DEFAULT_REDEEM_LOCK_MS;\n this.signatureDeadlineSeconds =\n config.signatureDeadlineSeconds ?? DEFAULT_SIG_DEADLINE_SEC;\n this.now = config.now ?? (() => Date.now());\n if (config.redemptionService) {\n this.redemptionService = config.redemptionService;\n }\n }\n\n async handle(request: PTRedeemRequest): Promise<PTRedeemResponse> {\n if (getAddress(request.authenticatedAddress) !== getAddress(request.userAddress)) {\n throw new PTRedeemError(\n \"UNAUTHORIZED\",\n `userAddress (${request.userAddress}) does not match authenticated session (${request.authenticatedAddress})`,\n );\n }\n if (request.amount <= 0n) {\n throw new PTRedeemError(\"INVALID_AMOUNT\", \"redeem amount must be positive\");\n }\n\n // Pre-flight RedemptionPolicy check. Done BEFORE any chain read or\n // signer call so a denied request fails fast and doesn't burn an\n // RPC quota or signer slot. Race window vs concurrent requests is\n // bounded by `inFlightNonces` below — two parallel requests that\n // both pass evaluate() under the same headroom can still slip\n // through, but the second one will be rejected as NONCE_IN_FLIGHT.\n if (this.redemptionService) {\n const decision = await this.redemptionService.evaluate(\n request.userAddress,\n request.amount,\n this.pointTokenAddress,\n );\n if (!decision.allowed) {\n const denial = decision.denial!;\n throw new PTRedeemError(\n \"REDEMPTION_POLICY_DENIED\",\n `redemption denied: ${denial.message}`,\n { policyDenialCode: denial.code },\n );\n }\n }\n\n // Read the current burnRequestNonces[user] from-chain.\n let burnNonce: bigint;\n try {\n burnNonce = (await this.provider.readContract({\n address: this.pointTokenAddress,\n abi: POINT_TOKEN_V2_ABI,\n functionName: \"burnRequestNonces\",\n args: [request.userAddress],\n })) as bigint;\n } catch (err) {\n throw new PTRedeemError(\n \"NONCE_READ_FAILED\",\n `failed to read burnRequestNonces(${request.userAddress}): ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n\n // Guard: reject concurrent requests that share the same on-chain nonce.\n // Without this, two parallel requests both read nonce=N, sign two UserOps\n // with nonce=N, and create two pending credits — the losing UserOp reverts\n // on-chain, leaving an orphaned PENDING credit and a wasted signer call.\n const userKey = getAddress(request.userAddress).toLowerCase();\n let userNonces = this.inFlightNonces.get(userKey);\n if (!userNonces) {\n userNonces = new Set<bigint>();\n this.inFlightNonces.set(userKey, userNonces);\n }\n if (userNonces.has(burnNonce)) {\n throw new PTRedeemError(\n \"NONCE_IN_FLIGHT\",\n `A burn request for nonce ${burnNonce} is already in progress for ${request.userAddress}. Retry after the current request completes.`,\n );\n }\n userNonces.add(burnNonce);\n\n try {\n return await this._handleAfterNonceLock(request, burnNonce);\n } finally {\n userNonces.delete(burnNonce);\n if (userNonces.size === 0) this.inFlightNonces.delete(userKey);\n }\n }\n\n private async _handleAfterNonceLock(\n request: PTRedeemRequest,\n burnNonce: bigint,\n ): Promise<PTRedeemResponse> {\n // Self-compute fee + recipient when caller didn't override.\n // Mirrors PTClaimHandler so issuer endpoints stay symmetric.\n let fee: bigint;\n if (request.feeAmount !== undefined) {\n fee = request.feeAmount > 0n ? request.feeAmount : 0n;\n } else if (this.feeService) {\n fee = await this.feeService.estimateGasFee();\n } else {\n fee = 0n;\n }\n const feeRecipient =\n request.feeRecipient ??\n (request.chainId !== undefined\n ? getContractAddresses(request.chainId).pafiFeeRecipient\n : getContractAddresses(this.chainId).pafiFeeRecipient);\n\n if (fee > 0n && fee >= request.amount) {\n throw new PTRedeemError(\n \"INVALID_AMOUNT\",\n `fee (${fee}) must be strictly less than redeem amount (${request.amount})`,\n );\n }\n\n // Verify on-chain balance BEFORE signing — avoid wasting signature slots.\n const onChainBalance = await getPointTokenBalance(\n this.provider,\n this.pointTokenAddress,\n request.userAddress,\n );\n if (onChainBalance < request.amount) {\n throw new PTRedeemError(\n \"INVALID_AMOUNT\",\n `insufficient on-chain PT balance: have ${onChainBalance}, need ${request.amount}`,\n );\n }\n\n const deadline = BigInt(\n Math.floor(this.now() / 1000) + this.signatureDeadlineSeconds,\n );\n const domain: PointTokenDomainConfig = {\n name: this.domain.name,\n chainId: this.chainId,\n verifyingContract: this.domain.verifyingContract ?? this.pointTokenAddress,\n };\n\n // Sponsored path: BurnRequest for (amount - fee). Ops include fee\n // transfer FIRST so a user holding exactly `amount` can complete.\n const sponsoredBurnAmount = request.amount - fee;\n const sponsoredBurnRequest: BurnRequest = {\n from: request.userAddress,\n amount: sponsoredBurnAmount,\n nonce: burnNonce,\n deadline,\n };\n\n let sponsoredSig: Hex;\n try {\n sponsoredSig = (\n await signBurnRequest(this.burnerSignerWallet, domain, sponsoredBurnRequest)\n ).serialized;\n } catch (err) {\n throw new PTRedeemError(\n \"SIGNING_FAILED\",\n `failed to sign sponsored BurnRequest: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n // Reserve sponsored credit (= amount - fee).\n const sponsoredLockId = await this.ledger.reservePendingCredit!(\n request.userAddress,\n sponsoredBurnAmount,\n this.redeemLockDurationMs,\n this.pointTokenAddress,\n );\n\n try {\n const sponsoredUserOp = await this.relayService.prepareBurn({\n mode: \"burnWithSig\",\n userAddress: request.userAddress,\n aaNonce: request.aaNonce,\n pointTokenAddress: this.pointTokenAddress,\n batchExecutorAddress: this.batchExecutorAddress,\n burnRequest: sponsoredBurnRequest,\n burnerSignature: sponsoredSig,\n feeAmount: fee,\n feeRecipient,\n });\n\n // Fallback path: only built when fee > 0. BurnRequest for full `amount`,\n // no fee transfer. Same nonce as sponsored — only one can fire on-chain.\n // The unused lock expires after `redeemLockDurationMs`.\n let fallback: PTRedeemResponse[\"fallback\"] = undefined;\n if (fee > 0n) {\n const fallbackBurnRequest: BurnRequest = {\n from: request.userAddress,\n amount: request.amount,\n nonce: burnNonce,\n deadline,\n };\n\n let fallbackSig: Hex;\n try {\n fallbackSig = (\n await signBurnRequest(this.burnerSignerWallet, domain, fallbackBurnRequest)\n ).serialized;\n } catch (err) {\n throw new PTRedeemError(\n \"SIGNING_FAILED\",\n `failed to sign fallback BurnRequest: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n const fallbackLockId = await this.ledger.reservePendingCredit!(\n request.userAddress,\n request.amount,\n this.redeemLockDurationMs,\n this.pointTokenAddress,\n );\n\n // Inner try — if fallback build fails, release fallback lock.\n // Outer try (below) handles sponsored lock cleanup.\n let fallbackUserOp;\n try {\n fallbackUserOp = await this.relayService.prepareBurn({\n mode: \"burnWithSig\",\n userAddress: request.userAddress,\n aaNonce: request.aaNonce,\n pointTokenAddress: this.pointTokenAddress,\n batchExecutorAddress: this.batchExecutorAddress,\n burnRequest: fallbackBurnRequest,\n burnerSignature: fallbackSig,\n // Explicit 0n — fallback is fee-free regardless of how\n // RelayService is configured. Without this, an\n // auto-quoting RelayService would try to quote a fee here\n // and re-add the PT.transfer we're trying to strip.\n feeAmount: 0n,\n });\n } catch (err) {\n await this.ledger.releaseLock(fallbackLockId).catch(() => {\n /* noop — lock will auto-expire via TTL */\n });\n throw err;\n }\n\n fallback = {\n lockId: fallbackLockId,\n userOp: fallbackUserOp,\n netCreditAmount: request.amount,\n };\n }\n\n // Record against the user's redemption history AFTER both UserOps\n // are built but BEFORE returning. The full requested `amount` is\n // counted (not the net), since sponsored and fallback share the\n // same burnNonce — exactly one will land on-chain and the user\n // commits to a single redemption of `amount` PT either way.\n // Best-effort: a failure here doesn't roll back the reserves —\n // the locks will auto-expire if the burn doesn't land.\n if (this.redemptionService) {\n await this.redemptionService\n .recordSuccessfulInitiate({\n user: request.userAddress,\n amountPt: request.amount,\n pointTokenAddress: this.pointTokenAddress,\n reservationId: sponsoredLockId,\n })\n .catch(() => {\n /* noop — non-critical, history will reconcile from burn events */\n });\n }\n\n return {\n lockId: sponsoredLockId,\n userOp: sponsoredUserOp,\n netCreditAmount: sponsoredBurnAmount,\n feeAmount: fee,\n fallback,\n expiresInSeconds: Math.floor(this.redeemLockDurationMs / 1000),\n signatureDeadline: deadline,\n };\n } catch (err) {\n // Release sponsored lock on ANY post-reserve failure.\n await this.ledger.releaseLock(sponsoredLockId).catch(() => {\n /* noop — lock will auto-expire via TTL */\n });\n throw err;\n }\n }\n}\n","import type { Address, Hex } from \"viem\";\nimport type { IPointLedger, LockedMintRequest, PendingCredit } from \"../ledger/types\";\nimport type { PafiBackendClient } from \"../pafi-backend/client\";\nimport { PafiSdkError } from \"../errors\";\n\n/**\n * Status snapshot returned to mobile clients polling a mint lock.\n * Matches the legacy gg56 shape so existing FE code keeps working.\n */\nexport interface MintStatusResponse {\n lockId: string;\n status: \"PENDING\" | \"MINTED\" | \"EXPIRED\" | \"FAILED\";\n txHash: Hex | null;\n amount: string;\n createdAt: string;\n expiresAt: string;\n}\n\nexport interface BurnStatusResponse {\n lockId: string;\n status: \"PENDING\" | \"RESOLVED\" | \"EXPIRED\";\n txHash: Hex | null;\n amount: string;\n createdAt: string;\n expiresAt: string;\n resolvedAt?: string | null;\n}\n\nexport interface MintStatusParams {\n lockId: string;\n /** User EOA from auth — handler returns 404 if the lock isn't theirs. */\n userAddress: Address;\n ledger: IPointLedger;\n /**\n * PAFI backend client for the bundler-receipt fallback. Optional —\n * when omitted, status only reflects what the on-chain `PointIndexer`\n * has finalized into the ledger. With it, the handler queries the\n * bundler receipt when:\n * - lock.status === \"PENDING\"\n * - lock.userOpHash is bound (set by `/claim/submit`)\n *\n * If the bundler reports the UserOp confirmed, the handler updates\n * the ledger lock + returns `MINTED` immediately, bypassing\n * `PointIndexer`'s amount-based race (multiple PENDING locks with\n * the same amount can be matched to the wrong tx_hash).\n */\n pafiBackendClient?: PafiBackendClient | null;\n /** Optional logger for \"ledger update failed\" warnings. */\n onWarning?: (msg: string) => void;\n}\n\nexport interface BurnStatusParams {\n lockId: string;\n userAddress: Address;\n ledger: IPointLedger;\n pafiBackendClient?: PafiBackendClient | null;\n onWarning?: (msg: string) => void;\n}\n\nclass LockNotFoundError extends PafiSdkError {\n readonly code = \"LOCK_NOT_FOUND\";\n readonly httpStatus = \"not_found\" as const;\n constructor() {\n super(\"Lock not found or does not belong to authenticated user\");\n }\n}\n\nexport { LockNotFoundError };\n\n/**\n * Handle GET /claim/status/:lockId.\n *\n * Returns the lock's current state from the ledger. When the ledger\n * supports `userOpHash` binding AND the lock is still PENDING, falls\n * back to the bundler receipt — the canonical truth that bypasses\n * `PointIndexer`'s amount-based matching race.\n *\n * Throws `LockNotFoundError` when the lock doesn't exist or doesn't\n * belong to `userAddress`. Caller maps to HTTP 404.\n */\nexport async function handleClaimStatus(\n params: MintStatusParams,\n): Promise<MintStatusResponse> {\n if (!params.ledger.getMintLock) {\n throw new Error(\n \"handleClaimStatus: ledger does not implement `getMintLock` — implement the optional method on `IPointLedger` or write a custom status handler.\",\n );\n }\n\n const lock = await params.ledger.getMintLock(\n params.lockId,\n params.userAddress,\n );\n if (\n !lock ||\n lock.userAddress.toLowerCase() !== params.userAddress.toLowerCase()\n ) {\n throw new LockNotFoundError();\n }\n\n let status: LockedMintRequest[\"status\"] = lock.status;\n let txHash: Hex | null = lock.txHash ?? null;\n\n // Bundler-receipt fallback. Only runs when:\n // - status is still PENDING (terminal states are authoritative)\n // - userOpHash is bound (i.e. /claim/submit ran successfully)\n // - PafiBackendClient is supplied (caller opted in)\n if (\n status === \"PENDING\" &&\n lock.userOpHash &&\n params.pafiBackendClient\n ) {\n try {\n const receipt = await params.pafiBackendClient.getUserOpReceipt(\n lock.userOpHash,\n );\n if (receipt) {\n status = receipt.success ? \"MINTED\" : \"FAILED\";\n txHash = receipt.txHash;\n // Best-effort persist so the next poll hits the cheap ledger\n // path and so PointIndexer/audit downstream gets the correct\n // tx_hash even if the indexer's amount-match resolved a\n // different sibling lock.\n await params.ledger\n .updateMintStatus(lock.lockId, status, receipt.txHash)\n .catch((err) => {\n params.onWarning?.(\n `handleClaimStatus: ledger updateMintStatus failed for lock ${lock.lockId}: ${err}`,\n );\n });\n }\n } catch (err) {\n params.onWarning?.(\n `handleClaimStatus: bundler-receipt fallback failed for lock ${lock.lockId}: ${err}`,\n );\n }\n }\n\n return {\n lockId: lock.lockId,\n status,\n txHash,\n amount: lock.amount.toString(),\n createdAt: new Date(lock.createdAt).toISOString(),\n expiresAt: new Date(lock.expiresAt).toISOString(),\n };\n}\n\n/**\n * Handle GET /redeem/status/:lockId. Symmetric to `handleClaimStatus`\n * for the burn → off-chain credit flow.\n *\n * Bundler-receipt fallback resolves the credit via\n * `ledger.resolveCreditByBurnTx` when the bundler reports the burn\n * UserOp succeeded.\n */\nexport async function handleRedeemStatus(\n params: BurnStatusParams,\n): Promise<BurnStatusResponse> {\n if (!params.ledger.getPendingCredit) {\n throw new Error(\n \"handleRedeemStatus: ledger does not implement `getPendingCredit` — implement the optional method on `IPointLedger` or write a custom status handler.\",\n );\n }\n\n const credit = await params.ledger.getPendingCredit(\n params.lockId,\n params.userAddress,\n );\n if (\n !credit ||\n credit.userAddress.toLowerCase() !== params.userAddress.toLowerCase()\n ) {\n throw new LockNotFoundError();\n }\n\n let status: PendingCredit[\"status\"] = credit.status;\n let txHash: Hex | null = credit.txHash ?? null;\n\n if (\n status === \"PENDING\" &&\n credit.userOpHash &&\n params.pafiBackendClient\n ) {\n try {\n const receipt = await params.pafiBackendClient.getUserOpReceipt(\n credit.userOpHash,\n );\n if (receipt && receipt.success) {\n status = \"RESOLVED\";\n txHash = receipt.txHash;\n if (params.ledger.resolveCreditByBurnTx) {\n await params.ledger\n .resolveCreditByBurnTx(credit.lockId, receipt.txHash)\n .catch((err) => {\n params.onWarning?.(\n `handleRedeemStatus: resolveCreditByBurnTx failed for lock ${credit.lockId}: ${err}`,\n );\n });\n }\n }\n } catch (err) {\n params.onWarning?.(\n `handleRedeemStatus: bundler-receipt fallback failed for lock ${credit.lockId}: ${err}`,\n );\n }\n }\n\n return {\n lockId: credit.lockId,\n status,\n txHash,\n amount: credit.amount.toString(),\n createdAt: new Date(credit.createdAt).toISOString(),\n expiresAt: new Date(credit.expiresAt).toISOString(),\n resolvedAt: credit.resolvedAt\n ? new Date(credit.resolvedAt).toISOString()\n : null,\n };\n}\n","import type { Address, Hex, PublicClient } from \"viem\";\nimport { getAddress } from \"viem\";\nimport {\n ENTRY_POINT_V08,\n parseEip7702DelegatedAddress,\n type PartialUserOperation,\n} from \"@pafi-dev/core\";\nimport {\n prepareMobileUserOp,\n serializeEntryToJsonRpc,\n type IPendingUserOpStore,\n type PrepareMobileUserOpResult,\n} from \"../userop-store\";\nimport {\n requestPaymaster,\n relayUserOp,\n} from \"../pafi-backend/helpers\";\nimport type { PafiBackendClient } from \"../pafi-backend/client\";\nimport { PafiSdkError } from \"../errors\";\n\n/**\n * Mobile prepare/submit orchestrators — abstract the duplicate glue\n * between `/claim/prepare`+`/claim/submit` and `/redeem/prepare`+\n * `/redeem/submit`. Both share the same shape:\n *\n * prepare: fees + delegation check → paymaster → prepareMobileUserOp\n * submit: fetch entry → serialize+sign → relay → bind hash → delete\n *\n * Issuer controllers shrink to ~30 LoC each — wire the handler that\n * produces `partialUserOp[+ fallback]`, hand off to these.\n */\n\nexport class PendingUserOpNotFoundError extends PafiSdkError {\n readonly code = \"PENDING_USEROP_NOT_FOUND\";\n readonly httpStatus = \"not_found\" as const;\n constructor(lockId: string) {\n super(\n `No pending UserOp found for lockId ${lockId} — it may have expired or already been submitted.`,\n );\n }\n}\n\n/**\n * Thrown when the authenticated user attempts to submit a pending\n * UserOp that belongs to a different sender. Map to 403.\n *\n * The pending-userop store is keyed by `lockId` (a UUID), but lockIds\n * are predictable by anyone who logs them — without this check, user A\n * could submit user B's UserOp once they leak/guess the lockId. The\n * UserOp signature itself recovers to user B (so on-chain it would\n * fail), but the issuer would still bind the bundler hash to user B's\n * lock and consume relay quota, enabling DoS / state pollution.\n */\nexport class PendingUserOpForbiddenError extends PafiSdkError {\n readonly code = \"PENDING_USEROP_FORBIDDEN\";\n readonly httpStatus = \"forbidden\" as const;\n constructor(lockId: string) {\n super(\n `Pending UserOp ${lockId} does not belong to the authenticated user.`,\n );\n }\n}\n\nexport interface HandleMobilePrepareParams {\n /** User EOA — used for the delegation check. */\n userAddress: Address;\n chainId: number;\n /** Lock id (issuer-generated) keying both store entry + ledger row. */\n lockId: string;\n /**\n * Partial UserOp from the upstream handler (PTClaimHandler /\n * PTRedeemHandler). The orchestrator will top up `maxFeePerGas` /\n * `maxPriorityFeePerGas` from `provider.estimateFeesPerGas()` if the\n * fields aren't already set, then request paymaster sponsorship.\n */\n partialUserOp: PartialUserOperation;\n /** Optional fee-stripped fallback variant. */\n partialUserOpFallback?: PartialUserOperation;\n /**\n * Scenario tag — passed to `requestSponsorship` so the relayer can\n * apply per-scenario L1 enforcement (`mint`, `burn`, etc.).\n */\n scenario: string;\n /** Target contract for the paymaster intent. */\n pointTokenAddress: Address;\n\n /** TTL the store entry should outlive. Typically the lock duration in seconds. */\n ttlSeconds: number;\n\n store: IPendingUserOpStore;\n provider: PublicClient;\n /** Optional — when omitted, paymaster is skipped and `sponsored: false` is returned. */\n pafiBackendClient?: PafiBackendClient | null;\n onWarning?: (msg: string) => void;\n}\n\nexport interface HandleMobilePrepareResult extends PrepareMobileUserOpResult {\n /**\n * True when paymaster sponsorship was applied to the sponsored variant.\n * (Renamed from `sponsored` to avoid clashing with\n * `PrepareMobileUserOpResult.sponsored` which is the PreparedUserOp object.)\n */\n isSponsored: boolean;\n /**\n * True when the user's EOA has no EIP-7702 delegation set on-chain.\n * The mobile client must run the `/delegate/*` flow first.\n */\n needsDelegation: boolean;\n}\n\n/**\n * Build the mobile prepare response. End-to-end:\n *\n * 1. Pull `estimateFeesPerGas` + `getCode(user)` in parallel.\n * 2. Top up sponsored UserOp fees if the upstream handler left them\n * unset.\n * 3. `requestPaymaster` — non-fatal: if PAFI declines, the sponsored\n * variant still works, the FE just falls back to the unsponsored\n * response.\n * 4. `prepareMobileUserOp` — merge + hash + persist.\n */\nexport async function handleMobilePrepare(\n params: HandleMobilePrepareParams,\n): Promise<HandleMobilePrepareResult> {\n const [fees, userCode] = await Promise.all([\n params.provider.estimateFeesPerGas(),\n params.provider.getCode({ address: params.userAddress }),\n ]);\n\n const needsDelegation = parseEip7702DelegatedAddress(userCode) === null;\n\n const sponsoredOp = {\n ...params.partialUserOp,\n maxFeePerGas: fees.maxFeePerGas ?? params.partialUserOp.maxFeePerGas ?? 0n,\n maxPriorityFeePerGas:\n fees.maxPriorityFeePerGas ??\n params.partialUserOp.maxPriorityFeePerGas ??\n 0n,\n };\n\n const paymasterFields = await requestPaymaster({\n client: params.pafiBackendClient,\n chainId: params.chainId,\n scenario: params.scenario,\n userOp: sponsoredOp,\n pointTokenAddress: params.pointTokenAddress,\n onWarning: params.onWarning,\n });\n\n const prepared = await prepareMobileUserOp({\n lockId: params.lockId,\n partialUserOp: sponsoredOp,\n partialUserOpFallback: params.partialUserOpFallback,\n paymasterFields,\n chainId: params.chainId,\n store: params.store,\n ttlSeconds: params.ttlSeconds,\n });\n\n return {\n ...prepared,\n isSponsored: !!paymasterFields,\n needsDelegation,\n };\n}\n\nexport interface HandleMobileSubmitParams {\n lockId: string;\n /**\n * Authenticated user EOA — extracted from the JWT / session by the\n * caller. Enforces ownership: the helper rejects with\n * `PendingUserOpForbiddenError` when `entry.sender !==\n * authenticatedAddress`.\n */\n authenticatedAddress: Address;\n /** User signature over `userOpHash` (or `userOpHashFallback`). */\n signature: Hex;\n /** Which variant the user actually signed. Defaults to `'sponsored'`. */\n variant?: \"sponsored\" | \"fallback\";\n store: IPendingUserOpStore;\n /**\n * Bind the bundler-returned hash to the lock so `claim/redeem status`\n * can fall back to the bundler receipt when the indexer's\n * amount-based match races and resolves a sibling. Different ledgers\n * use different methods (`bindMintUserOpHash` vs `bindCreditUserOpHash`),\n * so the caller passes the correct one.\n */\n bindUserOpHash: (lockId: string, userOpHash: Hex) => Promise<void>;\n\n pafiBackendClient?: PafiBackendClient | null;\n /** Defaults to `ENTRY_POINT_V08`. */\n entryPoint?: string;\n}\n\n/**\n * Submit a previously-prepared mobile UserOp to the bundler.\n *\n * Throws:\n * - `PendingUserOpNotFoundError` — entry expired or already submitted.\n * Map to 404.\n * - `PendingUserOpForbiddenError` — `entry.sender` doesn't match the\n * authenticated user. Map to 403.\n * - `BundlerNotConfiguredError` — `pafiBackendClient` missing. Map to 503.\n * - `BundlerRejectedError` — bundler rejected the UserOp. Map to 422.\n */\nexport async function handleMobileSubmit(\n params: HandleMobileSubmitParams,\n): Promise<{ userOpHash: Hex }> {\n const entry = await params.store.get(params.lockId);\n if (!entry) {\n throw new PendingUserOpNotFoundError(params.lockId);\n }\n\n // Ownership check — entry.sender must match the JWT-authenticated\n // user. Without this, user A could submit user B's pending UserOp\n // once they leak/guess B's lockId. Compare via `getAddress` so\n // checksummed/lowercased forms both work.\n if (\n getAddress(entry.sender) !== getAddress(params.authenticatedAddress)\n ) {\n throw new PendingUserOpForbiddenError(params.lockId);\n }\n\n const variant = params.variant ?? \"sponsored\";\n const userOpJson = serializeEntryToJsonRpc(entry, params.signature, variant);\n\n const result = await relayUserOp({\n client: params.pafiBackendClient,\n userOp: userOpJson,\n entryPoint: params.entryPoint ?? ENTRY_POINT_V08,\n });\n\n await params.bindUserOpHash(params.lockId, result.userOpHash);\n await params.store.delete(params.lockId);\n\n return { userOpHash: result.userOpHash };\n}\n","import type { Hex } from \"viem\";\nimport { serializeUserOpToJsonRpc } from \"@pafi-dev/core\";\nimport type { PendingUserOpEntry } from \"./types\";\n\n/**\n * Convert a stored `PendingUserOpEntry` (decimal-string fields) plus a\n * signature into the JSON-RPC wire format for `eth_sendUserOperation`.\n *\n * Bridges the gap between the serialized storage format (decimal strings,\n * safe for JSON/Redis) and `serializeUserOpToJsonRpc` which expects bigints.\n */\nexport function serializeEntryToJsonRpc(\n entry: PendingUserOpEntry,\n signature: Hex,\n variant: \"sponsored\" | \"fallback\" = \"sponsored\",\n): Record<string, string | null> {\n // Fallback variant: same sender / nonce / fees but different\n // callData (no PT operator-fee transfer), no paymaster fields.\n // User pays ERC-4337 gas in ETH directly.\n if (variant === \"fallback\") {\n if (!entry.fallback) {\n throw new Error(\n \"serializeEntryToJsonRpc: variant=fallback requested but the stored entry has no `fallback` branch — caller should resubmit with variant='sponsored' or re-prepare with a fee configured.\",\n );\n }\n return serializeUserOpToJsonRpc(\n {\n sender: entry.sender,\n nonce: BigInt(entry.nonce),\n callData: entry.fallback.callData,\n callGasLimit: BigInt(entry.fallback.callGasLimit),\n verificationGasLimit: BigInt(entry.fallback.verificationGasLimit),\n preVerificationGas: BigInt(entry.fallback.preVerificationGas),\n maxFeePerGas: BigInt(entry.maxFeePerGas),\n maxPriorityFeePerGas: BigInt(entry.maxPriorityFeePerGas),\n // intentionally no paymaster — user pays ETH gas\n },\n signature,\n );\n }\n\n return serializeUserOpToJsonRpc(\n {\n sender: entry.sender,\n nonce: BigInt(entry.nonce),\n callData: entry.callData,\n callGasLimit: BigInt(entry.callGasLimit),\n verificationGasLimit: BigInt(entry.verificationGasLimit),\n preVerificationGas: BigInt(entry.preVerificationGas),\n maxFeePerGas: BigInt(entry.maxFeePerGas),\n maxPriorityFeePerGas: BigInt(entry.maxPriorityFeePerGas),\n paymaster: entry.paymaster,\n paymasterVerificationGasLimit:\n entry.paymasterVerificationGasLimit != null\n ? BigInt(entry.paymasterVerificationGasLimit)\n : undefined,\n paymasterPostOpGasLimit:\n entry.paymasterPostOpGasLimit != null\n ? BigInt(entry.paymasterPostOpGasLimit)\n : undefined,\n paymasterData: entry.paymasterData,\n },\n signature,\n );\n}\n","import type { IPendingUserOpStore, PendingUserOpEntry } from \"./types\";\n\ninterface MemoryEntry {\n entry: PendingUserOpEntry;\n expiresAt: number;\n}\n\n/**\n * In-memory `IPendingUserOpStore` for **single-instance dev / test\n * harnesses only**.\n *\n * Multi-instance deployments (k8s, PM2 cluster) MUST use a shared store\n * — typically Redis with a TTL key or Postgres with `expires_at`. If\n * `prepare` lands on instance A and `submit` on instance B, an\n * in-memory store on A loses the entry.\n *\n * Entries are evicted lazily on `get()` if expired. Periodic sweep is\n * not implemented — the in-memory map's footprint scales with\n * outstanding pending UserOps, which is bounded by the issuer's lock\n * duration (default 15 min).\n */\nexport class MemoryPendingUserOpStore implements IPendingUserOpStore {\n private readonly entries = new Map<string, MemoryEntry>();\n private readonly now: () => number;\n\n constructor(now: () => number = () => Date.now()) {\n this.now = now;\n }\n\n async save(\n lockId: string,\n entry: PendingUserOpEntry,\n ttlSeconds: number,\n ): Promise<void> {\n this.entries.set(lockId, {\n entry,\n expiresAt: this.now() + ttlSeconds * 1000,\n });\n }\n\n async get(lockId: string): Promise<PendingUserOpEntry | null> {\n const hit = this.entries.get(lockId);\n if (!hit) return null;\n if (hit.expiresAt <= this.now()) {\n this.entries.delete(lockId);\n return null;\n }\n return hit.entry;\n }\n\n async delete(lockId: string): Promise<void> {\n this.entries.delete(lockId);\n }\n}\n","import type { Address, Hex } from \"viem\";\nimport {\n buildUserOpTypedData,\n computeUserOpHash,\n type PartialUserOperation,\n type UserOpTypedData,\n} from \"@pafi-dev/core\";\nimport type { IPendingUserOpStore, PendingUserOpEntry } from \"./types\";\n\n/**\n * Re-shape `UserOpTypedData` so all `bigint` fields become hex strings —\n * required for JSON transport over HTTP. Mirrors the inverse of what\n * `walletClient.signTypedData` accepts on the client (it auto-coerces hex\n * strings back to bigints for `uint256` fields).\n */\nexport type SerializedUserOpTypedData = {\n domain: UserOpTypedData[\"domain\"];\n types: UserOpTypedData[\"types\"];\n primaryType: UserOpTypedData[\"primaryType\"];\n message: {\n sender: Address;\n nonce: Hex;\n initCode: Hex;\n callData: Hex;\n accountGasLimits: Hex;\n preVerificationGas: Hex;\n gasFees: Hex;\n paymasterAndData: Hex;\n };\n};\n\n/**\n * Convert a `UserOpTypedData` payload into the JSON-safe wire form\n * (bigint → hex string). The mobile client passes this directly to\n * `eth_signTypedData_v4` / viem's `signTypedData`.\n */\nexport function serializeUserOpTypedData(\n td: UserOpTypedData,\n): SerializedUserOpTypedData {\n return {\n domain: td.domain,\n types: td.types,\n primaryType: td.primaryType,\n message: {\n sender: td.message.sender,\n nonce: `0x${td.message.nonce.toString(16)}` as Hex,\n initCode: td.message.initCode,\n callData: td.message.callData,\n accountGasLimits: td.message.accountGasLimits,\n preVerificationGas: `0x${td.message.preVerificationGas.toString(\n 16,\n )}` as Hex,\n gasFees: td.message.gasFees,\n paymasterAndData: td.message.paymasterAndData,\n },\n };\n}\n\n/**\n * Merge Pimlico's paymaster-sponsorship response into a UserOp\n * skeleton, applying only fields that are actually defined.\n *\n * `pm_sponsorUserOperation` returns:\n * - `paymaster` / `paymasterData` — required for the sponsored sig\n * - `paymasterVerificationGasLimit` / `paymasterPostOpGasLimit`\n * - **re-estimated** `callGasLimit` / `verificationGasLimit` /\n * `preVerificationGas` — the paymaster signature is computed over\n * these new values\n * - **bundler-required** `maxFeePerGas` / `maxPriorityFeePerGas` —\n * Base RPC's `eth_feeHistory` underestimates, bundler rejects\n *\n * Callers MUST re-merge ALL of these into the userOp BEFORE computing\n * the EIP-712 userOpHash — otherwise both the paymaster signature\n * (AA34) and the user signature (AA24, recovered against a different\n * hash) fail at validation.\n *\n * Skips fields that are undefined so legacy paymaster responses\n * (without re-estimated gas) don't accidentally zero out the original\n * estimates.\n */\nexport function mergePaymasterFields<T extends object>(\n userOp: T,\n paymasterFields:\n | {\n paymaster: Address;\n paymasterData: Hex;\n paymasterVerificationGasLimit: bigint;\n paymasterPostOpGasLimit: bigint;\n callGasLimit?: bigint;\n verificationGasLimit?: bigint;\n preVerificationGas?: bigint;\n maxFeePerGas?: bigint;\n maxPriorityFeePerGas?: bigint;\n }\n | undefined,\n): T {\n if (!paymasterFields) return userOp;\n const merged: Record<string, unknown> = {\n ...(userOp as Record<string, unknown>),\n };\n for (const [k, v] of Object.entries(paymasterFields)) {\n if (v !== undefined) merged[k] = v;\n }\n return merged as unknown as T;\n}\n\nexport interface PreparedUserOp {\n /** The bundler-ready UserOp (with paymaster + Pimlico-quoted gas). */\n userOp: PartialUserOperation & {\n maxFeePerGas: bigint;\n maxPriorityFeePerGas: bigint;\n paymaster?: Address;\n paymasterData?: Hex;\n paymasterVerificationGasLimit?: bigint;\n paymasterPostOpGasLimit?: bigint;\n };\n /** Hex-encoded EIP-712 digest. Equals `EntryPoint.getUserOpHash`. */\n userOpHash: Hex;\n /** Typed-data payload — pass directly to `eth_signTypedData_v4`. */\n typedData: SerializedUserOpTypedData;\n}\n\nexport interface PrepareMobileUserOpParams {\n /** Lock id (issuer-generated) keying both store entry + ledger row. */\n lockId: string;\n /**\n * Sponsored variant — built with the PT operator-fee transfer\n * included. SDK or caller should set `partialUserOp.maxFeePerGas` /\n * `maxPriorityFeePerGas` from `provider.estimateFeesPerGas()` before\n * calling — they get overridden by Pimlico's quote anyway, but\n * they must be valid bigints for the merge.\n */\n partialUserOp: PartialUserOperation & {\n maxFeePerGas: bigint;\n maxPriorityFeePerGas: bigint;\n };\n /**\n * Optional fee-stripped fallback variant — built with `gasFeePt: 0n`\n * (no PT operator-fee transfer). Submitted when paymaster refuses\n * sponsorship and the user pays ETH gas directly.\n */\n partialUserOpFallback?: PartialUserOperation & {\n maxFeePerGas?: bigint;\n maxPriorityFeePerGas?: bigint;\n };\n /** Paymaster sponsorship response, or `undefined` if PAFI declined. */\n paymasterFields?: {\n paymaster: Address;\n paymasterData: Hex;\n paymasterVerificationGasLimit: bigint;\n paymasterPostOpGasLimit: bigint;\n callGasLimit?: bigint;\n verificationGasLimit?: bigint;\n preVerificationGas?: bigint;\n maxFeePerGas?: bigint;\n maxPriorityFeePerGas?: bigint;\n };\n chainId: number;\n /** Pending-userop store implementation (Redis/Postgres/Memory). */\n store: IPendingUserOpStore;\n /** TTL the store entry should outlive — typically the MintRequest deadline. */\n ttlSeconds: number;\n}\n\nexport interface PrepareMobileUserOpResult {\n sponsored: PreparedUserOp;\n /**\n * Set when `partialUserOpFallback` was supplied AND the PT fee was\n * non-zero (i.e. sponsored ≠ fallback). Mobile client picks which\n * variant to sign + submit; SDK's `serializeEntryToJsonRpc` reads\n * the `variant` flag to dispatch.\n */\n fallback?: PreparedUserOp;\n /** What got persisted into the pending-userop store. */\n entry: PendingUserOpEntry;\n}\n\n/**\n * Build the sponsored UserOp + (optional) fee-stripped fallback for the\n * mobile prepare/submit flow.\n *\n * What this does, end-to-end:\n * 1. Merge Pimlico's paymaster sponsorship + re-quoted gas into the\n * caller's `partialUserOp` skeleton.\n * 2. Compute the EIP-712 userOpHash + the typed-data payload (in\n * JSON-safe form for HTTP transport).\n * 3. (Optional) Repeat for the `partialUserOpFallback` skeleton with\n * no paymaster fields — produces a separate hash + typed-data so\n * the client can re-sign if it falls back.\n * 4. Persist a single store entry containing BOTH callData variants\n * keyed by lockId. `serializeEntryToJsonRpc` reads the `variant`\n * param at submit time.\n *\n * Replaces ~100 LoC of glue per scenario in issuer controllers.\n */\nexport async function prepareMobileUserOp(\n params: PrepareMobileUserOpParams,\n): Promise<PrepareMobileUserOpResult> {\n const userOp = mergePaymasterFields(\n params.partialUserOp,\n params.paymasterFields,\n ) as unknown as PreparedUserOp[\"userOp\"];\n\n const userOpHash = computeUserOpHash(userOp, params.chainId);\n const typedData = serializeUserOpTypedData(\n buildUserOpTypedData(userOp, params.chainId),\n );\n\n let fallback: PreparedUserOp | undefined;\n let fallbackEntry: PendingUserOpEntry[\"fallback\"];\n if (params.partialUserOpFallback) {\n // Fallback fees MUST mirror the sponsored variant's *post-paymaster*\n // values — the pending-userop store reuses top-level fees for both\n // variants when serializing on submit, so hashing fallback against\n // pre-paymaster RPC estimates causes AA24 (signed digest ≠ submitted).\n const fallbackUserOp = {\n ...params.partialUserOpFallback,\n maxFeePerGas: userOp.maxFeePerGas,\n maxPriorityFeePerGas: userOp.maxPriorityFeePerGas,\n };\n const fallbackHash = computeUserOpHash(fallbackUserOp, params.chainId);\n const fallbackTypedData = serializeUserOpTypedData(\n buildUserOpTypedData(fallbackUserOp, params.chainId),\n );\n fallback = {\n userOp: fallbackUserOp as PreparedUserOp[\"userOp\"],\n userOpHash: fallbackHash,\n typedData: fallbackTypedData,\n };\n fallbackEntry = {\n callData: fallbackUserOp.callData,\n callGasLimit: fallbackUserOp.callGasLimit.toString(),\n verificationGasLimit: fallbackUserOp.verificationGasLimit.toString(),\n preVerificationGas: fallbackUserOp.preVerificationGas.toString(),\n userOpHash: fallbackHash,\n };\n }\n\n const entry: PendingUserOpEntry = {\n sender: userOp.sender,\n nonce: userOp.nonce.toString(),\n callData: userOp.callData,\n callGasLimit: userOp.callGasLimit.toString(),\n verificationGasLimit: userOp.verificationGasLimit.toString(),\n preVerificationGas: userOp.preVerificationGas.toString(),\n maxFeePerGas: userOp.maxFeePerGas.toString(),\n maxPriorityFeePerGas: userOp.maxPriorityFeePerGas.toString(),\n paymaster: userOp.paymaster,\n paymasterVerificationGasLimit:\n userOp.paymasterVerificationGasLimit?.toString(),\n paymasterPostOpGasLimit: userOp.paymasterPostOpGasLimit?.toString(),\n paymasterData: userOp.paymasterData,\n chainId: params.chainId,\n userOpHash,\n fallback: fallbackEntry,\n };\n\n await params.store.save(params.lockId, entry, params.ttlSeconds);\n\n return {\n sponsored: { userOp, userOpHash, typedData },\n fallback,\n entry,\n };\n}\n","export interface RetryConfig {\n maxAttempts?: number;\n initialDelayMs?: number;\n maxDelayMs?: number;\n maxRetryAfterMs?: number;\n}\n\nexport interface PafiBackendConfig {\n /**\n * chainId — used to derive the sponsor-relayer base URL via\n * `getPafiServiceUrls(chainId).sponsorRelayer`. SDK ships with the\n * URL per chainId; bump SDK version to retarget.\n */\n chainId: number;\n issuerId: string;\n apiKey: string;\n fetchImpl?: typeof fetch;\n timeoutMs?: number;\n retry?: RetryConfig;\n}\n\nexport type PafiBackendErrorCode =\n | \"MISSING_ISSUER_ID\"\n | \"MISSING_API_KEY\"\n | \"ISSUER_UNAUTHORIZED\"\n | \"USER_UNAUTHORIZED\"\n | \"INTENT_REJECTED\"\n | \"MINT_CAP_EXCEEDED\"\n | \"ISSUER_INACTIVE\"\n | \"BROKER_NOT_WHITELISTED\"\n | \"RATE_LIMIT_EXCEEDED\"\n | \"RATE_LIMIT_EXCEEDED_DAILY\"\n | \"RATE_LIMIT_EXCEEDED_PER_USER\"\n | \"ISSUER_BUDGET_EXCEEDED\"\n | \"RATE_LIMITER_UNAVAILABLE\"\n | \"PAYMASTER_UNAVAILABLE\"\n | \"TARGET_NOT_ALLOWLISTED\"\n | \"BAD_REQUEST\"\n | \"INTERNAL_ERROR\"\n | \"TIMEOUT\"\n | \"NETWORK_ERROR\"\n | (string & {});\n\nexport class PafiBackendError extends Error {\n public readonly retryAfter?: number;\n private readonly serverSafeToRetry?: boolean;\n\n constructor(\n public code: PafiBackendErrorCode,\n message: string,\n public httpStatus: number,\n public details?: unknown,\n opts?: { retryAfter?: number; safeToRetry?: boolean },\n ) {\n super(message);\n this.name = \"PafiBackendError\";\n if (opts?.retryAfter !== undefined) this.retryAfter = opts.retryAfter;\n if (opts?.safeToRetry !== undefined) this.serverSafeToRetry = opts.safeToRetry;\n }\n\n get safeToRetry(): boolean {\n if (this.serverSafeToRetry !== undefined) return this.serverSafeToRetry;\n switch (this.code) {\n case \"RATE_LIMITER_UNAVAILABLE\":\n case \"INTERNAL_ERROR\":\n case \"TIMEOUT\":\n case \"NETWORK_ERROR\":\n return true;\n case \"RATE_LIMIT_EXCEEDED\":\n case \"RATE_LIMIT_EXCEEDED_DAILY\":\n case \"RATE_LIMIT_EXCEEDED_PER_USER\":\n case \"ISSUER_BUDGET_EXCEEDED\":\n return true;\n default:\n return false;\n }\n }\n}\n","import type { Address, Hex } from \"viem\";\nimport type { ENTRY_POINT_V08 } from \"@pafi-dev/core\";\nimport type { PafiBackendClient } from \"./client\";\nimport { PafiBackendError } from \"./types\";\nimport { PafiSdkError } from \"../errors\";\n\n/**\n * Typed errors thrown by the helpers below — issuer controllers map\n * these to the appropriate HTTP status. We don't depend on @nestjs/common\n * here because the SDK is framework-agnostic; the consuming controller\n * wraps each one in its preferred exception class.\n */\nexport class BundlerNotConfiguredError extends PafiSdkError {\n readonly code = \"BUNDLER_NOT_CONFIGURED\";\n readonly httpStatus = \"service_unavailable\" as const;\n constructor() {\n super(\n \"PAFI backend client not configured — set PAFI_BACKEND_URL, PAFI_ISSUER_ID, PAFI_API_KEY to enable mobile submit.\",\n );\n }\n}\n\nexport class BundlerRejectedError extends PafiSdkError {\n readonly code = \"BUNDLER_REJECTED\";\n readonly httpStatus = \"unprocessable\" as const;\n readonly cause: unknown;\n constructor(message: string, cause: unknown) {\n super(message);\n this.cause = cause;\n }\n}\n\nexport interface RequestPaymasterParams {\n /** PAFI backend client. When `null` / `undefined` → returns `undefined`. */\n client: PafiBackendClient | null | undefined;\n chainId: number;\n scenario: string;\n /** UserOp skeleton — must have all gas + fee fields set. */\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 };\n /** Target contract (typically the PointToken). */\n pointTokenAddress: Address;\n /**\n * Function name to surface in audit / paymaster context. Defaults to\n * a per-scenario sensible value (`mint` / `burn` / `swap` / generic\n * scenario name).\n */\n functionName?: string;\n /**\n * EIP-7702 authorization tuple — REQUIRED for the `delegate`\n * scenario. Forwarded to sponsor-relayer's `/paymaster/sponsor`\n * which embeds it into the UserOp before `pm_sponsorUserOperation`,\n * letting Pimlico simulate the delegation atomically with the\n * sponsored UserOp. Without this, simulator throws\n * `AA20 account not deployed`. v0.7.5 added.\n */\n eip7702Auth?: {\n chainId: string;\n address: string;\n nonce: string;\n r: string;\n s: string;\n yParity: string;\n };\n /** Optional logger for the \"sponsorship declined\" warning. */\n onWarning?: (msg: string) => void;\n}\n\n/**\n * Thin wrapper around `PafiBackendClient.requestSponsorship` with the\n * \"non-fatal on failure\" semantics every issuer wants:\n *\n * - When the client is missing → returns `undefined` (the caller falls\n * back to a self-funded UserOp).\n * - When the network call throws OR PAFI declines (rate limit, intent\n * rejection, paymaster outage) → returns `undefined` after logging,\n * so the controller doesn't hard-fail. The caller's\n * `prepareMobileUserOp` / `mergePaymasterFields` will gracefully\n * produce the unsponsored variant.\n *\n * Replaces ~30 LoC of try/catch + scenario-to-function mapping every\n * issuer would copy.\n */\nexport async function requestPaymaster(\n params: RequestPaymasterParams,\n): Promise<\n Awaited<ReturnType<PafiBackendClient[\"requestSponsorship\"]>> | undefined\n> {\n if (!params.client) return undefined;\n\n const fn = params.functionName ?? defaultFunctionForScenario(params.scenario);\n\n try {\n return await params.client.requestSponsorship({\n chainId: params.chainId,\n scenario: params.scenario,\n userOp: params.userOp,\n target: {\n contract: params.pointTokenAddress,\n function: fn,\n pointToken: params.pointTokenAddress,\n },\n ...(params.eip7702Auth ? { eip7702Auth: params.eip7702Auth } : {}),\n });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (err instanceof PafiBackendError && isTransientPaymasterError(err.code)) {\n params.onWarning?.(`Paymaster sponsorship declined (transient): ${msg}`);\n return undefined;\n }\n // Non-transient: rethrow so the issuer sees the real failure.\n throw err;\n }\n}\n\nfunction isTransientPaymasterError(code: string): boolean {\n switch (code) {\n case \"NETWORK_ERROR\":\n case \"TIMEOUT\":\n case \"PAYMASTER_UNAVAILABLE\":\n case \"RATE_LIMITER_UNAVAILABLE\":\n case \"INTERNAL_ERROR\":\n return true;\n default:\n return false;\n }\n}\n\nfunction defaultFunctionForScenario(scenario: string): string {\n switch (scenario) {\n case \"mint\":\n return \"mint\";\n case \"burn\":\n return \"burn\";\n case \"swap\":\n return \"swap\";\n case \"perp-deposit\":\n return \"deposit\";\n default:\n return scenario;\n }\n}\n\nexport interface RelayUserOpParams {\n client: PafiBackendClient | null | undefined;\n /** EntryPoint address — typically `ENTRY_POINT_V08` from core. */\n entryPoint: typeof ENTRY_POINT_V08 | string;\n userOp: Record<string, string | null>;\n /** EIP-7702 authorization (delegation UserOps only). */\n eip7702Auth?: {\n chainId: string;\n address: string;\n nonce: string;\n r: string;\n s: string;\n yParity: string;\n };\n}\n\n/**\n * Submit a serialized UserOp to the Pimlico bundler via PAFI's\n * sponsor-relayer. Handles the \"client missing\" / \"bundler rejected\"\n * branches as typed errors so the controller can map to HTTP cleanly.\n *\n * Every issuer mobile flow has this exact wrapper — moved into SDK\n * to drop ~30 LoC of try/catch + error-shape boilerplate per\n * controller.\n *\n * Throws:\n * - `BundlerNotConfiguredError` — caller didn't configure\n * `PafiBackendClient`. Map to 503.\n * - `BundlerRejectedError` — bundler returned an error. Map to 422\n * (the FE can show the reason — usually `AA21` / `AA34` / etc.).\n */\nexport async function relayUserOp(\n params: RelayUserOpParams,\n): Promise<{ userOpHash: Hex }> {\n if (!params.client) {\n throw new BundlerNotConfiguredError();\n }\n\n try {\n const result = await params.client.relayUserOperation({\n userOp: params.userOp,\n entryPoint: params.entryPoint as string,\n eip7702Auth: params.eip7702Auth,\n });\n return { userOpHash: result.userOpHash };\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new BundlerRejectedError(msg, err);\n }\n}\n","import type { Address, PublicClient, WalletClient } from \"viem\";\nimport { getAddress } from \"viem\";\nimport {\n decodeBatchExecuteCalls,\n getContractAddresses,\n type PartialUserOperation,\n} from \"@pafi-dev/core\";\n\ntype DecodedCall = ReturnType<typeof decodeBatchExecuteCalls>[number];\nimport type { IPointLedger } from \"../../ledger/types\";\nimport type { RelayService } from \"../../relay/relayService\";\nimport type { FeeManager } from \"../../relay/feeManager\";\nimport { IssuerStateError } from \"../../issuer-state/types\";\nimport { PafiSdkError } from \"../../errors\";\n\n/**\n * Structural shape — accepts both the raw `IssuerStateValidator` from\n * `@pafi-dev/issuer/issuer-state` and any host-framework wrapper (e.g.\n * a NestJS Injectable that delegates to it). Only `preValidateMint`\n * is needed on this surface.\n */\nexport interface IssuerStateValidatorLike {\n preValidateMint(pointToken: Address, amount: bigint): Promise<unknown>;\n}\n\n/**\n * v1.4 sig-gated mint handler — mirrors `PTRedeemHandler` on the mint side.\n *\n * Pre-validates against IssuerRegistry + on-chain totalSupply, locks the\n * off-chain balance, builds the sponsored UserOp (mint + PT fee\n * transfer) plus an optional fallback variant (mint only — for\n * paymaster-refused fallback).\n *\n * Caller fetches AA + mintRequest nonces (so issuers can plug in their\n * own composer — gg56 uses a timestamp-key 2D nonce). Caller layers\n * paymaster sponsorship + sponsorAuth on top of the returned UserOps.\n */\nexport type PTClaimErrorCode =\n | \"INVALID_AMOUNT\"\n | \"VALIDATION_FAILED\"\n | \"BUILD_FAILED\";\n\nexport class PTClaimError extends PafiSdkError {\n readonly httpStatus = \"unprocessable\" as const;\n readonly code: PTClaimErrorCode;\n readonly details?: Record<string, unknown>;\n\n constructor(\n code: PTClaimErrorCode,\n message: string,\n details?: Record<string, unknown>,\n ) {\n super(message);\n this.code = code;\n this.details = details;\n }\n}\n\nexport interface PTClaimHandlerConfig {\n ledger: IPointLedger;\n relayService: RelayService;\n provider: PublicClient;\n /** Issuer minter signer wallet — passed through to RelayService.prepareMint. */\n issuerSignerWallet: WalletClient;\n\n /**\n * EIP-712 domain `name` for `MintRequest`. Typically the PointToken\n * ERC-20 name. RelayService will set chainId + verifyingContract from\n * the request.\n */\n pointTokenDomainName: string;\n\n /** Optional — when wired, used to estimate the PT gas-reimbursement fee. */\n feeService?: FeeManager;\n /** Optional — pre-validates issuer status + cap before locking balance. */\n issuerStateValidator?: IssuerStateValidatorLike;\n\n /** How long the off-chain balance lock survives if the mint never lands. Default 15 min. */\n lockDurationMs?: number;\n /** How far ahead of `now` to set the MintRequest deadline. Default 15 min. */\n signatureDeadlineSeconds?: number;\n now?: () => number;\n\n /**\n * Optional override for the v1.6+ MintFeeWrapper address. By default the\n * handler auto-resolves the wrapper from the request's `chainId` via\n * `getContractAddresses(chainId).mintFeeWrapper`. Set this only when:\n * - testing against a non-canonical wrapper deploy, or\n * - the SDK's hardcoded address is stale and you can't bump the SDK yet\n *\n * Pass the dead-zero address to opt OUT of the wrapper path (force direct\n * mint with no fee). Pass `undefined` (default) to use SDK's lookup.\n *\n * Caller responsibility either way:\n * - DAO must have called `IssuerRegistry.setMintFeeWrapper(...)` so\n * `addIssuer` cascades the recipient list.\n * - The PointToken must have been registered with the wrapper (via\n * `addIssuer` cascade or owner-only `wrapper.registerToken`).\n */\n mintFeeWrapperAddress?: Address;\n}\n\n/**\n * Recognised \"no wrapper\" sentinels — both the canonical zero address\n * and the SDK's `PLACEHOLDER_DEAD(\"dead\")` placeholder used when a chain\n * hasn't deployed the wrapper yet. Treated identically: skip wrapper,\n * fall back to direct mint.\n */\nfunction isNoWrapper(address: Address | undefined): boolean {\n if (!address) return true;\n const lower = address.toLowerCase();\n return (\n lower === \"0x0000000000000000000000000000000000000000\" ||\n lower === \"0x000000000000000000000000000000000000dead\"\n );\n}\n\nexport interface PTClaimRequest {\n authenticatedAddress: Address;\n userAddress: Address;\n amount: bigint;\n pointTokenAddress: Address;\n chainId: number;\n /** ERC-4337 account nonce for the user's EOA. */\n aaNonce: bigint;\n /** Current `mintRequestNonces[userAddress]` from PointToken. */\n mintRequestNonce: bigint;\n}\n\nexport interface PTClaimResponse {\n /** Sponsored UserOp — mint + PT fee transfer (when feeAmount > 0). */\n userOp: PartialUserOperation;\n /**\n * Fallback UserOp — mint only, no PT fee transfer. Present only when\n * `feeAmount > 0`. User pays gas in ETH directly.\n */\n fallback?: PartialUserOperation;\n lockId: string;\n feeAmount: bigint;\n signatureDeadline: bigint;\n expiresInSeconds: number;\n /** Decoded calls for the sponsored UserOp (convenience for FE-submit path). */\n calls: DecodedCall[];\n /** Decoded calls for the fallback UserOp (when present). */\n callsFallback?: DecodedCall[];\n}\n\nconst DEFAULT_LOCK_MS = 15 * 60 * 1000;\nconst DEFAULT_SIG_DEADLINE_SEC = 15 * 60;\n\nexport class PTClaimHandler {\n private readonly cfg: PTClaimHandlerConfig & {\n lockDurationMs: number;\n signatureDeadlineSeconds: number;\n now: () => number;\n };\n\n constructor(config: PTClaimHandlerConfig) {\n this.cfg = {\n ...config,\n lockDurationMs: config.lockDurationMs ?? DEFAULT_LOCK_MS,\n signatureDeadlineSeconds:\n config.signatureDeadlineSeconds ?? DEFAULT_SIG_DEADLINE_SEC,\n now: config.now ?? (() => Date.now()),\n };\n }\n\n async handle(request: PTClaimRequest): Promise<PTClaimResponse> {\n if (\n getAddress(request.authenticatedAddress) !==\n getAddress(request.userAddress)\n ) {\n throw new PTClaimError(\n \"VALIDATION_FAILED\",\n `userAddress (${request.userAddress}) does not match authenticated session (${request.authenticatedAddress})`,\n );\n }\n if (request.amount <= 0n) {\n throw new PTClaimError(\"INVALID_AMOUNT\", \"claim amount must be positive\");\n }\n\n if (this.cfg.issuerStateValidator) {\n try {\n await this.cfg.issuerStateValidator.preValidateMint(\n request.pointTokenAddress,\n request.amount,\n );\n } catch (err) {\n if (err instanceof IssuerStateError) throw err;\n throw new PTClaimError(\n \"VALIDATION_FAILED\",\n `issuer-state pre-validate failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n\n const chainAddresses = getContractAddresses(request.chainId);\n const { batchExecutor: batchExecutorAddress } = chainAddresses;\n\n // Resolve wrapper address: explicit override > SDK default > none.\n // Pass `undefined` to RelayService.prepareMint when no wrapper —\n // RelayService treats undefined as direct-mint mode.\n const wrapperOverride = this.cfg.mintFeeWrapperAddress;\n const wrapperFromSdk = chainAddresses.mintFeeWrapper;\n const resolvedWrapper: Address | undefined = wrapperOverride !== undefined\n ? (isNoWrapper(wrapperOverride) ? undefined : wrapperOverride)\n : (isNoWrapper(wrapperFromSdk) ? undefined : wrapperFromSdk);\n\n const lockId = await this.cfg.ledger.lockForMinting(\n request.userAddress,\n request.amount,\n this.cfg.lockDurationMs,\n request.pointTokenAddress,\n );\n\n try {\n const signatureDeadline = BigInt(\n Math.floor(this.cfg.now() / 1000) + this.cfg.signatureDeadlineSeconds,\n );\n\n const feeAmount = this.cfg.feeService\n ? await this.cfg.feeService.estimateGasFee()\n : 0n;\n\n const domain = {\n name: this.cfg.pointTokenDomainName,\n chainId: request.chainId,\n verifyingContract: request.pointTokenAddress,\n };\n\n let userOp: PartialUserOperation;\n try {\n userOp = await this.cfg.relayService.prepareMint({\n userAddress: request.userAddress,\n aaNonce: request.aaNonce,\n batchExecutorAddress,\n pointTokenAddress: request.pointTokenAddress,\n amount: request.amount,\n issuerSignerWallet: this.cfg.issuerSignerWallet,\n domain,\n mintRequestNonce: request.mintRequestNonce,\n deadline: signatureDeadline,\n mintFeeWrapperAddress: resolvedWrapper,\n // No feeAmount/feeRecipient — RelayService auto-resolves.\n });\n } catch (err) {\n throw new PTClaimError(\n \"BUILD_FAILED\",\n `prepareMint failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n let fallback: PartialUserOperation | undefined;\n if (feeAmount > 0n) {\n try {\n fallback = await this.cfg.relayService.prepareMint({\n userAddress: request.userAddress,\n aaNonce: request.aaNonce,\n batchExecutorAddress,\n pointTokenAddress: request.pointTokenAddress,\n amount: request.amount,\n issuerSignerWallet: this.cfg.issuerSignerWallet,\n domain,\n mintRequestNonce: request.mintRequestNonce,\n deadline: signatureDeadline,\n feeAmount: 0n,\n mintFeeWrapperAddress: resolvedWrapper,\n });\n } catch (err) {\n throw new PTClaimError(\n \"BUILD_FAILED\",\n `prepareMint (fallback) failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n\n const calls = decodeBatchExecuteCalls(userOp.callData);\n const callsFallback = fallback\n ? decodeBatchExecuteCalls(fallback.callData)\n : undefined;\n\n return {\n userOp,\n fallback,\n lockId,\n feeAmount,\n signatureDeadline,\n expiresInSeconds: Math.floor(this.cfg.lockDurationMs / 1000),\n calls,\n callsFallback,\n };\n } catch (err) {\n // Release the lock on ANY failure after acquire — fee quote, sign,\n // build, fallback build. Swallow release errors so the original\n // throw surfaces (don't shadow the real failure).\n await this.cfg.ledger.releaseLock(lockId).catch(() => {\n /* noop — lock will auto-expire via TTL */\n });\n throw err;\n }\n }\n}\n","import type { Address } from \"viem\";\nimport { PafiSdkError } from \"../errors\";\n\nexport interface IssuerRegistryRecord {\n issuerAddress: Address;\n signerAddress: Address;\n name: string;\n symbol: string;\n active: boolean;\n pointToken: Address;\n mintingOracle: Address;\n}\n\n/**\n * v1.6 — per-token cap config read from `MintingOracle.tokenCaps()`.\n * Caps moved off the issuer record so a single issuer can run many\n * PointTokens with different supply policies.\n */\nexport interface TokenCapRecord {\n declaredTotalSupply: bigint;\n capBasisPoints: number;\n}\n\nexport interface PreValidateMintResult {\n /** Registry record read at pre-validation time. */\n issuer: IssuerRegistryRecord;\n /** Per-token cap config from MintingOracle. */\n tokenCap: TokenCapRecord;\n /** Current on-chain PointToken.totalSupply(). */\n totalSupply: bigint;\n /** declaredTotalSupply × capBasisPoints / 10000. */\n hardCap: bigint;\n /** hardCap − totalSupply (clamped to 0). */\n remaining: bigint;\n}\n\n/**\n * Thrown by `IssuerStateValidator.preValidateMint()`.\n * `code` maps 1:1 to the HTTP error the issuer API surfaces to clients.\n *\n * `MINT_CAP_EXCEEDED` is `safeToRetry: true` because the cap may free\n * up between requests as other mints expire or settle. The other two\n * codes are configuration-time states — the FE can't recover by retry.\n */\nexport type IssuerStateErrorCode =\n | \"ISSUER_NOT_REGISTERED\"\n | \"ISSUER_INACTIVE\"\n | \"MINT_CAP_EXCEEDED\";\n\nexport class IssuerStateError extends PafiSdkError {\n readonly httpStatus = \"unprocessable\" as const;\n readonly code: IssuerStateErrorCode;\n readonly details?: Record<string, unknown>;\n readonly safeToRetry: boolean;\n\n constructor(\n code: IssuerStateErrorCode,\n message: string,\n details?: Record<string, unknown>,\n ) {\n super(message);\n this.code = code;\n this.details = details;\n this.safeToRetry = code === \"MINT_CAP_EXCEEDED\";\n }\n}\n","import type { Address, PublicClient } from \"viem\";\nimport {\n BROKER_HASHES,\n ORDERLY_RELAY_ABI,\n ORDERLY_VAULT_ABI,\n ORDERLY_VAULT_ADDRESSES,\n TOKEN_HASHES,\n buildPerpDepositViaRelay,\n computeAccountId,\n decodeBatchExecuteCalls,\n getContractAddresses,\n quoteOperatorFeeUsdt,\n type PartialUserOperation,\n} from \"@pafi-dev/core\";\n\ntype DecodedCall = ReturnType<typeof decodeBatchExecuteCalls>[number];\nimport { PafiSdkError } from \"../../errors\";\n\n/**\n * Orderly perp-deposit handler — builds the sponsored + fallback\n * UserOps for the PAFI Relay path.\n *\n * Resolves USDC + verifies the broker is whitelisted on the Vault,\n * quotes the Relay's USDC fee (covers LayerZero msg.value out of the\n * Relay's ETH reserve), then builds two UserOps:\n *\n * - **sponsored** — USDC.transfer(fee, PAFI) + USDC.approve(relay,\n * total) + Relay.deposit(req). User reimburses gas in USDC\n * (input-token rule — user holds USDC at start of batch).\n * - **fallback** — USDC.approve + Relay.deposit only. User pays\n * ERC-4337 gas in ETH. Built only when `feeAmount > 0`.\n *\n * `maxFee` is set to `quote × 1.5` — slippage cap on the Relay's\n * USD-pricing during the inclusion window. If the actual fee at\n * execution exceeds maxFee the Relay reverts (`FeeExceedsMax`).\n *\n * v0.7 — Switched gas fee from PT (output-side, mixed token UX) to\n * USDC (input-side, single token UX). User now only needs USDC to\n * complete the deposit, no separate PT balance required.\n */\nexport type PerpDepositErrorCode =\n | \"PERP_DEPOSIT_UNAVAILABLE\"\n | \"BROKER_NOT_WHITELISTED\"\n | \"RELAY_FEE_EXCEEDS_AMOUNT\"\n | \"FEE_EXCEEDS_AMOUNT\"\n | \"INVALID_AMOUNT\";\n\nexport class PerpDepositError extends PafiSdkError {\n readonly httpStatus = \"unprocessable\" as const;\n readonly code: PerpDepositErrorCode;\n readonly safeToRetry: boolean;\n\n constructor(code: PerpDepositErrorCode, message: string) {\n super(message);\n this.code = code;\n this.safeToRetry = code === \"RELAY_FEE_EXCEEDS_AMOUNT\";\n }\n}\n\nexport interface PerpDepositHandlerConfig {\n provider: PublicClient;\n /**\n * Slippage premium applied on top of the Relay quote when computing\n * `maxFee`. Default 50% (factor 150). The Relay reverts if actual fee\n * exceeds `maxFee` so a generous cap reduces re-quote churn.\n */\n maxFeePremiumBps?: number;\n /**\n * Gas units used by `quoteOperatorFeeUsdt` to size the USDC gas\n * reimbursement. Default 500_000 (covers approve + Relay.deposit\n * + LayerZero call). Pass a tighter Pimlico estimate if available.\n */\n gasUnits?: bigint;\n /** Premium bps for `quoteOperatorFeeUsdt`. Default 12_000 (1.2×). */\n gasPremiumBps?: number;\n}\n\nexport interface PerpDepositRequest {\n userAddress: Address;\n chainId: number;\n amount: bigint;\n brokerId: keyof typeof BROKER_HASHES;\n /** ERC-4337 account nonce. */\n aaNonce: bigint;\n}\n\nexport interface PerpDepositResponse {\n userOp: PartialUserOperation;\n fallback?: PartialUserOperation;\n\n feeAmount: bigint;\n relayTokenFee: bigint;\n maxFee: bigint;\n netDeposit: bigint;\n accountId: `0x${string}`;\n brokerHash: `0x${string}`;\n usdcAddress: Address;\n relayAddress: Address;\n\n calls: DecodedCall[];\n callsFallback?: DecodedCall[];\n}\n\nconst DEFAULT_MAX_FEE_PREMIUM_BPS = 5000; // 50%\n\nexport class PerpDepositHandler {\n private readonly cfg: PerpDepositHandlerConfig & {\n maxFeePremiumBps: number;\n };\n\n constructor(config: PerpDepositHandlerConfig) {\n this.cfg = {\n ...config,\n maxFeePremiumBps:\n config.maxFeePremiumBps ?? DEFAULT_MAX_FEE_PREMIUM_BPS,\n };\n }\n\n async handle(request: PerpDepositRequest): Promise<PerpDepositResponse> {\n if (request.amount <= 0n) {\n throw new PerpDepositError(\"INVALID_AMOUNT\", \"amount must be positive\");\n }\n\n const brokerHash = BROKER_HASHES[request.brokerId];\n const tokenHash = TOKEN_HASHES.USDC;\n\n const vault = ORDERLY_VAULT_ADDRESSES[request.chainId];\n if (!vault) {\n throw new PerpDepositError(\n \"PERP_DEPOSIT_UNAVAILABLE\",\n `no Orderly Vault for chainId ${request.chainId}`,\n );\n }\n\n const { orderlyRelay: relayAddress, pafiFeeRecipient } =\n getContractAddresses(request.chainId);\n\n const [usdcAddress, brokerAllowed] = await Promise.all([\n this.cfg.provider.readContract({\n address: vault,\n abi: ORDERLY_VAULT_ABI,\n functionName: \"getAllowedToken\",\n args: [tokenHash],\n }) as Promise<Address>,\n this.cfg.provider.readContract({\n address: vault,\n abi: ORDERLY_VAULT_ABI,\n functionName: \"getAllowedBroker\",\n args: [brokerHash],\n }) as Promise<boolean>,\n ]);\n\n if (!brokerAllowed) {\n throw new PerpDepositError(\n \"BROKER_NOT_WHITELISTED\",\n `broker \"${request.brokerId}\" is not whitelisted on Orderly Vault`,\n );\n }\n\n const accountId = computeAccountId(request.userAddress, brokerHash);\n\n const requestForQuote = {\n token: usdcAddress,\n receiver: request.userAddress,\n brokerHash,\n totalAmount: request.amount,\n maxFee: 0n,\n };\n\n // Quote USDC gas fee in parallel with the Relay's tokenFee. USDC\n // is 6-dec stable; `quoteOperatorFeeUsdt` returns USD raw at 6 dec\n // (semantically identical for USDC).\n const [relayTokenFee, usdcGasFee] = await Promise.all([\n this.cfg.provider.readContract({\n address: relayAddress,\n abi: ORDERLY_RELAY_ABI,\n functionName: \"quoteTokenFee\",\n args: [requestForQuote],\n }) as Promise<bigint>,\n quoteOperatorFeeUsdt({\n provider: this.cfg.provider,\n chainId: request.chainId,\n gasUnits: this.cfg.gasUnits,\n premiumBps: this.cfg.gasPremiumBps,\n }),\n ]);\n\n if (relayTokenFee >= request.amount) {\n throw new PerpDepositError(\n \"RELAY_FEE_EXCEEDS_AMOUNT\",\n `Relay quoted fee ${relayTokenFee} >= deposit amount ${request.amount}`,\n );\n }\n if (usdcGasFee > 0n && usdcGasFee >= request.amount) {\n throw new PerpDepositError(\n \"FEE_EXCEEDS_AMOUNT\",\n `USDC gas fee ${usdcGasFee} >= deposit amount ${request.amount}`,\n );\n }\n const maxFee =\n (relayTokenFee * BigInt(10000 + this.cfg.maxFeePremiumBps)) / 10000n;\n\n const depositReq = {\n token: usdcAddress,\n receiver: request.userAddress,\n brokerHash,\n totalAmount: request.amount,\n maxFee,\n };\n\n const sponsoredOp = buildPerpDepositViaRelay({\n userAddress: request.userAddress,\n aaNonce: request.aaNonce,\n relayAddress,\n request: depositReq,\n gasFeeUsdc: usdcGasFee,\n gasFeeUsdcRecipient: pafiFeeRecipient,\n });\n\n const fallbackOp =\n usdcGasFee > 0n\n ? buildPerpDepositViaRelay({\n userAddress: request.userAddress,\n aaNonce: request.aaNonce,\n relayAddress,\n request: depositReq,\n })\n : undefined;\n\n return {\n userOp: sponsoredOp,\n fallback: fallbackOp,\n feeAmount: usdcGasFee,\n relayTokenFee,\n maxFee,\n netDeposit: request.amount - relayTokenFee,\n accountId,\n brokerHash,\n usdcAddress,\n relayAddress,\n calls: decodeBatchExecuteCalls(sponsoredOp.callData),\n callsFallback: fallbackOp\n ? decodeBatchExecuteCalls(fallbackOp.callData)\n : undefined,\n };\n }\n}\n","import type { Address, Hex } from \"viem\";\nimport {\n ENTRY_POINT_V08,\n buildDelegationUserOp,\n buildEip7702Authorization,\n computeUserOpHash,\n buildUserOpTypedData,\n getContractAddresses,\n serializeUserOpToJsonRpc,\n} from \"@pafi-dev/core\";\nimport { requestPaymaster, relayUserOp } from \"../pafi-backend/helpers\";\nimport type { PafiBackendClient } from \"../pafi-backend/client\";\nimport {\n serializeEntryToJsonRpc,\n serializeUserOpTypedData,\n type IPendingUserOpStore,\n type SerializedUserOpTypedData,\n} from \"../userop-store\";\nimport {\n PendingUserOpForbiddenError,\n PendingUserOpNotFoundError,\n} from \"./mobileHandlers\";\nimport { getAddress } from \"viem\";\n\n/**\n * EIP-7702 delegation flow uses the same mobile prepare/submit pattern\n * as claim/redeem:\n *\n * prepare: user signs `authorization` locally (Privy hook)\n * → POST /delegate/prepare with `authSig`\n * → backend builds delegation-anchor UserOp + paymaster\n * sponsorship (with eip7702Auth in pm_sponsorUserOperation)\n * + persists pending entry → returns `{ lockId,\n * userOpHash, typedData }`\n *\n * submit: user signs `userOpHash` locally (signTypedData)\n * → POST /delegate/submit `{ lockId, userOpSig }`\n * → backend retrieves entry + embeds userOpSig + relays\n * with `eip7702Auth` field on the UserOp\n *\n * **Why two signatures?** EIP-7702 authorization sets the EOA's\n * bytecode (delegation), but `validateUserOp` ALSO requires the user\n * to sign the userOpHash with ECDSA — Simple7702Account.validateUserOp\n * recovers the signer from `userOp.signature` and compares to\n * `address(this)`. An empty `signature: \"0x\"` reverts with OZ's\n * `ECDSAInvalidSignatureLength(uint256)` (selector `0xfce698f7`) inside\n * AA23. v0.7.7 — replaced single-shot `handleDelegateSubmit` that\n * tried to submit unsigned UserOps with this 2-step prepare/submit.\n */\nexport interface HandleDelegatePrepareParams {\n userAddress: Address;\n chainId: number;\n /** EOA tx nonce; mobile fetches via publicClient.getTransactionCount. */\n delegationNonce: bigint;\n /** ERC-4337 account nonce. Caller fetches via EntryPoint.getNonce. */\n aaNonce: bigint;\n /**\n * 65-byte secp256k1 signature over the EIP-7702 authorization hash\n * (chainId, address=batchExecutor, nonce). User signs locally via\n * Privy `useSign7702Authorization` BEFORE calling this endpoint.\n */\n authSig: Hex | string;\n fees: { maxFeePerGas?: bigint; maxPriorityFeePerGas?: bigint };\n\n /** Issuer-generated lock id (UUID) keying the pending entry. */\n lockId: string;\n store: IPendingUserOpStore;\n /** Pending entry TTL (seconds). Match the issuer's mobile lock duration. */\n ttlSeconds: number;\n\n pafiBackendClient?: PafiBackendClient | null;\n onWarning?: (msg: string) => void;\n\n gasLimits?: {\n callGasLimit?: bigint;\n verificationGasLimit?: bigint;\n preVerificationGas?: bigint;\n };\n}\n\nexport interface HandleDelegatePrepareResult {\n lockId: string;\n userOpHash: Hex;\n typedData: SerializedUserOpTypedData;\n expiresInSeconds: number;\n /** True when paymaster sponsorship attached (gas free). */\n isSponsored: boolean;\n}\n\nexport interface HandleDelegateSubmitParams {\n lockId: string;\n /** Authenticated user EOA — must match the entry's `sender`. */\n authenticatedAddress: Address;\n /** User signature over the persisted userOpHash. */\n userOpSig: Hex;\n store: IPendingUserOpStore;\n pafiBackendClient?: PafiBackendClient | null;\n /** Defaults to `ENTRY_POINT_V08`. */\n entryPoint?: string;\n}\n\nexport interface HandleDelegateSubmitResult {\n userOpHash: Hex;\n}\n\nconst DEFAULT_DELEGATE_GAS = {\n callGasLimit: 100_000n,\n verificationGasLimit: 150_000n,\n preVerificationGas: 50_000n,\n};\n\n/**\n * Build delegation-anchor UserOp + obtain paymaster sponsorship +\n * persist as a pending entry. Mobile signs the returned userOpHash\n * locally and submits via `handleDelegateSubmit`.\n */\nexport async function handleDelegatePrepare(\n params: HandleDelegatePrepareParams,\n): Promise<HandleDelegatePrepareResult> {\n const { batchExecutor } = getContractAddresses(params.chainId);\n\n // 1. Build the delegation-anchor UserOp (self-call EOA, empty calldata).\n const partial = buildDelegationUserOp({\n userAddress: params.userAddress,\n aaNonce: params.aaNonce,\n gasLimits: {\n callGasLimit:\n params.gasLimits?.callGasLimit ?? DEFAULT_DELEGATE_GAS.callGasLimit,\n verificationGasLimit:\n params.gasLimits?.verificationGasLimit ??\n DEFAULT_DELEGATE_GAS.verificationGasLimit,\n preVerificationGas:\n params.gasLimits?.preVerificationGas ??\n DEFAULT_DELEGATE_GAS.preVerificationGas,\n },\n });\n\n const userOp = {\n sender: partial.sender,\n nonce: partial.nonce,\n callData: partial.callData,\n callGasLimit: partial.callGasLimit,\n verificationGasLimit: partial.verificationGasLimit,\n preVerificationGas: partial.preVerificationGas,\n maxFeePerGas: params.fees.maxFeePerGas ?? 0n,\n maxPriorityFeePerGas: params.fees.maxPriorityFeePerGas ?? 0n,\n };\n\n // 2. Build the EIP-7702 authorization tuple from the user's authSig.\n const authorization = buildEip7702Authorization({\n chainId: params.chainId,\n address: batchExecutor as Address,\n nonce: params.delegationNonce,\n authSig: params.authSig,\n });\n\n // 3. Request paymaster sponsorship — `eip7702Auth` lets Pimlico's\n // pm_sponsorUserOperation simulate validateUserOp on the freshly\n // delegated EOA bytecode (otherwise `AA20 account not deployed`).\n const paymasterFields = await requestPaymaster({\n client: params.pafiBackendClient,\n chainId: params.chainId,\n scenario: \"delegate\",\n userOp,\n pointTokenAddress: batchExecutor as Address,\n eip7702Auth: authorization,\n onWarning: params.onWarning,\n });\n\n // 4. Merge paymaster gas overrides BEFORE computing userOpHash —\n // Pimlico re-estimates gas during sponsor + signs over the new\n // values, so the hash must be over post-merge fields or both\n // paymaster sig (AA34) and user sig (AA24) fail.\n const merged = {\n sender: userOp.sender,\n nonce: userOp.nonce,\n callData: userOp.callData,\n callGasLimit:\n paymasterFields?.callGasLimit ?? userOp.callGasLimit,\n verificationGasLimit:\n paymasterFields?.verificationGasLimit ?? userOp.verificationGasLimit,\n preVerificationGas:\n paymasterFields?.preVerificationGas ?? userOp.preVerificationGas,\n maxFeePerGas:\n paymasterFields?.maxFeePerGas ?? userOp.maxFeePerGas,\n maxPriorityFeePerGas:\n paymasterFields?.maxPriorityFeePerGas ?? userOp.maxPriorityFeePerGas,\n paymaster: paymasterFields?.paymaster,\n paymasterVerificationGasLimit:\n paymasterFields?.paymasterVerificationGasLimit,\n paymasterPostOpGasLimit: paymasterFields?.paymasterPostOpGasLimit,\n paymasterData: paymasterFields?.paymasterData,\n };\n\n const userOpHash = computeUserOpHash(merged, params.chainId);\n const typed = buildUserOpTypedData(merged, params.chainId);\n\n // 5. Persist pending entry — submit will read it back.\n await params.store.save(\n params.lockId,\n {\n sender: merged.sender,\n nonce: merged.nonce.toString(10),\n callData: merged.callData,\n callGasLimit: merged.callGasLimit.toString(10),\n verificationGasLimit: merged.verificationGasLimit.toString(10),\n preVerificationGas: merged.preVerificationGas.toString(10),\n maxFeePerGas: merged.maxFeePerGas.toString(10),\n maxPriorityFeePerGas: merged.maxPriorityFeePerGas.toString(10),\n ...(merged.paymaster ? { paymaster: merged.paymaster } : {}),\n ...(merged.paymasterVerificationGasLimit\n ? {\n paymasterVerificationGasLimit:\n merged.paymasterVerificationGasLimit.toString(10),\n }\n : {}),\n ...(merged.paymasterPostOpGasLimit\n ? {\n paymasterPostOpGasLimit:\n merged.paymasterPostOpGasLimit.toString(10),\n }\n : {}),\n ...(merged.paymasterData ? { paymasterData: merged.paymasterData } : {}),\n chainId: params.chainId,\n userOpHash,\n eip7702Auth: authorization,\n },\n params.ttlSeconds,\n );\n\n return {\n lockId: params.lockId,\n userOpHash,\n typedData: serializeUserOpTypedData(typed),\n expiresInSeconds: params.ttlSeconds,\n isSponsored: !!paymasterFields,\n };\n}\n\n/**\n * Retrieve the persisted delegate UserOp, embed the user's signature,\n * and submit to the bundler with the cached `eip7702Auth` field.\n *\n * Throws:\n * - `PendingUserOpNotFoundError` — entry expired or already submitted (404)\n * - `PendingUserOpForbiddenError` — sender mismatch (403)\n * - `BundlerNotConfiguredError` — `pafiBackendClient` missing (503)\n * - `BundlerRejectedError` — bundler rejected the UserOp (422)\n */\nexport async function handleDelegateSubmit(\n params: HandleDelegateSubmitParams,\n): Promise<HandleDelegateSubmitResult> {\n const entry = await params.store.get(params.lockId);\n if (!entry) {\n throw new PendingUserOpNotFoundError(params.lockId);\n }\n if (\n getAddress(entry.sender) !== getAddress(params.authenticatedAddress)\n ) {\n throw new PendingUserOpForbiddenError(params.lockId);\n }\n if (!entry.eip7702Auth) {\n // Should never happen for delegate scenario — the prepare step\n // always persists eip7702Auth. Defensive: refuse rather than\n // silently relay an unsigned-delegation UserOp.\n throw new Error(\n `delegate entry ${params.lockId} missing eip7702Auth — prepare step did not run correctly`,\n );\n }\n\n const userOpJson = serializeEntryToJsonRpc(entry, params.userOpSig, \"sponsored\");\n\n const result = await relayUserOp({\n client: params.pafiBackendClient,\n userOp: userOpJson,\n entryPoint: params.entryPoint ?? ENTRY_POINT_V08,\n eip7702Auth: entry.eip7702Auth,\n });\n\n await params.store.delete(params.lockId);\n\n return { userOpHash: result.userOpHash };\n}\n\n// `serializeUserOpToJsonRpc` kept as a re-imported reference so tooling\n// that drops unused imports doesn't strip it.\nvoid serializeUserOpToJsonRpc;\n","import { randomUUID } from \"node:crypto\";\nimport type { Address, Hex, PublicClient, WalletClient } from \"viem\";\nimport { getAddress } from \"viem\";\nimport {\n buildAndSignSponsorAuth,\n computeAuthorizationHash,\n decodeBatchExecuteCalls,\n encodeBatchExecute,\n ENTRY_POINT_V08,\n getContractAddresses,\n parseEip7702DelegatedAddress,\n type BuiltSponsorAuth,\n type SponsorshipScenario,\n} from \"@pafi-dev/core\";\nimport type { IssuerService } from \"../config\";\nimport type { IPointLedger } from \"../ledger/types\";\nimport type { IPendingUserOpStore } from \"../userop-store/types\";\nimport type { PafiBackendClient } from \"../pafi-backend/client\";\nimport type { PTRedeemHandler } from \"./handlers/ptRedeemHandler\";\nimport type { PTClaimHandler } from \"./handlers/ptClaimHandler\";\nimport type { PerpDepositHandler } from \"./handlers/perpDepositHandler\";\nimport {\n handleClaimStatus,\n handleRedeemStatus,\n type MintStatusResponse,\n type BurnStatusResponse,\n} from \"./statusHandlers\";\nimport {\n handleMobilePrepare,\n handleMobileSubmit,\n type HandleMobilePrepareResult,\n} from \"./mobileHandlers\";\nimport {\n handleDelegatePrepare,\n handleDelegateSubmit,\n} from \"./delegateHandler\";\nimport type { SerializedUserOpTypedData } from \"../userop-store\";\n\n/**\n * Adapter that absorbs every \"framework-agnostic\" endpoint body into a\n * single class so issuer controllers stay thin (one line per endpoint).\n *\n * What this absorbs:\n * - Reading + reshaping IssuerApiHandlers responses into wire DTOs\n * (bigint → string, etc.)\n * - Composing handler.handle() output with sponsorAuth + decoded calls\n * - Wiring handleMobilePrepare / handleMobileSubmit / handleClaimStatus\n * - Building the EIP-7702 delegate UserOp + relay\n * - Quoting PT → USDT for the cashout preview\n *\n * What stays in the issuer controller:\n * - HTTP routing decorators (`@Get`, `@Post`, `@UseGuards`)\n * - Auth context extraction (`@User() user: AuthContext`)\n * - Body DTO classes (with framework-specific decorators)\n * - Nonce composer (issuer-specific; e.g. gg56 uses 2D timestamp keys)\n *\n * Every method that can throw a typed SDK error throws `PafiSdkError` —\n * the controller wraps every call with `try/catch + mapSdkError`, or\n * the adapter pre-binds an `errorMapper` and auto-translates.\n */\n\nexport interface IssuerApiAdapterConfig {\n issuerService: IssuerService;\n ledger: IPointLedger;\n provider: PublicClient;\n /** Issuer signer wallet — used for `buildSponsorAuth` (EIP-712 sign). */\n issuerSignerWallet: WalletClient;\n /** Optional issuer id — when omitted, sponsorAuth is skipped (returns `undefined`). */\n pafiIssuerId?: string;\n\n /** Sig-gated mint handler. Required for `claim` / `claimPrepare`. */\n ptClaimHandler?: PTClaimHandler | null;\n /** Reverse-flow handler. Required for `redeem` / `redeemPrepare`. */\n ptRedeemHandler?: PTRedeemHandler | null;\n /** Orderly perp-deposit handler. Required for `perpDeposit`. */\n perpHandler?: PerpDepositHandler | null;\n\n /** Pending UserOp store — required for mobile prepare/submit. */\n pendingUserOpStore: IPendingUserOpStore;\n /** PAFI backend client — required for mobile submit + delegate submit + status fallback. */\n pafiBackendClient?: PafiBackendClient | null;\n\n /** Optional logger surface for non-fatal warnings. */\n onWarning?: (msg: string) => void;\n}\n\n// ---------------------------------------------------------------------------\n// Wire DTO shapes\n//\n// Issuer controllers can declare their own DTO classes that match these\n// shapes — TypeScript structural typing ensures the adapter's output\n// fits the controller's response type without import coupling.\n// ---------------------------------------------------------------------------\n\nexport interface ConfigDto {\n chainId: number;\n contracts: Record<string, string | undefined>;\n}\n\nexport interface GasFeeDto {\n gasFeeUsdt: string;\n}\n\nexport interface PoolsDto {\n pools: unknown[];\n}\n\nexport interface UserDto {\n mintRequestNonce: string;\n receiverConsentNonce: string;\n offChainBalance: string;\n onChainBalance: string;\n totalBalance: string;\n /** @deprecated alias for `offChainBalance` */\n balance: string;\n isMinter: boolean;\n}\n\n// QuoteDto removed (2026-04-27) — quote moved to @pafi-dev/trading.\n\nexport interface DecodedCallDto {\n to: string;\n data: string;\n value: string;\n}\n\nexport interface ClaimDto {\n calls: DecodedCallDto[];\n callsFallback?: DecodedCallDto[];\n feeAmount: string;\n lockId: string;\n signatureDeadline: string;\n sponsorAuth?: BuiltSponsorAuth;\n}\n\nexport interface RedeemDto {\n calls: DecodedCallDto[];\n callsFallback?: DecodedCallDto[];\n feeAmount: string;\n lockId: string;\n lockIdFallback?: string;\n netCreditAmount: string;\n netCreditAmountFallback?: string;\n expiresInSeconds: number;\n signatureDeadline: string;\n sponsorAuth?: BuiltSponsorAuth;\n}\n\n// SwapDto removed (2026-04-27) — swap moved to @pafi-dev/trading.\n\nexport interface PerpDepositDto {\n calls: DecodedCallDto[];\n callsFallback?: DecodedCallDto[];\n relayTokenFee: string;\n maxFee: string;\n netDeposit: string;\n ptGasFee: string;\n accountId: Hex;\n brokerHash: Hex;\n usdcAddress: Address;\n relayAddress: Address;\n sponsorAuth?: BuiltSponsorAuth;\n}\n\nexport interface MobilePrepareDto {\n lockId: string;\n userOpHash: Hex;\n typedData: SerializedUserOpTypedData;\n userOpHashFallback?: Hex;\n typedDataFallback?: SerializedUserOpTypedData;\n feeAmount: string;\n signatureDeadline: string;\n expiresInSeconds: number;\n sponsored: boolean;\n needsDelegation: boolean;\n}\n\nexport interface RedeemPrepareDto extends MobilePrepareDto {\n netCreditAmount: string;\n netCreditAmountFallback?: string;\n}\n\nexport interface MobileSubmitDto {\n userOpHash: Hex;\n}\n\nexport interface DelegateStatusDto {\n isDelegated: boolean;\n batchExecutorAddress: Address;\n /**\n * EOA tx count fetched on-chain via `getTransactionCount(blockTag: 'pending')`.\n * Mobile passes this VERBATIM into Privy `signAuthorization({ ..., nonce })` —\n * MUST NOT hardcode `0` or fetch independently. For a fresh embedded wallet\n * it will be `\"0\"`, but re-delegation or post-activity wallets will be > 0.\n * Returned as decimal string (preserves bigint precision over JSON).\n */\n delegationNonce: string;\n chainId: number;\n}\n\nexport interface DelegatePrepareDto {\n /**\n * v0.7.7 — refactored to mobile prepare/submit pattern. Mobile signs\n * the EIP-7702 authorization LOCALLY (Privy `signAuthorization`),\n * then signs `userOpHash` (or `typedData`) BEFORE submit. See\n * `handleDelegatePrepare` for rationale.\n *\n * Pre-v0.7.7 callers expected `{ authorizationHash, delegationNonce,\n * batchExecutorAddress, chainId }` — `delegationNonce` +\n * `batchExecutorAddress` retained for back-compat so mobile can\n * compute the authorization hash if needed; `authorizationHash`\n * removed (mobile's Privy hook computes it locally).\n */\n lockId: string;\n userOpHash: Hex;\n typedData: SerializedUserOpTypedData;\n expiresInSeconds: number;\n isSponsored: boolean;\n /** Echoed for mobile to recompute the authorization hash if desired. */\n delegationNonce: string;\n batchExecutorAddress: Address;\n chainId: number;\n}\n\n// ---------------------------------------------------------------------------\n// Adapter\n// ---------------------------------------------------------------------------\n\nexport class AdapterMisconfiguredError extends Error {\n readonly code = \"ADAPTER_MISCONFIGURED\" as const;\n constructor(message: string) {\n super(message);\n this.name = \"AdapterMisconfiguredError\";\n }\n}\n\nexport class IssuerApiAdapter {\n private readonly cfg: IssuerApiAdapterConfig;\n\n constructor(config: IssuerApiAdapterConfig) {\n if (config.ptClaimHandler) {\n if (typeof config.ledger.bindMintUserOpHash !== \"function\") {\n throw new AdapterMisconfiguredError(\n \"ledger.bindMintUserOpHash is required when ptClaimHandler is wired (mobile claim flow). \" +\n \"Implement it on your IPointLedger or omit ptClaimHandler from IssuerApiAdapter config.\",\n );\n }\n if (typeof config.ledger.getMintLock !== \"function\") {\n throw new AdapterMisconfiguredError(\n \"ledger.getMintLock is required when ptClaimHandler is wired — claimStatus uses it to look up the lock.\",\n );\n }\n }\n if (config.ptRedeemHandler) {\n if (typeof config.ledger.reservePendingCredit !== \"function\") {\n throw new AdapterMisconfiguredError(\n \"ledger.reservePendingCredit is required when ptRedeemHandler is wired (burn/redeem reverse flow). \" +\n \"PTRedeemHandler also enforces this at construction; see ledger/types.ts comments.\",\n );\n }\n if (typeof config.ledger.bindCreditUserOpHash !== \"function\") {\n throw new AdapterMisconfiguredError(\n \"ledger.bindCreditUserOpHash is required when ptRedeemHandler is wired (mobile redeem flow).\",\n );\n }\n if (typeof config.ledger.getPendingCredit !== \"function\") {\n throw new AdapterMisconfiguredError(\n \"ledger.getPendingCredit is required when ptRedeemHandler is wired — redeemStatus uses it to look up the credit.\",\n );\n }\n }\n this.cfg = config;\n }\n\n // ------------------------------ Read endpoints ---------------------------\n\n async config(chainId: number): Promise<ConfigDto> {\n const result = await this.cfg.issuerService.api.handleConfig(chainId);\n return {\n chainId: result.chainId,\n contracts: result.contracts as Record<string, string | undefined>,\n };\n }\n\n async gasFee(): Promise<GasFeeDto> {\n const result = await this.cfg.issuerService.api.handleGasFee();\n return { gasFeeUsdt: result.gasFeeUsdt.toString() };\n }\n\n async pools(\n authenticatedAddress: Address,\n chainId: number,\n pointTokenAddress: Address,\n ): Promise<PoolsDto> {\n const result = await this.cfg.issuerService.api.handlePools(\n authenticatedAddress,\n { chainId, pointTokenAddress: getAddress(pointTokenAddress) },\n );\n return { pools: result.pools };\n }\n\n async user(\n authenticatedAddress: Address,\n chainId: number,\n userAddress: Address,\n pointTokenAddress: Address,\n ): Promise<UserDto> {\n const result = await this.cfg.issuerService.api.handleUser(\n authenticatedAddress,\n {\n chainId,\n userAddress: getAddress(userAddress),\n pointTokenAddress: getAddress(pointTokenAddress),\n },\n );\n return {\n mintRequestNonce: result.mintRequestNonce.toString(),\n receiverConsentNonce: result.receiverConsentNonce.toString(),\n offChainBalance: result.offChainBalance.toString(),\n onChainBalance: result.onChainBalance.toString(),\n totalBalance: result.totalBalance.toString(),\n balance: result.offChainBalance.toString(),\n isMinter: result.isMinter,\n };\n }\n\n // quote() removed (2026-04-27) — FE PAFI calls @pafi-dev/trading\n // directly. Issuer SDK doesn't ship swap/quote anymore.\n\n // ------------------------------ Action endpoints -------------------------\n\n async claim(input: {\n authenticatedAddress: Address;\n chainId: number;\n pointTokenAddress: Address;\n amount: bigint;\n aaNonce: bigint;\n mintRequestNonce: bigint;\n }): Promise<ClaimDto> {\n const ptClaimHandler = this.assertHandler(\n this.cfg.ptClaimHandler,\n \"ptClaimHandler\",\n \"claim\",\n );\n const pointTokenAddress = getAddress(input.pointTokenAddress);\n const result = await ptClaimHandler.handle({\n authenticatedAddress: input.authenticatedAddress,\n userAddress: input.authenticatedAddress,\n amount: input.amount,\n pointTokenAddress,\n chainId: input.chainId,\n aaNonce: input.aaNonce,\n mintRequestNonce: input.mintRequestNonce,\n });\n\n const sponsorAuth = await this.buildSponsorAuth(\n input.authenticatedAddress,\n result.userOp.callData,\n input.chainId,\n \"mint\",\n );\n\n return {\n calls: result.calls,\n callsFallback: result.callsFallback,\n feeAmount: result.feeAmount.toString(),\n lockId: result.lockId,\n signatureDeadline: result.signatureDeadline.toString(),\n sponsorAuth,\n };\n }\n\n async redeem(input: {\n authenticatedAddress: Address;\n chainId: number;\n amount: bigint;\n aaNonce: bigint;\n }): Promise<RedeemDto> {\n this.assertRedeemHandler();\n const response = await this.cfg.ptRedeemHandler!.handle({\n userAddress: input.authenticatedAddress,\n authenticatedAddress: input.authenticatedAddress,\n amount: input.amount,\n aaNonce: input.aaNonce,\n chainId: input.chainId,\n });\n\n const sponsorAuth = await this.buildSponsorAuth(\n input.authenticatedAddress,\n response.userOp.callData,\n input.chainId,\n \"burn\",\n );\n\n return {\n calls: decodeBatchExecuteCalls(response.userOp.callData),\n callsFallback: response.fallback\n ? decodeBatchExecuteCalls(response.fallback.userOp.callData)\n : undefined,\n feeAmount: response.feeAmount.toString(),\n lockId: response.lockId,\n lockIdFallback: response.fallback?.lockId,\n netCreditAmount: response.netCreditAmount.toString(),\n netCreditAmountFallback: response.fallback?.netCreditAmount.toString(),\n expiresInSeconds: response.expiresInSeconds,\n signatureDeadline: response.signatureDeadline.toString(),\n sponsorAuth,\n };\n }\n\n // swap() removed (2026-04-27) — moved to @pafi-dev/trading.\n // PAFI's web FE calls TradingHandlers.handleSwap directly.\n\n async perpDeposit(input: {\n authenticatedAddress: Address;\n chainId: number;\n amount: bigint;\n brokerId: Parameters<PerpDepositHandler[\"handle\"]>[0][\"brokerId\"];\n aaNonce: bigint;\n }): Promise<PerpDepositDto> {\n const perpHandler = this.assertHandler(\n this.cfg.perpHandler,\n \"perpHandler\",\n \"perpDeposit\",\n );\n const result = await perpHandler.handle({\n userAddress: input.authenticatedAddress,\n chainId: input.chainId,\n amount: input.amount,\n brokerId: input.brokerId,\n aaNonce: input.aaNonce,\n });\n\n const sponsorAuth = await this.buildSponsorAuth(\n input.authenticatedAddress,\n result.userOp.callData,\n input.chainId,\n \"perp-deposit\",\n );\n\n return {\n calls: result.calls,\n callsFallback: result.callsFallback,\n relayTokenFee: result.relayTokenFee.toString(),\n maxFee: result.maxFee.toString(),\n netDeposit: result.netDeposit.toString(),\n ptGasFee: result.feeAmount.toString(),\n accountId: result.accountId,\n brokerHash: result.brokerHash,\n usdcAddress: result.usdcAddress,\n relayAddress: result.relayAddress,\n sponsorAuth,\n };\n }\n\n // ------------------------------ Mobile endpoints -------------------------\n\n async claimPrepare(input: {\n authenticatedAddress: Address;\n chainId: number;\n pointTokenAddress: Address;\n amount: bigint;\n aaNonce: bigint;\n mintRequestNonce: bigint;\n }): Promise<MobilePrepareDto> {\n const ptClaimHandler = this.assertHandler(\n this.cfg.ptClaimHandler,\n \"ptClaimHandler\",\n \"claimPrepare\",\n );\n const pointTokenAddress = getAddress(input.pointTokenAddress);\n const claimResult = await ptClaimHandler.handle({\n authenticatedAddress: input.authenticatedAddress,\n userAddress: input.authenticatedAddress,\n amount: input.amount,\n pointTokenAddress,\n chainId: input.chainId,\n aaNonce: input.aaNonce,\n mintRequestNonce: input.mintRequestNonce,\n });\n\n const prepared = await this.runMobilePrepare(\n input.authenticatedAddress,\n input.chainId,\n claimResult.lockId,\n claimResult.userOp,\n claimResult.fallback,\n \"mint\",\n pointTokenAddress,\n claimResult.expiresInSeconds,\n );\n\n return {\n lockId: claimResult.lockId,\n userOpHash: prepared.sponsored.userOpHash,\n typedData: prepared.sponsored.typedData,\n userOpHashFallback: prepared.fallback?.userOpHash,\n typedDataFallback: prepared.fallback?.typedData,\n feeAmount: claimResult.feeAmount.toString(),\n signatureDeadline: claimResult.signatureDeadline.toString(),\n expiresInSeconds: claimResult.expiresInSeconds,\n sponsored: prepared.isSponsored,\n needsDelegation: prepared.needsDelegation,\n };\n }\n\n async claimSubmit(input: {\n authenticatedAddress: Address;\n lockId: string;\n signature: Hex;\n variant?: \"sponsored\" | \"fallback\";\n }): Promise<MobileSubmitDto> {\n return await handleMobileSubmit({\n lockId: input.lockId,\n authenticatedAddress: input.authenticatedAddress,\n signature: input.signature,\n variant: input.variant,\n store: this.cfg.pendingUserOpStore,\n bindUserOpHash: (lockId, hash) =>\n this.cfg.ledger.bindMintUserOpHash!(lockId, hash),\n pafiBackendClient: this.cfg.pafiBackendClient,\n });\n }\n\n async redeemPrepare(input: {\n authenticatedAddress: Address;\n chainId: number;\n pointTokenAddress: Address;\n amount: bigint;\n aaNonce: bigint;\n }): Promise<RedeemPrepareDto> {\n this.assertRedeemHandler();\n const pointTokenAddress = getAddress(input.pointTokenAddress);\n\n const redeemResponse = await this.cfg.ptRedeemHandler!.handle({\n userAddress: input.authenticatedAddress,\n authenticatedAddress: input.authenticatedAddress,\n amount: input.amount,\n aaNonce: input.aaNonce,\n chainId: input.chainId,\n });\n\n const prepared = await this.runMobilePrepare(\n input.authenticatedAddress,\n input.chainId,\n redeemResponse.lockId,\n redeemResponse.userOp,\n redeemResponse.fallback?.userOp,\n \"burn\",\n pointTokenAddress,\n redeemResponse.expiresInSeconds,\n );\n\n return {\n lockId: redeemResponse.lockId,\n userOpHash: prepared.sponsored.userOpHash,\n typedData: prepared.sponsored.typedData,\n userOpHashFallback: prepared.fallback?.userOpHash,\n typedDataFallback: prepared.fallback?.typedData,\n netCreditAmount: redeemResponse.netCreditAmount.toString(),\n netCreditAmountFallback:\n redeemResponse.fallback?.netCreditAmount.toString(),\n feeAmount: redeemResponse.feeAmount.toString(),\n signatureDeadline: redeemResponse.signatureDeadline.toString(),\n expiresInSeconds: redeemResponse.expiresInSeconds,\n sponsored: prepared.isSponsored,\n needsDelegation: prepared.needsDelegation,\n };\n }\n\n async redeemSubmit(input: {\n authenticatedAddress: Address;\n lockId: string;\n signature: Hex;\n variant?: \"sponsored\" | \"fallback\";\n }): Promise<MobileSubmitDto> {\n return await handleMobileSubmit({\n lockId: input.lockId,\n authenticatedAddress: input.authenticatedAddress,\n signature: input.signature,\n variant: input.variant,\n store: this.cfg.pendingUserOpStore,\n bindUserOpHash: (lockId, hash) =>\n this.cfg.ledger.bindCreditUserOpHash!(lockId, hash),\n pafiBackendClient: this.cfg.pafiBackendClient,\n });\n }\n\n async claimStatus(\n authenticatedAddress: Address,\n lockId: string,\n ): Promise<MintStatusResponse> {\n return await handleClaimStatus({\n lockId,\n userAddress: authenticatedAddress,\n ledger: this.cfg.ledger,\n pafiBackendClient: this.cfg.pafiBackendClient,\n onWarning: this.cfg.onWarning,\n });\n }\n\n async redeemStatus(\n authenticatedAddress: Address,\n lockId: string,\n ): Promise<BurnStatusResponse> {\n return await handleRedeemStatus({\n lockId,\n userAddress: authenticatedAddress,\n ledger: this.cfg.ledger,\n pafiBackendClient: this.cfg.pafiBackendClient,\n onWarning: this.cfg.onWarning,\n });\n }\n\n // ------------------------------ Delegate endpoints -----------------------\n\n async delegateStatus(\n authenticatedAddress: Address,\n chainId: number,\n ): Promise<DelegateStatusDto> {\n const { batchExecutor } = getContractAddresses(chainId);\n // v0.7.8 — fetch delegationNonce on-chain so mobile doesn't have to.\n // Mobile MUST use this exact value when calling Privy\n // `signAuthorization({ contractAddress, chainId, nonce })`. Hardcoding\n // `0` works only for the very first delegation of a brand-new EOA;\n // re-delegate / post-activity wallets have non-zero nonce and any\n // mismatch reverts AA23 \"invalid authorization nonce\".\n //\n // `blockTag: 'pending'` includes mempool to avoid race when the user\n // has another tx in flight (rare for embedded wallets, but free\n // safety margin).\n const [code, nonce] = await Promise.all([\n this.cfg.provider.getCode({ address: authenticatedAddress }),\n this.cfg.provider.getTransactionCount({\n address: authenticatedAddress,\n blockTag: \"pending\",\n }),\n ]);\n return {\n isDelegated: parseEip7702DelegatedAddress(code) !== null,\n batchExecutorAddress: batchExecutor,\n delegationNonce: nonce.toString(),\n chainId,\n };\n }\n\n /**\n * Build the delegation-anchor UserOp + obtain paymaster sponsorship\n * + persist as a pending entry. Mobile must:\n *\n * 1. Sign EIP-7702 authorization LOCALLY (Privy `signAuthorization`\n * with `{contractAddress: batchExecutorAddress, chainId,\n * nonce: delegationNonce}`) → 65-byte authSig hex.\n * 2. POST `/delegate/prepare` with `{ chainId, delegationNonce,\n * authSig }` → this method.\n * 3. Sign returned `userOpHash` LOCALLY (`signTypedData(typedData)`).\n * 4. POST `/delegate/submit` with `{ lockId, userOpSig }`.\n *\n * v0.7.7 — replaces single-shot delegateSubmit that tried to relay\n * a UserOp with empty `signature: \"0x\"` (Simple7702Account's\n * validateUserOp reverts `ECDSAInvalidSignatureLength` 0xfce698f7).\n */\n async delegatePrepare(\n authenticatedAddress: Address,\n input: {\n chainId: number;\n delegationNonce: bigint;\n authSig: Hex | string;\n aaNonce: bigint;\n },\n ): Promise<DelegatePrepareDto> {\n const { batchExecutor } = getContractAddresses(input.chainId);\n const fees = await this.cfg.provider.estimateFeesPerGas();\n const lockId = randomUUID();\n\n const result = await handleDelegatePrepare({\n userAddress: authenticatedAddress,\n chainId: input.chainId,\n delegationNonce: input.delegationNonce,\n aaNonce: input.aaNonce,\n authSig: input.authSig,\n fees,\n lockId,\n store: this.cfg.pendingUserOpStore,\n ttlSeconds: 15 * 60, // 15min — match claim/redeem mobile lock duration\n pafiBackendClient: this.cfg.pafiBackendClient,\n onWarning: this.cfg.onWarning,\n });\n\n return {\n lockId: result.lockId,\n userOpHash: result.userOpHash,\n typedData: result.typedData,\n expiresInSeconds: result.expiresInSeconds,\n isSponsored: result.isSponsored,\n delegationNonce: input.delegationNonce.toString(),\n batchExecutorAddress: batchExecutor,\n chainId: input.chainId,\n };\n }\n\n async delegateSubmit(input: {\n authenticatedAddress: Address;\n lockId: string;\n userOpSig: Hex;\n }): Promise<MobileSubmitDto> {\n const result = await handleDelegateSubmit({\n lockId: input.lockId,\n authenticatedAddress: input.authenticatedAddress,\n userOpSig: input.userOpSig,\n store: this.cfg.pendingUserOpStore,\n pafiBackendClient: this.cfg.pafiBackendClient,\n });\n return { userOpHash: result.userOpHash };\n }\n\n // ------------------------------ Internal helpers -------------------------\n\n /**\n * Build + sign a SponsorAuth payload. Returns `undefined` when no\n * issuer id is configured, so the controller can skip the field.\n */\n private async buildSponsorAuth(\n authenticatedAddress: Address,\n callData: Hex,\n chainId: number,\n scenario: SponsorshipScenario,\n ): Promise<BuiltSponsorAuth | undefined> {\n if (!this.cfg.pafiIssuerId) return undefined;\n return buildAndSignSponsorAuth({\n userAddress: authenticatedAddress,\n callData,\n chainId,\n scenario,\n issuerId: this.cfg.pafiIssuerId,\n issuerSignerWallet: this.cfg.issuerSignerWallet,\n });\n }\n\n private async runMobilePrepare(\n authenticatedAddress: Address,\n chainId: number,\n lockId: string,\n partialUserOp: Parameters<typeof handleMobilePrepare>[0][\"partialUserOp\"],\n partialUserOpFallback: Parameters<\n typeof handleMobilePrepare\n >[0][\"partialUserOpFallback\"],\n scenario: SponsorshipScenario,\n pointTokenAddress: Address,\n ttlSeconds: number,\n ): Promise<HandleMobilePrepareResult> {\n return await handleMobilePrepare({\n userAddress: authenticatedAddress,\n chainId,\n lockId,\n partialUserOp,\n partialUserOpFallback,\n scenario,\n pointTokenAddress,\n ttlSeconds,\n store: this.cfg.pendingUserOpStore,\n provider: this.cfg.provider,\n pafiBackendClient: this.cfg.pafiBackendClient,\n onWarning: this.cfg.onWarning,\n });\n }\n\n private assertRedeemHandler(): void {\n if (!this.cfg.ptRedeemHandler) {\n throw new Error(\n \"PTRedeemHandler not wired — IssuerApiAdapter.redeem* require a configured ptRedeemHandler.\",\n );\n }\n }\n\n /**\n * Narrow an optional handler to non-null and throw a clear error when\n * the issuer wired the adapter without it. Lets issuers opt out of\n * flows they don't expose (gg56 ships only mobile claim/redeem, so\n * `swapHandler` + `perpHandler` aren't constructed).\n */\n private assertHandler<T>(\n handler: T | null | undefined,\n fieldName: string,\n methodName: string,\n ): T {\n if (handler === null || handler === undefined) {\n throw new Error(\n `${fieldName} not wired — IssuerApiAdapter.${methodName}() requires a configured ${fieldName}.`,\n );\n }\n return handler;\n }\n}\n\n// Reference exports kept to silence \"unused import\" warnings on\n// generators that drop unused identifiers, since the helpers are still\n// referenced from method signatures via parameter inference.\nvoid encodeBatchExecute;\nvoid ENTRY_POINT_V08;\n","import { isAddress, type Address } from \"viem\";\nimport type { PoolKey } from \"@pafi-dev/core\";\nimport type {\n ApiPoolsRequest,\n ApiPoolsResponse,\n PoolsProvider,\n} from \"../api/types\";\n\nimport { PAFI_SUBGRAPH_URL } from \"@pafi-dev/core\";\nexport { PAFI_SUBGRAPH_URL };\n\n/**\n * Config for `createSubgraphPoolsProvider`.\n */\nexport interface SubgraphPoolsProviderConfig {\n /**\n * Fully qualified subgraph GraphQL endpoint.\n * Defaults to the PAFI-hosted subgraph (`PAFI_SUBGRAPH_URL`).\n * Override only when pointing at a staging or custom deployment.\n */\n subgraphUrl?: string;\n\n /**\n * Cache TTL in milliseconds. Pool discovery is near-static — a 30s\n * cache removes subgraph load without meaningfully delaying UX.\n * Set to 0 to disable caching. Default: 30_000.\n */\n cacheTtlMs?: number;\n\n /**\n * Optional fetch override for test harnesses. Defaults to global `fetch`.\n */\n fetchImpl?: typeof fetch;\n\n /**\n * Optional clock override for tests.\n */\n now?: () => number;\n}\n\ninterface CacheEntry {\n expiresAt: number;\n pools: PoolKey[];\n}\n\ninterface PafiTokenWithPool {\n id: string;\n pool: {\n id: string;\n feeTier: string;\n tickSpacing: string;\n hooks: string;\n token0: { id: string };\n token1: { id: string };\n } | null;\n}\n\ninterface GraphQLResponse {\n data?: { pafiToken: PafiTokenWithPool | null };\n errors?: { message: string }[];\n}\n\nconst DEFAULT_CACHE_TTL_MS = 30_000;\n\nconst POOL_QUERY = `\n query GetPoolForPointToken($id: ID!) {\n pafiToken(id: $id) {\n id\n pool {\n id\n feeTier\n tickSpacing\n hooks\n token0 { id }\n token1 { id }\n }\n }\n }\n`;\n\n/**\n * Create a `PoolsProvider` backed by the PAFI subgraph.\n *\n * Queries the `pafiTokens` entity for the given `pointTokenAddress`,\n * reads its linked `Pool`, and returns a single-element `PoolKey[]`.\n * Multiple pools per token would require a subgraph schema change.\n *\n * The result is cached in-process with a short TTL (default 30s). Pool\n * discovery is near-static so this avoids hammering the subgraph without\n * blocking config changes for long.\n *\n * Returns `{ pools: [] }` if:\n * - the token is not registered on PAFI yet (no PafiToken entity)\n * - the token is registered but its pool has not been initialised\n * - the subgraph is unreachable or returns an error (logs to console,\n * does not throw — callers should handle empty pool list gracefully)\n *\n * Assumes the PAFI subgraph schema. Issuers with a custom subgraph must\n * implement `PoolsProvider` themselves instead of using this helper.\n *\n * @example\n * ```ts\n * import { createSubgraphPoolsProvider, createIssuerService } from \"@pafi/issuer\";\n *\n * const service = createIssuerService({\n * // ...other config\n * poolsProvider: createSubgraphPoolsProvider({\n * subgraphUrl: \"https://graph.pacificfinance.org/subgraphs/name/pafi\",\n * }),\n * });\n * ```\n */\nexport function createSubgraphPoolsProvider(\n config: SubgraphPoolsProviderConfig = {},\n): PoolsProvider {\n const subgraphUrl = config.subgraphUrl ?? PAFI_SUBGRAPH_URL;\n\n try {\n const parsed = new URL(subgraphUrl);\n if (process.env.NODE_ENV === \"production\" && parsed.protocol !== \"https:\") {\n throw new Error(\"subgraphUrl must use HTTPS in production\");\n }\n } catch (err) {\n if (err instanceof TypeError) {\n throw new Error(\n `subgraphPoolsProvider: invalid subgraphUrl: ${subgraphUrl}`,\n );\n }\n throw err;\n }\n\n const cacheTtl = config.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;\n const fetchImpl = config.fetchImpl ?? globalThis.fetch;\n const now = config.now ?? (() => Date.now());\n const cache = new Map<string, CacheEntry>();\n\n if (!fetchImpl) {\n throw new Error(\n \"createSubgraphPoolsProvider: no fetch implementation available — pass `fetchImpl` or run on Node 18+\",\n );\n }\n\n return async (request: ApiPoolsRequest): Promise<ApiPoolsResponse> => {\n const cacheKey = `${request.chainId}:${request.pointTokenAddress.toLowerCase()}`;\n\n if (cacheTtl > 0) {\n const cached = cache.get(cacheKey);\n if (cached && cached.expiresAt > now()) {\n return { pools: cached.pools };\n }\n }\n\n const pools = await fetchPoolsFromSubgraph(\n fetchImpl,\n subgraphUrl,\n request.pointTokenAddress,\n );\n\n if (cacheTtl > 0) {\n cache.set(cacheKey, {\n expiresAt: now() + cacheTtl,\n pools,\n });\n }\n\n return { pools };\n };\n}\n\nasync function fetchPoolsFromSubgraph(\n fetchImpl: typeof fetch,\n subgraphUrl: string,\n pointTokenAddress: Address,\n): Promise<PoolKey[]> {\n let response: Response;\n try {\n response = await fetchImpl(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 // Network failure — surface empty list, caller decides UX.\n console.warn(\n \"[subgraphPoolsProvider] subgraph unreachable:\",\n (err as Error).message,\n );\n return [];\n }\n\n if (!response.ok) {\n console.warn(\n `[subgraphPoolsProvider] subgraph returned ${response.status}`,\n );\n return [];\n }\n\n const json = (await response.json()) as GraphQLResponse;\n if (json.errors && json.errors.length > 0) {\n console.warn(\n \"[subgraphPoolsProvider] subgraph errors:\",\n json.errors.map((e) => e.message).join(\"; \"),\n );\n return [];\n }\n\n const token = json.data?.pafiToken;\n if (!token || !token.pool) {\n return [];\n }\n\n const { pool } = token;\n\n if (!isAddress(pool.hooks)) {\n console.error(\n \"[PAFI] SubgraphPoolsProvider: invalid hooks address in response:\",\n pool.hooks,\n \"— skipping pool\",\n );\n return [];\n }\n if (!isAddress(pool.token0.id) || !isAddress(pool.token1.id)) {\n console.error(\n \"[PAFI] SubgraphPoolsProvider: invalid token address in response — skipping pool\",\n );\n return [];\n }\n if (\n !Number.isFinite(Number(pool.feeTier)) ||\n !Number.isFinite(Number(pool.tickSpacing))\n ) {\n console.error(\n \"[PAFI] SubgraphPoolsProvider: invalid feeTier/tickSpacing — skipping pool\",\n );\n return [];\n }\n\n const [currency0, currency1] = sortCurrencies(\n pool.token0.id as Address,\n pool.token1.id as Address,\n );\n\n const poolKey: PoolKey = {\n currency0,\n currency1,\n fee: Number(pool.feeTier),\n tickSpacing: Number(pool.tickSpacing),\n hooks: pool.hooks as Address,\n };\n\n return [poolKey];\n}\n\n/**\n * Uniswap V4 requires `currency0 < currency1` in the PoolKey. Subgraph\n * stores token0/token1 already sorted, but we re-sort defensively in\n * case of schema drift.\n */\nfunction sortCurrencies(a: Address, b: Address): [Address, Address] {\n return a.toLowerCase() < b.toLowerCase() ? [a, b] : [b, a];\n}\n","import { PAFI_SUBGRAPH_URL } from \"./subgraphPoolsProvider\";\n\n/**\n * Config for `createSubgraphNativeUsdtQuoter`.\n */\nexport interface SubgraphNativeUsdtQuoterConfig {\n /**\n * Fully qualified subgraph GraphQL endpoint.\n * Defaults to the PAFI-hosted subgraph (`PAFI_SUBGRAPH_URL`).\n * Override only when pointing at a staging or custom deployment.\n */\n subgraphUrl?: string;\n\n /**\n * Decimals of the USDT token. Defaults to 6 (standard USDT/USDC on\n * Base, Ethereum, Polygon). Override for chains where USDT uses a\n * different decimals value.\n */\n usdtDecimals?: number;\n\n /**\n * Decimals of the native token (ETH on Base/mainnet/Arbitrum/Optimism,\n * MATIC on Polygon). Default: 18.\n */\n nativeDecimals?: number;\n\n /**\n * Cache TTL in milliseconds. ETH price drifts slowly relative to gas\n * estimation needs — a 30s cache keeps fees stable across bursts of\n * requests without letting them go stale during volatile markets.\n * Set to 0 to disable caching. Default: 30_000.\n */\n cacheTtlMs?: number;\n\n /**\n * Fallback price (USDT per native token, human-readable float) used\n * when the subgraph is unreachable. This keeps the backend operational\n * during subgraph outages rather than bricking cashouts. The fee will\n * be slightly off but the operator still gets paid. Default: 3000.\n */\n fallbackEthPriceUsd?: number;\n\n /** Optional fetch override for test harnesses. */\n fetchImpl?: typeof fetch;\n\n /** Optional clock override for tests. */\n now?: () => number;\n}\n\ninterface GraphQLResponse {\n data?: { bundle: { ethPriceUSD: string } | null };\n errors?: { message: string }[];\n}\n\nconst DEFAULT_CACHE_TTL_MS = 30_000;\nconst DEFAULT_FALLBACK_PRICE = 3000;\nconst DEFAULT_USDT_DECIMALS = 6;\nconst DEFAULT_NATIVE_DECIMALS = 18;\n\n/**\n * The PAFI subgraph maintains a single `Bundle` entity with id `\"1\"` that\n * stores the current ETH/USD price (updated on every Uniswap pool sync).\n * This is the same source the Uniswap analytics UIs use.\n */\nconst PRICE_QUERY = `\n query GetEthPrice {\n bundle(id: \"1\") {\n ethPriceUSD\n }\n }\n`;\n\n/**\n * Create a native→USDT quoter backed by the PAFI subgraph's\n * `Bundle.ethPriceUSD`. The returned function has the shape\n * `(amountNative: bigint) => Promise<bigint>` and can be passed as\n * `quoteNativeToFee` to `FeeManager` — in v1.4 the fee currency\n * is configured at the integration layer, not hardcoded here.\n *\n * Used by `FeeManager.estimateGasFee()` to convert the gas cost into\n * an ERC-20 amount charged as part of the sponsored UserOp batch.\n * Price precision is not critical — a 1-2% drift is acceptable since\n * the fee manager applies a `gasPremiumBps` buffer.\n *\n * The result is cached in-process with a short TTL (default 30s). If\n * the subgraph is unreachable, falls back to `fallbackEthPriceUsd` so\n * gas estimation doesn't block user flow during a subgraph outage.\n *\n * @example\n * ```ts\n * import { createSubgraphNativeUsdtQuoter, createIssuerService } from \"@pafi-dev/issuer\";\n *\n * const service = createIssuerService({\n * // ...other config\n * fee: {\n * quoteNativeToFee: createSubgraphNativeUsdtQuoter({\n * subgraphUrl: \"https://graph.pacificfinance.org/subgraphs/name/pafi\",\n * }),\n * },\n * });\n * ```\n */\nexport function createSubgraphNativeUsdtQuoter(\n config: SubgraphNativeUsdtQuoterConfig = {},\n): (amountNative: bigint) => Promise<bigint> {\n const subgraphUrl = config.subgraphUrl ?? PAFI_SUBGRAPH_URL;\n\n try {\n const parsed = new URL(subgraphUrl);\n if (process.env.NODE_ENV === \"production\" && parsed.protocol !== \"https:\") {\n throw new Error(\"subgraphUrl must use HTTPS in production\");\n }\n } catch (err) {\n if (err instanceof TypeError) {\n throw new Error(\n `createSubgraphNativeUsdtQuoter: invalid subgraphUrl: ${subgraphUrl}`,\n );\n }\n throw err;\n }\n\n const usdtDecimals = config.usdtDecimals ?? DEFAULT_USDT_DECIMALS;\n const nativeDecimals = config.nativeDecimals ?? DEFAULT_NATIVE_DECIMALS;\n const cacheTtl = config.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;\n const fallbackPrice = config.fallbackEthPriceUsd ?? DEFAULT_FALLBACK_PRICE;\n const fetchImpl = config.fetchImpl ?? globalThis.fetch;\n const now = config.now ?? (() => Date.now());\n\n if (!fetchImpl) {\n throw new Error(\n \"createSubgraphNativeUsdtQuoter: no fetch implementation available — pass `fetchImpl` or run on Node 18+\",\n );\n }\n\n // Cache: stores the USDT-per-native scaling factor (not the raw price\n // string) so conversion is a single bigint multiply per request.\n let cached: { usdtPerNative: bigint; expiresAt: number } | undefined;\n\n async function getUsdtPerNative(): Promise<bigint> {\n if (cacheTtl > 0 && cached && cached.expiresAt > now()) {\n return cached.usdtPerNative;\n }\n\n const price = await fetchEthPriceFromSubgraph(\n fetchImpl,\n subgraphUrl,\n );\n const usdtPerNative = toUsdtPerNative(\n price ?? fallbackPrice,\n usdtDecimals,\n );\n\n if (cacheTtl > 0) {\n cached = {\n usdtPerNative,\n expiresAt: now() + cacheTtl,\n };\n }\n\n return usdtPerNative;\n }\n\n return async (amountNative: bigint): Promise<bigint> => {\n if (amountNative === 0n) return 0n;\n const usdtPerNative = await getUsdtPerNative();\n // usdtPerNative is already scaled to USDT decimals (e.g. 3000 USD →\n // 3_000_000_000 for 6-decimal USDT). Dividing by 10^nativeDecimals\n // converts raw wei to whole native units implicitly.\n return (amountNative * usdtPerNative) / 10n ** BigInt(nativeDecimals);\n };\n}\n\nasync function fetchEthPriceFromSubgraph(\n fetchImpl: typeof fetch,\n subgraphUrl: string,\n): Promise<number | null> {\n let response: Response;\n try {\n response = await fetchImpl(subgraphUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ query: PRICE_QUERY }),\n });\n } catch (err) {\n console.warn(\n \"[subgraphNativeUsdtQuoter] subgraph unreachable:\",\n (err as Error).message,\n );\n return null;\n }\n\n if (!response.ok) {\n console.warn(\n `[subgraphNativeUsdtQuoter] subgraph returned ${response.status}`,\n );\n return null;\n }\n\n const json = (await response.json()) as GraphQLResponse;\n if (json.errors && json.errors.length > 0) {\n console.warn(\n \"[subgraphNativeUsdtQuoter] subgraph errors:\",\n json.errors.map((e) => e.message).join(\"; \"),\n );\n return null;\n }\n\n const raw = json.data?.bundle?.ethPriceUSD;\n if (!raw) return null;\n\n const parsed = Number(raw);\n if (!Number.isFinite(parsed) || parsed <= 0) {\n console.warn(\n `[subgraphNativeUsdtQuoter] invalid ethPriceUSD from subgraph: ${raw}`,\n );\n return null;\n }\n const MIN_REASONABLE_ETH_PRICE = 100; // $100\n const MAX_REASONABLE_ETH_PRICE = 100_000; // $100,000\n if (parsed < MIN_REASONABLE_ETH_PRICE || parsed > MAX_REASONABLE_ETH_PRICE) {\n console.warn(\n `[PAFI] SubgraphNativeUsdtQuoter: ETH/USD price ${parsed} is outside reasonable range. Using fallback.`,\n );\n return null;\n }\n return parsed;\n}\n\n/**\n * Convert a float \"USDT per 1 native token\" value into a bigint scaled\n * to USDT decimals. Done via string manipulation to preserve precision\n * beyond JS float (~15-17 significant digits).\n *\n * Example: price=3245.6789, usdtDecimals=6 → 3_245_678_900n\n */\nfunction toUsdtPerNative(priceFloat: number, usdtDecimals: number): bigint {\n // Normalize to fixed notation and strip exponent. `.toFixed` rounds to\n // the requested number of fractional digits and returns a plain string.\n const fixed = priceFloat.toFixed(usdtDecimals);\n const [whole, fraction = \"\"] = fixed.split(\".\");\n const padded = (fraction + \"0\".repeat(usdtDecimals)).slice(0, usdtDecimals);\n return BigInt(whole + padded);\n}\n","import { parseAbi } from \"viem\";\nimport type { Address, PublicClient } from \"viem\";\nimport { PAFI_SUBGRAPH_URL } from \"./subgraphPoolsProvider\";\n\nconst CHAINLINK_ABI = parseAbi([\n \"function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)\",\n]);\n\n/**\n * Max age for a Chainlink price before it's considered stale.\n * Base ETH/USD feed heartbeat is 1 hour (updates every hour or on 0.5% deviation).\n */\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\nexport interface NativePtQuoterConfig {\n /** Viem PublicClient — used to call Chainlink on-chain. */\n provider: PublicClient;\n /** Address of the PointToken being traded. */\n pointTokenAddress: Address;\n /** Chainlink ETH/USD feed address. Defaults to Base mainnet feed. */\n chainlinkFeedAddress?: Address;\n /** PAFI subgraph GraphQL endpoint. */\n subgraphUrl?: string;\n /** Cache TTL in ms. Default: 30_000. */\n cacheTtlMs?: number;\n /** Fallback ETH price (USD) when Chainlink is unreachable. Default: 3000. */\n fallbackEthPriceUsd?: number;\n /** Fallback PT price (USDT per 1 PT) when subgraph is unreachable. Default: 0.1. */\n fallbackPtPriceUsdt?: number;\n fetchImpl?: typeof fetch;\n now?: () => number;\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 * Create a native→PT quoter for use as `FeeManager.quoteNativeToFee`.\n *\n * Converts ETH gas cost → USDT (via Chainlink ETH/USD) → PT (via subgraph\n * pool price), returning the fee amount in PT raw units (18 decimals).\n *\n * Formula:\n * feeInPT = amountNative × ethPrice_8dec × ptPerUsdt_18dec / 10^26\n *\n * Both prices are cached in-process (default 30s TTL).\n *\n * @example\n * ```ts\n * fee: {\n * quoteNativeToFee: createNativePtQuoter({\n * provider,\n * pointTokenAddress: \"0x...\",\n * chainlinkFeedAddress: getContractAddresses(chainId).chainlinkEthUsd,\n * }),\n * }\n * ```\n */\nexport function createNativePtQuoter(\n config: NativePtQuoterConfig,\n): (amountNative: bigint) => Promise<bigint> {\n const {\n provider,\n pointTokenAddress,\n chainlinkFeedAddress = \"0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70\" as Address,\n subgraphUrl = PAFI_SUBGRAPH_URL,\n cacheTtlMs = 30_000,\n fallbackEthPriceUsd = 3_000,\n fallbackPtPriceUsdt = 0.1,\n fetchImpl = globalThis.fetch,\n now = () => Date.now(),\n } = config;\n\n let ethPriceCache: { value: bigint; expiresAt: number } | undefined;\n let ptPriceCache: { value: bigint; expiresAt: number } | undefined;\n\n async function getEthPrice8dec(): Promise<bigint> {\n const ts = now();\n if (ethPriceCache && ethPriceCache.expiresAt > ts) return ethPriceCache.value;\n\n try {\n const result = await provider.readContract({\n address: chainlinkFeedAddress,\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(ts / 1000)) - updatedAt;\n if (ageS > CHAINLINK_MAX_AGE_S) {\n throw new Error(`Chainlink: price stale by ${ageS}s`);\n }\n\n ethPriceCache = { value: answer, expiresAt: ts + cacheTtlMs };\n return answer;\n } catch (err) {\n console.warn(\"[nativePtQuoter] Chainlink unavailable, using fallback:\", (err as Error).message);\n return BigInt(Math.round(fallbackEthPriceUsd * 1e8));\n }\n }\n\n async function getPtPerUsdt18dec(): Promise<bigint> {\n const ts = now();\n if (ptPriceCache && ptPriceCache.expiresAt > ts) return ptPriceCache.value;\n\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) throw new Error(json.errors.map((e) => e.message).join(\"; \"));\n\n const pool = json.data?.pafiToken?.pool;\n if (!pool) throw new Error(\"pafiToken or pool not found in subgraph\");\n\n // PAFI V4 subgraph returns prices WITHOUT decimal adjustment.\n // token0Price = token1_raw / token0_raw (raw amounts ratio, not human-readable).\n //\n // Regardless of which token is PT/USDT, the field we pick always equals\n // USDT_raw / PT_raw (a small number, e.g. 1.082e-12 when 1 PT = 1.082 USDT):\n // PT=token0 → token0Price = USDT_raw / PT_raw\n // PT=token1 → token1Price = USDT_raw / PT_raw\n //\n // To convert to ptPerUsdt_18dec (\"raw PT units per 1 human USDT\"):\n // ptPerUsdt_18dec = 10^6 / (USDT_raw/PT_raw)\n // = 10^24 / parseBigDecimalTo18(priceStr)\n // where 10^6 = USDT decimals\n const isPtToken0 =\n pool.token0.id.toLowerCase() === pointTokenAddress.toLowerCase();\n const ptPerUsdtStr = isPtToken0 ? pool.token0Price : pool.token1Price;\n\n if (!ptPerUsdtStr || Number(ptPerUsdtStr) <= 0) {\n throw new Error(`invalid PT/USDT price from subgraph: ${ptPerUsdtStr}`);\n }\n\n const raw = parseBigDecimalTo18(ptPerUsdtStr);\n if (raw === 0n) throw new Error(`pool price parsed to zero: ${ptPerUsdtStr}`);\n const value = 10n ** 24n / raw;\n ptPriceCache = { value, expiresAt: ts + cacheTtlMs };\n return value;\n } catch (err) {\n console.warn(\"[nativePtQuoter] subgraph unavailable, using fallback:\", (err as Error).message);\n // fallbackPtPriceUsdt is USDT per 1 PT (e.g. 0.1 → 1 PT costs 0.1 USDT → 10 PT per USDT)\n const ptPerUsdtHuman = 1 / fallbackPtPriceUsdt;\n return parseBigDecimalTo18(ptPerUsdtHuman.toFixed(18));\n }\n }\n\n return async (amountNative: bigint): Promise<bigint> => {\n if (amountNative === 0n) return 0n;\n\n const [ethPrice8dec, ptPerUsdt18dec] = await Promise.all([\n getEthPrice8dec(),\n getPtPerUsdt18dec(),\n ]);\n\n // feeInPT_18dec = amountNative_wei × ethPrice_8dec × ptPerUsdt_18dec / 10^26\n //\n // Derivation:\n // feeInUSDT_6dec = amountNative × ethPrice_8dec / 10^(18+8-6) = / 10^20\n // feeInPT_18dec = feeInUSDT_6dec × ptPerUsdt_18dec / 10^(18-6) = / 10^12\n // combined = amountNative × ethPrice_8dec × ptPerUsdt_18dec / 10^32... wait\n //\n // Correct combined formula (verified with concrete numbers):\n // feeInPT_18dec = amountNative × ethPrice_8dec × ptPerUsdt_18dec / 10^(20+12) = / 10^32\n // But we scaled ptPerUsdt to 18 decimals, so:\n // feeInPT_18dec = amountNative × ethPrice_8dec × ptPerUsdt_18dec / 10^(8 + 18 + 18 - 18)\n // = / 10^26\n return (amountNative * ethPrice8dec * ptPerUsdt18dec) / 10n ** 26n;\n };\n}\n\n/**\n * Parse a BigDecimal string (e.g. \"10.523\") into a bigint scaled to\n * `decimals` decimal places (e.g. 18 → 10_523_000_000_000_000_000n).\n * Truncates extra digits silently.\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 type { Address, PublicClient } from \"viem\";\nimport { getPointTokenBalance } from \"@pafi-dev/core\";\nimport type { IPointLedger } from \"../ledger/types\";\n\n/**\n * Combined off-chain + on-chain balance for a single user / token pair.\n *\n * - `offChain` — the issuer's ledger balance (available, excluding locks)\n * - `onChain` — the user's ERC-20 balance from `PointToken.balanceOf`\n * - `total` — `offChain + onChain` (what the Issuer App displays)\n */\nexport interface CombinedBalance {\n offChain: bigint;\n onChain: bigint;\n total: bigint;\n}\n\nexport interface BalanceAggregatorConfig {\n provider: PublicClient;\n ledger: IPointLedger;\n}\n\n/**\n * v1.4 utility — aggregates off-chain + on-chain point balances into a\n * single view for the \"combined balance\" UI in the Issuer App.\n *\n * The `/user` API handler uses this internally; the helper is exposed\n * publicly so Issuer Apps can call it directly without going through\n * the HTTP layer (e.g., for server-rendered pages or admin dashboards).\n *\n * See [REQUIREMENTS_V2.md] §1 — \"The Issuer App displays a combined\n * balance (off-chain points + on-chain PT) and does not surface USDT.\"\n */\nexport class BalanceAggregator {\n private readonly provider: PublicClient;\n private readonly ledger: IPointLedger;\n\n constructor(config: BalanceAggregatorConfig) {\n if (!config.provider) {\n throw new Error(\"BalanceAggregator: provider is required\");\n }\n if (!config.ledger) {\n throw new Error(\"BalanceAggregator: ledger is required\");\n }\n this.provider = config.provider;\n this.ledger = config.ledger;\n }\n\n /**\n * Combined balance for a single (user, token) pair. Fetches off-chain\n * + on-chain in parallel.\n */\n async getCombinedBalance(\n user: Address,\n pointToken: Address,\n ): Promise<CombinedBalance> {\n const [offChain, onChain] = await Promise.all([\n this.ledger.getBalance(user, pointToken),\n getPointTokenBalance(this.provider, pointToken, user),\n ]);\n return {\n offChain,\n onChain,\n total: offChain + onChain,\n };\n }\n\n /**\n * Combined balance for multiple tokens owned by the same user. Runs\n * all lookups in parallel. Returns a Map keyed by the token address\n * (same casing as supplied — caller should normalize if needed).\n */\n async getCombinedBalanceMulti(\n user: Address,\n pointTokens: Address[],\n ): Promise<Map<Address, CombinedBalance>> {\n const entries = await Promise.all(\n pointTokens.map(async (token) => {\n const balance = await this.getCombinedBalance(user, token);\n return [token, balance] as const;\n }),\n );\n return new Map(entries);\n }\n}\n","import type { Address, Hex } from \"viem\";\nimport { getPafiServiceUrls } from \"@pafi-dev/core\";\nimport { PafiBackendError, type PafiBackendConfig } from \"./types\";\n\n/**\n * Extract `code` + `message` from a PAFI service error response.\n *\n * PAFI services use the standard envelope:\n * { success: false, statusCode, error: { code, message, ... }, meta: ... }\n *\n * Older / non-PAFI upstreams (raw Pimlico, third-party services) may use\n * a flat shape: { code, message }. We probe the nested envelope first\n * and fall back to flat — never silently swallow the upstream message\n * into a generic \"HTTP 500\" string, which loses the AA21/AA23/etc reason\n * the FE / ops needs to debug.\n */\nfunction extractPafiErrorFields(\n json: Record<string, unknown>,\n status: number,\n): { code: string; message: string } {\n const inner =\n typeof json.error === \"object\" && json.error !== null\n ? (json.error as Record<string, unknown>)\n : null;\n const code =\n (inner?.code as string | undefined) ??\n (json.code as string | undefined) ??\n \"INTERNAL_ERROR\";\n const message =\n (inner?.message as string | undefined) ??\n (json.message as string | undefined) ??\n `HTTP ${status}`;\n return { code, message };\n}\n\nexport interface RelayUserOpRequest {\n userOp: Record<string, string | null>;\n entryPoint: string;\n eip7702Auth?: {\n chainId: string;\n address: string;\n nonce: string;\n r: string;\n s: string;\n yParity: string;\n };\n}\n\nexport interface RelayUserOpResponse {\n userOpHash: Hex;\n}\n\nexport interface SponsorshipUserOp {\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\nexport interface SponsorshipTarget {\n contract: Address;\n function: string;\n pointToken: Address;\n}\n\nexport interface SponsorshipRequest {\n chainId: number;\n scenario: string;\n userOp: SponsorshipUserOp;\n target: SponsorshipTarget;\n /**\n * EIP-7702 authorization tuple — REQUIRED for the `delegate`\n * scenario so Pimlico's `pm_sponsorUserOperation` can simulate the\n * UserOp with the EOA already delegated. Without it, simulator\n * reverts `AA20 account not deployed` (chicken-and-egg: the same\n * UserOp anchors the delegation).\n *\n * Optional for non-delegate scenarios where the sender already\n * has bytecode (i.e. delegated previously). v0.7.5 added.\n */\n eip7702Auth?: {\n chainId: string;\n address: string;\n nonce: string;\n r: string;\n s: string;\n yParity: string;\n };\n}\n\nexport interface SponsorshipResponse {\n paymaster: Address;\n paymasterData: Hex;\n paymasterVerificationGasLimit: bigint;\n paymasterPostOpGasLimit: bigint;\n /**\n * Pimlico's `pm_sponsorUserOperation` re-estimates these gas fields\n * and signs its paymaster signature over the new values. Callers\n * MUST overwrite the matching userOp fields with these BEFORE\n * computing the EIP-712 userOpHash and submitting to the bundler.\n * Otherwise both `AA34` (paymaster sig) and `AA24` (sender sig) will\n * fire — the EntryPoint hashes the actual on-chain field values, and\n * a mismatch invalidates every sig over the hash.\n */\n callGasLimit?: bigint;\n verificationGasLimit?: bigint;\n preVerificationGas?: bigint;\n /**\n * Bundler-required gas price (Pimlico's `pimlico_getUserOperationGasPrice`\n * fast tier). Pimlico's paymaster signs over these — caller MUST apply\n * to the userOp before computing the EIP-712 userOpHash. Base RPC's\n * `eth_feeHistory` underestimates the bundler floor by 10-15 %, so\n * relying on it produces \"maxFeePerGas must be at least ...\" rejections.\n */\n maxFeePerGas?: bigint;\n maxPriorityFeePerGas?: bigint;\n expiresAt: number;\n}\n\nfunction serializeBigInt(_key: string, value: unknown): unknown {\n return typeof value === \"bigint\" ? value.toString(10) : value;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport class PafiBackendClient {\n private readonly config: PafiBackendConfig;\n private readonly baseUrl: string;\n\n constructor(config: PafiBackendConfig) {\n if (!config.chainId) throw new Error(\"PafiBackendClient: chainId is required\");\n if (!config.issuerId) throw new Error(\"PafiBackendClient: issuerId is required\");\n if (!config.apiKey) throw new Error(\"PafiBackendClient: apiKey is required\");\n this.config = config;\n this.baseUrl = getPafiServiceUrls(config.chainId).sponsorRelayer;\n }\n\n async requestSponsorship(\n request: SponsorshipRequest,\n ): Promise<SponsorshipResponse> {\n const maxAttempts = this.config.retry?.maxAttempts ?? 1;\n const initialDelayMs = this.config.retry?.initialDelayMs ?? 100;\n const maxDelayMs = this.config.retry?.maxDelayMs ?? 10_000;\n const maxRetryAfterMs = this.config.retry?.maxRetryAfterMs;\n\n let lastError: PafiBackendError | undefined;\n let delay = initialDelayMs;\n\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n return await this._doRequest(request);\n } catch (err) {\n if (!(err instanceof PafiBackendError)) throw err;\n lastError = err;\n\n if (attempt >= maxAttempts) break;\n if (!err.safeToRetry) break;\n\n const retryAfterMs =\n err.retryAfter !== undefined ? err.retryAfter * 1000 : undefined;\n if (\n maxRetryAfterMs !== undefined &&\n retryAfterMs !== undefined &&\n retryAfterMs > maxRetryAfterMs\n ) {\n break;\n }\n\n await sleep(retryAfterMs ?? delay);\n delay = Math.min(delay * 2, maxDelayMs);\n }\n }\n\n throw lastError;\n }\n\n /**\n * Fetch ERC-4337 UserOp receipt via PAFI's authenticated bundler proxy.\n * Returns `null` when the bundler hasn't seen the userOp yet — caller\n * should keep polling. Used by status endpoints to short-circuit the\n * on-chain indexer when several PENDING locks share the same amount.\n */\n async getUserOpReceipt(\n userOpHash: Hex,\n ): Promise<{ success: boolean; txHash: Hex; blockNumber: string } | null> {\n const fetchFn = this.config.fetchImpl ?? fetch;\n const url = `${this.baseUrl}/bundler/receipt`;\n\n let response: Response;\n try {\n response = await fetchFn(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.config.apiKey}`,\n \"X-Issuer-Id\": this.config.issuerId,\n },\n body: JSON.stringify({ userOpHash }),\n });\n } catch (err: unknown) {\n throw new PafiBackendError(\n \"NETWORK_ERROR\",\n `Network error: ${err instanceof Error ? err.message : String(err)}`,\n 0,\n );\n }\n\n const text = await response.text();\n let json: Record<string, unknown> = {};\n try {\n json = JSON.parse(text);\n } catch {\n // non-JSON\n }\n\n if (!response.ok) {\n const { code, message } = extractPafiErrorFields(json, response.status);\n throw new PafiBackendError(code, message, response.status, json);\n }\n\n if (json.pending) return null;\n return {\n success: json.success as boolean,\n txHash: json.txHash as Hex,\n blockNumber: json.blockNumber as string,\n };\n }\n\n async relayUserOperation(\n request: RelayUserOpRequest,\n ): Promise<RelayUserOpResponse> {\n const fetchFn = this.config.fetchImpl ?? fetch;\n const url = `${this.baseUrl}/bundler/relay`;\n\n let response: Response;\n try {\n response = await fetchFn(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.config.apiKey}`,\n \"X-Issuer-Id\": this.config.issuerId,\n },\n body: JSON.stringify(request),\n });\n } catch (err: unknown) {\n throw new PafiBackendError(\n \"NETWORK_ERROR\",\n `Network error: ${err instanceof Error ? err.message : String(err)}`,\n 0,\n );\n }\n\n const text = await response.text();\n let json: Record<string, unknown> = {};\n try {\n json = JSON.parse(text);\n } catch {\n // non-JSON body\n }\n\n if (!response.ok) {\n const { code, message } = extractPafiErrorFields(json, response.status);\n throw new PafiBackendError(code, message, response.status, json);\n }\n\n return { userOpHash: json.userOpHash as Hex };\n }\n\n private async _doRequest(\n request: SponsorshipRequest,\n ): Promise<SponsorshipResponse> {\n const fetchFn = this.config.fetchImpl ?? fetch;\n const url = `${this.baseUrl}/paymaster/sponsor`;\n const body = JSON.stringify(request, serializeBigInt);\n\n let response: Response;\n try {\n response = await fetchFn(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.config.apiKey}`,\n \"X-Issuer-Id\": this.config.issuerId,\n },\n body,\n });\n } catch (err: unknown) {\n throw new PafiBackendError(\n \"NETWORK_ERROR\",\n `Network error: ${err instanceof Error ? err.message : String(err)}`,\n 0,\n );\n }\n\n const text = await response.text();\n let json: Record<string, unknown> = {};\n try {\n json = JSON.parse(text);\n } catch {\n // non-JSON body — treat as internal error\n }\n\n if (!response.ok) {\n const { code, message } = extractPafiErrorFields(json, response.status);\n const errInner =\n typeof json.error === \"object\" && json.error !== null\n ? (json.error as Record<string, unknown>)\n : json;\n const retryAfter =\n typeof errInner.retryAfter === \"number\"\n ? errInner.retryAfter\n : typeof json.retryAfter === \"number\"\n ? json.retryAfter\n : undefined;\n const safeToRetry =\n typeof errInner.safeToRetry === \"boolean\"\n ? errInner.safeToRetry\n : typeof json.safeToRetry === \"boolean\"\n ? json.safeToRetry\n : undefined;\n throw new PafiBackendError(code, message, response.status, json, {\n retryAfter,\n safeToRetry,\n });\n }\n\n return {\n paymaster: json.paymaster as Address,\n paymasterData: json.paymasterData as Hex,\n paymasterVerificationGasLimit: BigInt(\n json.paymasterVerificationGasLimit as string,\n ),\n paymasterPostOpGasLimit: BigInt(json.paymasterPostOpGasLimit as string),\n callGasLimit:\n json.callGasLimit != null\n ? BigInt(json.callGasLimit as string)\n : undefined,\n verificationGasLimit:\n json.verificationGasLimit != null\n ? BigInt(json.verificationGasLimit as string)\n : undefined,\n preVerificationGas:\n json.preVerificationGas != null\n ? BigInt(json.preVerificationGas as string)\n : undefined,\n maxFeePerGas:\n json.maxFeePerGas != null\n ? BigInt(json.maxFeePerGas as string)\n : undefined,\n maxPriorityFeePerGas:\n json.maxPriorityFeePerGas != null\n ? BigInt(json.maxPriorityFeePerGas as string)\n : undefined,\n expiresAt: json.expiresAt as number,\n };\n }\n}\n","import { getAddress } from \"viem\";\nimport type { Address, PublicClient, WalletClient } from \"viem\";\nimport { getContractAddresses } from \"@pafi-dev/core\";\nimport type { IPointLedger } from \"./ledger/types\";\nimport { DefaultPolicyEngine } from \"./policy/defaultPolicy\";\nimport type { IPolicyEngine } from \"./policy/types\";\nimport { MemorySessionStore } from \"./auth/memorySessionStore\";\nimport type { ISessionStore } from \"./auth/types\";\nimport { AuthService } from \"./auth/loginVerifier\";\nimport { RelayService } from \"./relay/relayService\";\nimport { FeeManager, type FeeManagerConfig } from \"./relay/feeManager\";\nimport { PointIndexer } from \"./indexer/pointIndexer\";\nimport type { IIndexerCursorStore } from \"./indexer/types\";\nimport { IssuerApiHandlers } from \"./api/handlers\";\nimport type {\n ApiConfigResponse,\n PoolsProvider,\n} from \"./api/types\";\nimport { RedemptionService } from \"./redemption/service\";\nimport { PolicyProvider } from \"./redemption/policyProvider\";\nimport type {\n IRedemptionHistoryStore,\n PolicyProviderConfig,\n} from \"./redemption/types\";\n\n// ---------------------------------------------------------------------------\n// Config shape\n// ---------------------------------------------------------------------------\n\n/**\n * Top-level configuration for `createIssuerService`.\n *\n * In v1.4 the SDK is HTTP-client-free: it signs EIP-712 messages, reads\n * on-chain state, builds unsigned UserOperations, and maintains the\n * off-chain ledger. It never broadcasts transactions — that's the\n * frontend's responsibility via Bundler + Paymaster.\n *\n * **Multi-token (0.2.0+):** Pass `pointTokenAddresses: Address[]` to\n * support multiple PointTokens under a single issuer backend. Legacy\n * `pointTokenAddress: Address` still works for single-token deployments.\n * When both are provided, `pointTokenAddresses` takes precedence.\n */\nexport interface IssuerServiceConfig {\n // -- Chain + contracts --------------------------------------------------\n\n chainId: number;\n /** Legacy single-token shortcut; prefer `pointTokenAddresses`. */\n pointTokenAddress?: Address;\n /** All PointToken addresses this issuer supports. */\n pointTokenAddresses?: Address[];\n /**\n * Issuer-specific contract addresses merged into the `/config` response.\n * PAFI-owned addresses (batchExecutor, usdt, issuerRegistry, mintingOracle,\n * pafiHook) are auto-resolved from `getContractAddresses(chainId)` and\n * can be omitted. Only `pointToken` / `pointTokens` must be provided.\n */\n contracts?: Pick<\n ApiConfigResponse[\"contracts\"],\n \"pointToken\" | \"pointTokens\" | \"relay\"\n >;\n\n // -- Infra --------------------------------------------------------------\n\n /**\n * Shared `PublicClient` used for on-chain reads, simulation, indexer\n * polling, and gas-price lookups. Must be pointed at the target chain.\n */\n provider: PublicClient;\n\n // -- Auth ---------------------------------------------------------------\n\n auth: {\n jwtSecret: string;\n /** SIWE-style login-message domain, e.g. `\"app.example.com\"`. */\n domain: string;\n /** Passed straight to `jose` (`\"24h\"`, `\"7d\"`, …). Default `\"24h\"`. */\n jwtExpiresIn?: string;\n };\n\n // -- Storage ------------------------------------------------------------\n\n /**\n * Off-chain point ledger — the source of truth for user balances and\n * in-flight minting locks. Every issuer provides their own database-backed\n * implementation (Postgres, Redis, etc.) that implements `IPointLedger`.\n * The SDK does not ship a production ledger; each issuer's data model and\n * infrastructure are different.\n */\n ledger: IPointLedger;\n\n /**\n * Policy engine — optional, defaults to `DefaultPolicyEngine` which checks\n * off-chain balance. Extend or replace to add KYC, volume caps, etc.\n */\n policy?: IPolicyEngine;\n\n /** Session store — optional, defaults to `MemorySessionStore` (dev/test only). */\n sessionStore?: ISessionStore;\n\n // -- Optional API collaborators ----------------------------------------\n\n /**\n * Fee management config. If omitted the `handleGasFee` endpoint will\n * throw \"not configured\" at request time.\n */\n fee?: Omit<FeeManagerConfig, \"provider\">;\n\n /**\n * Pool discovery function for `handlePools`. If omitted the endpoint\n * throws \"not configured\" at request time.\n */\n poolsProvider?: PoolsProvider;\n\n // Note: legacy `claim?: {...}` config (used by removed `handleClaim`)\n // dropped in 0.5.43. Wire `PTClaimHandler` directly or via\n // `IssuerApiAdapter` instead.\n\n // -- Indexer config (optional) -----------------------------------------\n\n indexer?: {\n fromBlock?: bigint;\n cursorStore?: IIndexerCursorStore;\n confirmations?: number;\n batchSize?: number;\n pollIntervalMs?: number;\n /**\n * If `true`, the factory calls `indexer.start()` before returning.\n * Default: `false` — the caller decides when to begin polling.\n */\n autoStart?: boolean;\n /**\n * v1.6 — override the MintFeeWrapper address used by the indexer.\n * When omitted, the factory auto-resolves from\n * `getContractAddresses(chainId).mintFeeWrapper`. Pass the\n * dead-zero address (`0x...dEaD`) to force direct-Transfer mode\n * (legacy v1.5, useful for local fork tests).\n */\n mintFeeWrapperAddress?: Address;\n };\n\n /**\n * Redemption restriction config. When provided, the SDK fetches the\n * per-issuer policy from PAFI issuer-api (with 5min cache + default\n * fallback) and exposes preview/evaluate via `service.redemption`.\n * The handler endpoints `handleRedemptionPreview` / `handleRedemptionEvaluate`\n * are wired only when this is configured.\n *\n * `chainId` is taken from the top-level config; the issuer-api URL\n * is looked up via `getPafiServiceUrls(chainId)`. Only credentials\n * + the history store are required here.\n */\n redemption?: {\n issuerId: string;\n apiKey: string;\n historyStore: IRedemptionHistoryStore;\n /** Override fetch (testing). */\n fetchImpl?: typeof fetch;\n /** Per-fetch timeout in ms. Default 1000. */\n fetchTimeoutMs?: number;\n /** Cache TTL in ms. Default 5 * 60 * 1000. */\n cacheTtlMs?: number;\n };\n}\n\n// ---------------------------------------------------------------------------\n// Output shape — everything the factory wires\n// ---------------------------------------------------------------------------\n\nexport interface IssuerService {\n /** AuthService — login, logout, nonce management. */\n auth: AuthService;\n /** Session store — nonce + JWT session persistence. */\n session: ISessionStore;\n ledger: IPointLedger;\n policy: IPolicyEngine;\n /** RelayService — prepareMint / prepareBurn UserOp builders. */\n relay: RelayService;\n /** FeeManager — gas fee estimation. Undefined if not configured. */\n fee: FeeManager | undefined;\n /** All indexers keyed by PointToken address. */\n indexers: Map<Address, PointIndexer>;\n /**\n * First indexer. Kept for backward compat with 0.1.x callers.\n * @deprecated use `indexers.get(tokenAddress)` for multi-token.\n */\n indexer: PointIndexer;\n /** Framework-agnostic HTTP handlers — wire into Express / Fastify / Hono. */\n api: IssuerApiHandlers;\n /**\n * Redemption restriction service. Undefined when `redemption` is not\n * configured — the corresponding handlers throw \"not configured\" at\n * request time.\n */\n redemption: RedemptionService | undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Wire a fully-functional issuer service from a single config object.\n *\n * Defaults:\n * - `sessionStore` → `MemorySessionStore` (dev/test only — replace in prod)\n * - `policy` → `DefaultPolicyEngine({ ledger })`\n * - `feeManager` → undefined (handleGasFee throws until configured)\n * - `poolsProvider` → undefined (handlePools throws until configured)\n * - `indexer.autoStart` → false\n *\n * Throws synchronously if any required field is missing.\n */\nexport function createIssuerService(\n config: IssuerServiceConfig,\n): IssuerService {\n if (!config.provider) {\n throw new Error(\"createIssuerService: provider is required\");\n }\n if (!config.ledger) {\n throw new Error(\"createIssuerService: ledger is required\");\n }\n if (!config.auth?.jwtSecret) {\n throw new Error(\"createIssuerService: auth.jwtSecret is required\");\n }\n if (!config.auth?.domain) {\n throw new Error(\"createIssuerService: auth.domain is required\");\n }\n\n const rawAddresses =\n config.pointTokenAddresses && config.pointTokenAddresses.length > 0\n ? config.pointTokenAddresses\n : config.pointTokenAddress\n ? [config.pointTokenAddress]\n : [];\n if (rawAddresses.length === 0) {\n throw new Error(\n \"createIssuerService: at least one of pointTokenAddress / pointTokenAddresses is required\",\n );\n }\n const tokenAddresses: Address[] = rawAddresses.map((a) => getAddress(a));\n\n const ledger: IPointLedger = config.ledger;\n const sessionStore: ISessionStore =\n config.sessionStore ?? new MemorySessionStore();\n const policy: IPolicyEngine =\n config.policy ?? new DefaultPolicyEngine({ ledger });\n\n const authServiceConfig: ConstructorParameters<typeof AuthService>[0] = {\n sessionStore,\n jwtSecret: config.auth.jwtSecret,\n domain: config.auth.domain,\n chainId: config.chainId,\n };\n if (config.auth.jwtExpiresIn) {\n authServiceConfig.jwtExpiresIn = config.auth.jwtExpiresIn;\n }\n const authService = new AuthService(authServiceConfig);\n\n // RelayService in v1.4 only wraps `prepareMint` / `prepareBurn` —\n // no operator wallet, no broadcasting. Pass `provider + chainId` so\n // the service can auto-quote the operator fee + auto-resolve the\n // PAFI fee recipient when callers don't pass them. Issuer can still\n // override per-call by passing `feeAmount` / `feeRecipient`\n // explicitly.\n const relayService = new RelayService({\n provider: config.provider,\n chainId: config.chainId,\n });\n\n let feeManager: FeeManager | undefined;\n if (config.fee) {\n feeManager = new FeeManager({\n ...config.fee,\n provider: config.provider,\n });\n }\n\n // v1.6 — auto-resolve MintFeeWrapper from chainId so the indexer\n // listens to `MintWithFee` events (where `to` = end user) instead of\n // `Transfer(0x0)` (where `to` = wrapper). Override via\n // `config.indexer.mintFeeWrapperAddress` for local fork tests; pass\n // the dead-zero address there to force direct-Transfer mode.\n const sdkWrapperAddress = getContractAddresses(config.chainId).mintFeeWrapper;\n const wrapperOverride = config.indexer?.mintFeeWrapperAddress;\n const resolvedWrapperAddress: Address | undefined =\n wrapperOverride !== undefined ? wrapperOverride : sdkWrapperAddress;\n\n const indexers = new Map<Address, PointIndexer>();\n for (const tokenAddress of tokenAddresses) {\n const indexerConfig: ConstructorParameters<typeof PointIndexer>[0] = {\n provider: config.provider,\n pointTokenAddress: tokenAddress,\n ledger,\n };\n if (resolvedWrapperAddress !== undefined) {\n indexerConfig.mintFeeWrapperAddress = resolvedWrapperAddress;\n }\n if (config.indexer?.fromBlock !== undefined) {\n indexerConfig.fromBlock = config.indexer.fromBlock;\n }\n if (config.indexer?.cursorStore) {\n indexerConfig.cursorStore = config.indexer.cursorStore;\n }\n if (config.indexer?.confirmations !== undefined) {\n indexerConfig.confirmations = config.indexer.confirmations;\n }\n if (config.indexer?.batchSize !== undefined) {\n indexerConfig.batchSize = config.indexer.batchSize;\n }\n if (config.indexer?.pollIntervalMs !== undefined) {\n indexerConfig.pollIntervalMs = config.indexer.pollIntervalMs;\n }\n indexers.set(tokenAddress, new PointIndexer(indexerConfig));\n }\n const firstIndexer = indexers.get(tokenAddresses[0]!)!;\n\n const chainAddresses = getContractAddresses(config.chainId);\n const resolvedContracts: ApiConfigResponse[\"contracts\"] = {\n batchExecutor: chainAddresses.batchExecutor,\n usdt: chainAddresses.usdt,\n issuerRegistry: chainAddresses.issuerRegistry,\n mintingOracle: chainAddresses.mintingOracle,\n pafiHook: chainAddresses.pafiHook,\n ...config.contracts,\n };\n if (resolvedWrapperAddress !== undefined) {\n resolvedContracts.mintFeeWrapper = resolvedWrapperAddress;\n }\n\n let redemption: RedemptionService | undefined;\n if (config.redemption) {\n const policyConfig: PolicyProviderConfig = {\n chainId: config.chainId,\n issuerId: config.redemption.issuerId,\n apiKey: config.redemption.apiKey,\n };\n if (config.redemption.fetchImpl) policyConfig.fetchImpl = config.redemption.fetchImpl;\n if (config.redemption.fetchTimeoutMs !== undefined) {\n policyConfig.fetchTimeoutMs = config.redemption.fetchTimeoutMs;\n }\n if (config.redemption.cacheTtlMs !== undefined) {\n policyConfig.cacheTtlMs = config.redemption.cacheTtlMs;\n }\n redemption = new RedemptionService({\n policyProvider: new PolicyProvider(policyConfig),\n historyStore: config.redemption.historyStore,\n });\n }\n\n const handlersConfig: ConstructorParameters<typeof IssuerApiHandlers>[0] = {\n authService,\n ledger,\n provider: config.provider,\n pointTokenAddresses: tokenAddresses,\n chainId: config.chainId,\n contracts: resolvedContracts,\n };\n if (feeManager) handlersConfig.feeManager = feeManager;\n if (config.poolsProvider) handlersConfig.poolsProvider = config.poolsProvider;\n if (redemption) handlersConfig.redemption = redemption;\n if (resolvedWrapperAddress !== undefined) {\n handlersConfig.mintFeeWrapperAddress = resolvedWrapperAddress;\n }\n const handlers = new IssuerApiHandlers(handlersConfig);\n\n if (config.indexer?.autoStart) {\n for (const idx of indexers.values()) {\n idx.start();\n }\n }\n\n return {\n auth: authService,\n session: sessionStore,\n ledger,\n policy,\n relay: relayService,\n fee: feeManager,\n indexers,\n indexer: firstIndexer,\n api: handlers,\n redemption,\n };\n}\n","import type {\n BlackoutWindow,\n RedemptionDecision,\n RedemptionDenial,\n RedemptionPolicy,\n RedemptionPolicySource,\n RedemptionPreview,\n} from \"@pafi-dev/core\";\n\nconst SECONDS_PER_DAY = 24 * 60 * 60;\n\nexport interface UserHistory {\n /** Total PT redeemed by user in the rolling 24h window. */\n redeemedLast24hPt: bigint;\n /** Last redemption timestamp (unix seconds), or null if never. */\n lastRedeemedAtUnixSec: number | null;\n}\n\nexport interface EvaluateInput {\n policy: RedemptionPolicy;\n policySource: RedemptionPolicySource;\n history: UserHistory;\n /** Amount being requested. Pass 0n for a pure preview. */\n amountPt: bigint;\n /** Current unix time in seconds (caller-controlled for testability). */\n nowUnixSec: number;\n}\n\n/**\n * Pure evaluator. Given a policy + user history snapshot + requested\n * amount, returns either ALLOW + a preview of the user's remaining\n * headroom, or DENY + the first failing rule.\n *\n * Preview is always populated, even on denial — UI uses it to render\n * \"X PT redeemable now\" / \"next available at HH:MM\" regardless.\n */\nexport function evaluateRedemption(input: EvaluateInput): RedemptionDecision {\n const { policy, history, amountPt, nowUnixSec, policySource } = input;\n\n const dailyRemaining =\n policy.dailyLimitPt > history.redeemedLast24hPt\n ? policy.dailyLimitPt - history.redeemedLast24hPt\n : 0n;\n\n const cooldownUntilUnixSec =\n history.lastRedeemedAtUnixSec !== null\n ? history.lastRedeemedAtUnixSec + policy.cooldownSec\n : null;\n const inCooldown =\n cooldownUntilUnixSec !== null && cooldownUntilUnixSec > nowUnixSec;\n\n const activeBlackout = findActiveBlackout(policy.blackoutWindows, nowUnixSec);\n const nextBlackoutEnd = nextBlackoutEndAfter(\n policy.blackoutWindows,\n nowUnixSec,\n );\n\n // availableAmountPt: largest single redemption that would pass right now\n // ignoring perTxMin (amount=0 case is \"preview\" not \"request\").\n let availableAmountPt: bigint = 0n;\n if (!inCooldown && !activeBlackout) {\n const headroom = dailyRemaining < policy.perTxMaxPt\n ? dailyRemaining\n : policy.perTxMaxPt;\n availableAmountPt = headroom >= policy.perTxMinPt ? headroom : 0n;\n }\n\n const preview: RedemptionPreview = {\n availableAmountPt,\n dailyRemainingPt: dailyRemaining,\n cooldownUntilUnixSec: inCooldown ? cooldownUntilUnixSec : null,\n nextBlackoutEndsAtUnixSec: activeBlackout\n ? activeBlackout.endUnixSec\n : nextBlackoutEnd,\n perTxMinPt: policy.perTxMinPt,\n perTxMaxPt: policy.perTxMaxPt,\n policyVersion: policy.version,\n policySource,\n };\n\n if (amountPt <= 0n) {\n return { allowed: false, preview, denial: rejectAmountBelowMin(policy) };\n }\n\n const denial = firstDenial({\n amountPt,\n policy,\n dailyRemaining,\n inCooldown,\n cooldownUntilUnixSec,\n activeBlackout,\n });\n if (denial) return { allowed: false, denial, preview };\n\n return { allowed: true, preview };\n}\n\nfunction firstDenial(args: {\n amountPt: bigint;\n policy: RedemptionPolicy;\n dailyRemaining: bigint;\n inCooldown: boolean;\n cooldownUntilUnixSec: number | null;\n activeBlackout: BlackoutWindow | null;\n}): RedemptionDenial | null {\n const { amountPt, policy, dailyRemaining, inCooldown, cooldownUntilUnixSec, activeBlackout } = args;\n\n if (activeBlackout) {\n return {\n code: \"BLACKOUT_WINDOW\",\n message: `Redemption is blocked until ${new Date(\n activeBlackout.endUnixSec * 1000,\n ).toISOString()}${activeBlackout.reason ? ` (${activeBlackout.reason})` : \"\"}`,\n };\n }\n if (inCooldown && cooldownUntilUnixSec !== null) {\n return {\n code: \"COOLDOWN_ACTIVE\",\n message: `Cooldown active until ${new Date(\n cooldownUntilUnixSec * 1000,\n ).toISOString()}`,\n };\n }\n if (amountPt < policy.perTxMinPt) {\n return {\n code: \"AMOUNT_BELOW_MIN\",\n message: `amount ${amountPt} below per-tx minimum ${policy.perTxMinPt}`,\n };\n }\n if (amountPt > policy.perTxMaxPt) {\n return {\n code: \"AMOUNT_ABOVE_MAX\",\n message: `amount ${amountPt} above per-tx maximum ${policy.perTxMaxPt}`,\n };\n }\n if (amountPt > dailyRemaining) {\n return {\n code: \"DAILY_LIMIT_EXCEEDED\",\n message: `amount ${amountPt} exceeds daily remaining ${dailyRemaining}`,\n };\n }\n return null;\n}\n\nfunction rejectAmountBelowMin(policy: RedemptionPolicy): RedemptionDenial {\n return {\n code: \"AMOUNT_BELOW_MIN\",\n message: `amount must be >= ${policy.perTxMinPt}`,\n };\n}\n\nfunction findActiveBlackout(\n windows: BlackoutWindow[],\n nowUnixSec: number,\n): BlackoutWindow | null {\n for (const w of windows) {\n if (w.startUnixSec <= nowUnixSec && nowUnixSec < w.endUnixSec) return w;\n }\n return null;\n}\n\nfunction nextBlackoutEndAfter(\n windows: BlackoutWindow[],\n nowUnixSec: number,\n): number | null {\n let earliest: number | null = null;\n for (const w of windows) {\n if (w.endUnixSec > nowUnixSec) {\n if (earliest === null || w.endUnixSec < earliest) earliest = w.endUnixSec;\n }\n }\n return earliest;\n}\n\nexport const REDEMPTION_HISTORY_WINDOW_SEC = SECONDS_PER_DAY;\n","import {\n getPafiServiceUrls,\n type BlackoutWindow,\n type RedemptionPolicy,\n} from \"@pafi-dev/core\";\nimport type { SettlementClientConfig } from \"./types\";\n\nconst DEFAULT_TIMEOUT_MS = 1_000;\n\n/**\n * Either a successful policy fetch or a structured failure. We never\n * throw from `fetchPolicy()` — callers fall back to cache/default on\n * any failure mode, so a thrown error would just force every caller\n * to wrap in try/catch.\n */\nexport type FetchResult =\n | { ok: true; policy: RedemptionPolicy }\n | { ok: false; reason: FetchFailureReason; status?: number };\n\nexport type FetchFailureReason =\n | \"TIMEOUT\"\n | \"NETWORK\"\n | \"NOT_FOUND\"\n | \"UNAUTHORIZED\"\n | \"SERVER_ERROR\"\n | \"INVALID_RESPONSE\";\n\ninterface RawPolicyDto {\n issuerId: string;\n dailyLimitPt: string;\n cooldownSec: number;\n perTxMinPt: string;\n perTxMaxPt: string;\n blackoutWindows: BlackoutWindow[];\n version: string;\n}\n\nexport class SettlementClient {\n private readonly config: {\n baseUrl: string;\n issuerId: string;\n apiKey: string;\n fetchTimeoutMs: number;\n fetchImpl?: typeof fetch;\n };\n\n constructor(config: SettlementClientConfig) {\n if (!config.chainId) throw new Error(\"SettlementClient: chainId is required\");\n if (!config.issuerId) throw new Error(\"SettlementClient: issuerId is required\");\n if (!config.apiKey) throw new Error(\"SettlementClient: apiKey is required\");\n this.config = {\n baseUrl: getPafiServiceUrls(config.chainId).issuerApi.replace(/\\/+$/, \"\"),\n issuerId: config.issuerId,\n apiKey: config.apiKey,\n fetchTimeoutMs: config.fetchTimeoutMs ?? DEFAULT_TIMEOUT_MS,\n fetchImpl: config.fetchImpl,\n };\n }\n\n async fetchPolicy(): Promise<FetchResult> {\n const fetchFn = this.config.fetchImpl ?? fetch;\n const url = `${this.config.baseUrl}/issuers/${encodeURIComponent(this.config.issuerId)}/redemption-policy`;\n\n const controller = new AbortController();\n const timer = setTimeout(\n () => controller.abort(),\n this.config.fetchTimeoutMs,\n );\n\n let response: Response;\n try {\n response = await fetchFn(url, {\n method: \"GET\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.config.apiKey}`,\n \"X-Issuer-Id\": this.config.issuerId,\n },\n signal: controller.signal,\n });\n } catch (err: unknown) {\n const isAbort =\n err instanceof Error &&\n (err.name === \"AbortError\" ||\n /aborted|timeout/i.test(err.message ?? \"\"));\n return { ok: false, reason: isAbort ? \"TIMEOUT\" : \"NETWORK\" };\n } finally {\n clearTimeout(timer);\n }\n\n if (response.status === 404) {\n return { ok: false, reason: \"NOT_FOUND\", status: 404 };\n }\n if (response.status === 401 || response.status === 403) {\n return { ok: false, reason: \"UNAUTHORIZED\", status: response.status };\n }\n if (!response.ok) {\n return { ok: false, reason: \"SERVER_ERROR\", status: response.status };\n }\n\n let raw: unknown;\n try {\n raw = await response.json();\n } catch {\n return { ok: false, reason: \"INVALID_RESPONSE\", status: response.status };\n }\n\n const parsed = parsePolicyDto(raw);\n if (!parsed) {\n return { ok: false, reason: \"INVALID_RESPONSE\", status: response.status };\n }\n return { ok: true, policy: parsed };\n }\n}\n\nfunction parsePolicyDto(raw: unknown): RedemptionPolicy | null {\n if (!raw || typeof raw !== \"object\") return null;\n const dto = raw as Partial<RawPolicyDto>;\n if (\n typeof dto.issuerId !== \"string\" ||\n typeof dto.dailyLimitPt !== \"string\" ||\n typeof dto.cooldownSec !== \"number\" ||\n typeof dto.perTxMinPt !== \"string\" ||\n typeof dto.perTxMaxPt !== \"string\" ||\n typeof dto.version !== \"string\" ||\n !Array.isArray(dto.blackoutWindows)\n ) {\n return null;\n }\n try {\n return {\n issuerId: dto.issuerId,\n dailyLimitPt: BigInt(dto.dailyLimitPt),\n cooldownSec: dto.cooldownSec,\n perTxMinPt: BigInt(dto.perTxMinPt),\n perTxMaxPt: BigInt(dto.perTxMaxPt),\n blackoutWindows: dto.blackoutWindows\n .map(normalizeBlackout)\n .filter((b): b is BlackoutWindow => b !== null),\n version: dto.version,\n };\n } catch {\n return null;\n }\n}\n\nfunction normalizeBlackout(raw: unknown): BlackoutWindow | null {\n if (!raw || typeof raw !== \"object\") return null;\n const win = raw as Partial<BlackoutWindow>;\n if (\n typeof win.startUnixSec !== \"number\" ||\n typeof win.endUnixSec !== \"number\" ||\n win.startUnixSec >= win.endUnixSec\n ) {\n return null;\n }\n return {\n startUnixSec: win.startUnixSec,\n endUnixSec: win.endUnixSec,\n reason: typeof win.reason === \"string\" ? win.reason : undefined,\n };\n}\n","import type { RedemptionPolicy } from \"@pafi-dev/core\";\n\nconst PT_DECIMALS = 10n ** 18n;\n\n/**\n * SDK-side fallback used when settlement-api is unreachable, returns\n * 404, or returns 5xx. Keep it permissive enough that an outage doesn't\n * lock all users out, but tight enough that it's not an abuse vector.\n */\nexport const DEFAULT_REDEMPTION_POLICY: RedemptionPolicy = {\n issuerId: \"default\",\n dailyLimitPt: 1_000n * PT_DECIMALS,\n cooldownSec: 60,\n perTxMinPt: 1n * PT_DECIMALS,\n perTxMaxPt: 500n * PT_DECIMALS,\n blackoutWindows: [],\n version: \"default-v1\",\n};\n\nexport function defaultPolicyFor(issuerId: string): RedemptionPolicy {\n return { ...DEFAULT_REDEMPTION_POLICY, issuerId };\n}\n","import type { RedemptionPolicy, RedemptionPolicySource } from \"@pafi-dev/core\";\nimport { SettlementClient } from \"./settlementClient\";\nimport { defaultPolicyFor } from \"./defaults\";\nimport type { PolicyProviderConfig } from \"./types\";\n\nconst DEFAULT_CACHE_TTL_MS = 5 * 60 * 1000;\n\ninterface CacheEntry {\n policy: RedemptionPolicy;\n expiresAtMs: number;\n}\n\nexport interface ResolvedPolicy {\n policy: RedemptionPolicy;\n source: RedemptionPolicySource;\n}\n\n/**\n * Wraps SettlementClient with a 5-minute TTL cache and a hardcoded\n * default fallback. Single-flight: concurrent getPolicy() calls during\n * a cache miss share the same in-flight request.\n *\n * Resolution order:\n * 1. fresh cache hit → return cached\n * 2. cache miss → fetch ok → cache + return (source=settlement)\n * 3. cache miss → fetch failed → DEFAULT_REDEMPTION_POLICY (source=default)\n *\n * Stale cache is NEVER returned — once the TTL expires we either fetch\n * fresh data or fall through to default. This keeps behavior predictable:\n * \"default\" means \"we couldn't talk to settlement RIGHT NOW\", not \"we got\n * lucky with a stale entry\".\n */\nexport class PolicyProvider {\n private readonly client: SettlementClient;\n private readonly issuerId: string;\n private readonly cacheTtlMs: number;\n private readonly now: () => number;\n\n private cache: CacheEntry | null = null;\n private inflight: Promise<ResolvedPolicy> | null = null;\n\n constructor(config: PolicyProviderConfig) {\n this.client = new SettlementClient(config);\n this.issuerId = config.issuerId;\n this.cacheTtlMs = config.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;\n this.now = config.now ?? (() => Date.now());\n }\n\n async getPolicy(): Promise<ResolvedPolicy> {\n const fresh = this.readCache();\n if (fresh) return { policy: fresh, source: \"cache\" };\n\n if (this.inflight) return this.inflight;\n\n this.inflight = this.fetchAndStore().finally(() => {\n this.inflight = null;\n });\n return this.inflight;\n }\n\n /** Drop cached policy. Next getPolicy() will refetch. */\n invalidate(): void {\n this.cache = null;\n }\n\n private readCache(): RedemptionPolicy | null {\n if (!this.cache) return null;\n if (this.cache.expiresAtMs <= this.now()) {\n this.cache = null;\n return null;\n }\n return this.cache.policy;\n }\n\n private async fetchAndStore(): Promise<ResolvedPolicy> {\n const result = await this.client.fetchPolicy();\n if (result.ok) {\n this.cache = {\n policy: result.policy,\n expiresAtMs: this.now() + this.cacheTtlMs,\n };\n return { policy: result.policy, source: \"settlement\" };\n }\n return { policy: defaultPolicyFor(this.issuerId), source: \"default\" };\n }\n}\n","import type { Address } from \"viem\";\nimport type { RedemptionDecision, RedemptionPreview } from \"@pafi-dev/core\";\nimport { evaluateRedemption, REDEMPTION_HISTORY_WINDOW_SEC } from \"./evaluator\";\nimport { PolicyProvider } from \"./policyProvider\";\nimport type {\n IRedemptionHistoryStore,\n PolicyProviderConfig,\n} from \"./types\";\n\nexport interface RedemptionServiceConfig {\n policyProvider: PolicyProvider | PolicyProviderConfig;\n historyStore: IRedemptionHistoryStore;\n /** Defaults to () => Math.floor(Date.now() / 1000). */\n nowUnixSec?: () => number;\n}\n\n/**\n * High-level facade used by HTTP handlers. Combines PolicyProvider +\n * IRedemptionHistoryStore into preview/evaluate operations.\n *\n * preview(user) → RedemptionPreview (no amount required)\n * evaluate(user, amount) → RedemptionDecision\n * recordSuccessfulInitiate(..) → call AFTER signing the BurnRequest\n *\n * Note: this service does NOT mutate anything during evaluate(). The\n * caller must call recordSuccessfulInitiate() after the BurnRequest is\n * signed and the pending credit is reserved on the ledger. Splitting\n * \"decide\" from \"record\" lets the handler atomically reserve + record\n * under one DB transaction if it wants.\n */\nexport class RedemptionService {\n private readonly policyProvider: PolicyProvider;\n private readonly historyStore: IRedemptionHistoryStore;\n private readonly nowUnixSec: () => number;\n\n constructor(config: RedemptionServiceConfig) {\n this.policyProvider =\n config.policyProvider instanceof PolicyProvider\n ? config.policyProvider\n : new PolicyProvider(config.policyProvider);\n this.historyStore = config.historyStore;\n this.nowUnixSec =\n config.nowUnixSec ?? (() => Math.floor(Date.now() / 1000));\n }\n\n async preview(\n user: Address,\n pointTokenAddress?: Address,\n ): Promise<RedemptionPreview> {\n const decision = await this.evaluate(user, 0n, pointTokenAddress);\n return decision.preview;\n }\n\n async evaluate(\n user: Address,\n amountPt: bigint,\n pointTokenAddress?: Address,\n ): Promise<RedemptionDecision> {\n const { policy, source } = await this.policyProvider.getPolicy();\n const now = this.nowUnixSec();\n\n const [redeemedLast24hPt, lastRedeemedAtUnixSec] = await Promise.all([\n this.historyStore.sumRedeemedSince(\n user,\n now - REDEMPTION_HISTORY_WINDOW_SEC,\n pointTokenAddress,\n ),\n this.historyStore.getLastRedeemedAtUnixSec(user, pointTokenAddress),\n ]);\n\n return evaluateRedemption({\n policy,\n policySource: source,\n history: { redeemedLast24hPt, lastRedeemedAtUnixSec },\n amountPt,\n nowUnixSec: now,\n });\n }\n\n async recordSuccessfulInitiate(entry: {\n user: Address;\n amountPt: bigint;\n pointTokenAddress?: Address;\n reservationId?: string;\n }): Promise<void> {\n await this.historyStore.recordRedemption({\n ...entry,\n unixSec: this.nowUnixSec(),\n });\n }\n}\n","import { getAddress } from \"viem\";\nimport type { Address, PublicClient } from \"viem\";\nimport {\n POINT_TOKEN_V2_ABI,\n issuerRegistryAbi,\n getContractAddresses,\n getTokenCap,\n} from \"@pafi-dev/core\";\nimport {\n IssuerStateError,\n type IssuerRegistryRecord,\n type PreValidateMintResult,\n type TokenCapRecord,\n} from \"./types\";\n\nconst ISSUER_RECORD_TTL_MS = 30_000;\n\ninterface CachedState {\n value: PreValidateMintResult;\n expiresAt: number;\n}\n\n/**\n * Pure (framework-agnostic) validator for issuer state.\n *\n * Reads IssuerRegistry + PointToken on-chain state and pre-validates\n * mint requests before the user submits a UserOp. Catching these\n * off-chain lets issuers fail fast with a clear error rather than\n * wasting gas on a revert.\n *\n * Caching:\n * - `PointToken.issuer()` — memoized for the process lifetime (immutable)\n * - Full state (registry + totalSupply) — 30s TTL per PointToken\n * - Burst calls while a fetch is in-flight share the same Promise\n * (thundering-herd protection)\n *\n * Usage in NestJS: wrap this in an `@Injectable()` service; pass\n * `PublicClient` and `registryAddress` from your DI container.\n */\nexport class IssuerStateValidator {\n private readonly pointTokenIssuerCache = new Map<Address, Address>();\n private readonly stateCache = new Map<Address, CachedState>();\n private readonly inflight = new Map<Address, Promise<PreValidateMintResult>>();\n\n constructor(\n private readonly provider: PublicClient,\n private readonly registryAddress: Address,\n ) {}\n\n /**\n * Convenience factory — reads `registryAddress` from the SDK\n * `CONTRACT_ADDRESSES` map for the given chain.\n */\n static forChain(provider: PublicClient, chainId: number): IssuerStateValidator {\n const { issuerRegistry } = getContractAddresses(chainId);\n return new IssuerStateValidator(provider, issuerRegistry);\n }\n\n /**\n * Invalidate cached state for one PointToken, or everything if omitted.\n * Call after admin txs that change registry or cap settings.\n */\n invalidate(pointToken?: Address): void {\n if (pointToken) {\n const key = getAddress(pointToken);\n this.pointTokenIssuerCache.delete(key);\n this.stateCache.delete(key);\n this.inflight.delete(key);\n } else {\n this.pointTokenIssuerCache.clear();\n this.stateCache.clear();\n this.inflight.clear();\n }\n }\n\n /**\n * Resolve `PointToken.issuer()` once per token and memoize.\n * The issuer field is set at `initialize()` and never changes.\n */\n async getIssuerAddressForPointToken(pointToken: Address): Promise<Address> {\n const key = getAddress(pointToken);\n const cached = this.pointTokenIssuerCache.get(key);\n if (cached) return cached;\n\n const issuer = (await this.provider.readContract({\n address: key,\n abi: POINT_TOKEN_V2_ABI,\n functionName: \"issuer\",\n })) as Address;\n this.pointTokenIssuerCache.set(key, getAddress(issuer));\n return getAddress(issuer);\n }\n\n /**\n * Read registry record + totalSupply, with 30s cache and in-flight\n * deduplication. Does NOT throw on inactive/missing — returns raw state.\n */\n async getIssuerState(pointToken: Address): Promise<PreValidateMintResult> {\n const tokenAddr = getAddress(pointToken);\n const now = Date.now();\n\n const cached = this.stateCache.get(tokenAddr);\n if (cached && cached.expiresAt > now) return cached.value;\n\n const existing = this.inflight.get(tokenAddr);\n if (existing) return existing;\n\n const promise = this.fetchIssuerState(tokenAddr)\n .then((state) => {\n this.stateCache.set(tokenAddr, {\n value: state,\n expiresAt: Date.now() + ISSUER_RECORD_TTL_MS,\n });\n return state;\n })\n .finally(() => {\n this.inflight.delete(tokenAddr);\n });\n\n this.inflight.set(tokenAddr, promise);\n return promise;\n }\n\n /**\n * Validate that `amount` PT can be minted on `pointToken` right now.\n *\n * Throws `IssuerStateError` with:\n * - `ISSUER_NOT_REGISTERED` — registry has no record for this issuer\n * - `ISSUER_INACTIVE` — issuer.active is false\n * - `MINT_CAP_EXCEEDED` — totalSupply + amount would exceed hardCap\n *\n * Returns the fetched state on success so callers can log without a\n * second RPC round-trip.\n */\n async preValidateMint(\n pointToken: Address,\n amount: bigint,\n ): Promise<PreValidateMintResult> {\n let state: PreValidateMintResult;\n try {\n state = await this.getIssuerState(pointToken);\n } catch (err) {\n if ((err as Error).message.includes(\"IssuerNotFound\")) {\n throw new IssuerStateError(\n \"ISSUER_NOT_REGISTERED\",\n `IssuerRegistry has no record for PointToken ${pointToken}`,\n { pointToken },\n );\n }\n throw err;\n }\n\n const { issuer, totalSupply, hardCap, remaining } = state;\n\n if (!issuer.active) {\n throw new IssuerStateError(\n \"ISSUER_INACTIVE\",\n `Issuer ${issuer.issuerAddress} is deactivated on IssuerRegistry`,\n { issuer: issuer.issuerAddress, pointToken: issuer.pointToken },\n );\n }\n\n if (totalSupply + amount > hardCap) {\n throw new IssuerStateError(\n \"MINT_CAP_EXCEEDED\",\n `Requested ${amount} PT would exceed mint cap. ` +\n `Cap=${hardCap}, minted=${totalSupply}, remaining=${remaining}`,\n {\n requested: amount.toString(),\n cap: hardCap.toString(),\n minted: totalSupply.toString(),\n remaining: remaining.toString(),\n },\n );\n }\n\n return state;\n }\n\n private async fetchIssuerState(tokenAddr: Address): Promise<PreValidateMintResult> {\n const issuerAddr = await this.getIssuerAddressForPointToken(tokenAddr);\n\n // First read the issuer record to learn which oracle owns this token's\n // cap. Cap config moved off the issuer in v1.6 (per-token, not\n // per-issuer) — `MintingOracle.tokenCaps(pointToken)` is the source.\n //\n // Use the auto-generated struct ABI directly. Earlier code used a\n // flat-output workaround for a viem ≤2.48 decoding bug, but that\n // workaround stopped matching the on-chain wire format once v1.6\n // added a second `string` (symbol) to the struct — the function\n // now returns a dynamic tuple with an outer 0x20 offset that flat\n // ABI mis-decodes (manifests as \"Number ... not in safe integer\n // range\"). Modern viem (≥2.47.x) decodes struct returns correctly.\n const issuerStruct = (await this.provider.readContract({\n address: this.registryAddress,\n abi: issuerRegistryAbi,\n functionName: \"getIssuer\",\n args: [issuerAddr],\n })) as {\n issuerAddress: Address;\n signerAddress: Address;\n name: string;\n symbol: string;\n active: boolean;\n pointToken: Address;\n mintingOracle: Address;\n };\n\n const issuer: IssuerRegistryRecord = {\n issuerAddress: issuerStruct.issuerAddress,\n signerAddress: issuerStruct.signerAddress,\n name: issuerStruct.name,\n symbol: issuerStruct.symbol,\n active: issuerStruct.active,\n pointToken: issuerStruct.pointToken,\n mintingOracle: issuerStruct.mintingOracle,\n };\n\n const [tokenCap, totalSupply] = await Promise.all([\n getTokenCap(this.provider, issuer.mintingOracle, tokenAddr),\n this.provider.readContract({\n address: tokenAddr,\n abi: POINT_TOKEN_V2_ABI,\n functionName: \"totalSupply\",\n }) as Promise<bigint>,\n ]);\n\n const tokenCapRecord: TokenCapRecord = {\n declaredTotalSupply: tokenCap.declaredTotalSupply,\n capBasisPoints: tokenCap.capBasisPoints,\n };\n\n const hardCap =\n (tokenCapRecord.declaredTotalSupply *\n BigInt(tokenCapRecord.capBasisPoints)) /\n 10000n;\n const remaining = hardCap > totalSupply ? hardCap - totalSupply : 0n;\n\n return {\n issuer,\n tokenCap: tokenCapRecord,\n totalSupply,\n hardCap,\n remaining,\n };\n }\n}\n","import type { Address } from \"viem\";\nimport type { IRedemptionHistoryStore } from \"./types\";\n\ninterface Entry {\n user: string;\n amountPt: bigint;\n pointTokenAddress: string | null;\n unixSec: number;\n}\n\n/**\n * In-memory IRedemptionHistoryStore for tests + the bundled NestJS\n * example. Production issuers should implement this against their\n * existing burn/audit table — sumRedeemedSince is hot path on every\n * redemption preview.\n */\nexport class MemoryRedemptionHistoryStore implements IRedemptionHistoryStore {\n private readonly entries: Entry[] = [];\n\n async sumRedeemedSince(\n user: Address,\n sinceUnixSec: number,\n pointTokenAddress?: Address,\n ): Promise<bigint> {\n const userKey = user.toLowerCase();\n const tokenKey = pointTokenAddress?.toLowerCase() ?? null;\n let total = 0n;\n for (const e of this.entries) {\n if (e.user !== userKey) continue;\n if (e.unixSec < sinceUnixSec) continue;\n if (tokenKey !== null && e.pointTokenAddress !== tokenKey) continue;\n total += e.amountPt;\n }\n return total;\n }\n\n async getLastRedeemedAtUnixSec(\n user: Address,\n pointTokenAddress?: Address,\n ): Promise<number | null> {\n const userKey = user.toLowerCase();\n const tokenKey = pointTokenAddress?.toLowerCase() ?? null;\n let latest: number | null = null;\n for (const e of this.entries) {\n if (e.user !== userKey) continue;\n if (tokenKey !== null && e.pointTokenAddress !== tokenKey) continue;\n if (latest === null || e.unixSec > latest) latest = e.unixSec;\n }\n return latest;\n }\n\n async recordRedemption(entry: {\n user: Address;\n amountPt: bigint;\n pointTokenAddress?: Address;\n unixSec: number;\n }): Promise<void> {\n this.entries.push({\n user: entry.user.toLowerCase(),\n amountPt: entry.amountPt,\n pointTokenAddress: entry.pointTokenAddress?.toLowerCase() ?? null,\n unixSec: entry.unixSec,\n });\n }\n}\n","// @pafi/issuer — Issuer backend boilerplate built on top of @pafi/core.\n//\n// Public API surface is exported in phases (see TASK_TRACKER §1.5):\n// Phase 1 — ledger / policy / signer / session store (interfaces + defaults)\n// Phase 2 — AuthService + JWT middleware\n// Phase 3 — RelayService + FeeManager\n// Phase 4 — MintingGateway orchestrator\n// Phase 5 — PointIndexer\n// Phase 6 — IssuerApiHandlers\n// Phase 7 — createIssuerService factory\n\ndeclare const __PAFI_ISSUER_SDK_VERSION__: string | undefined;\nexport const PAFI_ISSUER_SDK_VERSION: string =\n typeof __PAFI_ISSUER_SDK_VERSION__ === \"string\"\n ? __PAFI_ISSUER_SDK_VERSION__\n : \"dev\";\n\n// -----------------------------------------------------------------------------\n// SDK error base — every typed error inherits from this\n// -----------------------------------------------------------------------------\nexport {\n PafiSdkError,\n ValidationError,\n ConfigurationError,\n SDK_ERROR_HTTP_STATUS_CODE,\n defaultErrorTypeForStatus,\n type SdkErrorHttpStatus,\n type PafiErrorType,\n} from \"./errors\";\n\n// -----------------------------------------------------------------------------\n// HTTP — framework-agnostic error envelope normalizer\n// -----------------------------------------------------------------------------\nexport * from \"./http\";\n\n// -----------------------------------------------------------------------------\n// Phase 1 — core interfaces + default in-memory implementations\n// -----------------------------------------------------------------------------\nexport * from \"./ledger\";\nexport * from \"./policy\";\n\n// -----------------------------------------------------------------------------\n// Phase 2 — Auth\n// (includes ISessionStore, MemorySessionStore, AuthService, AuthError,\n// NonceManager, authenticateRequest)\n// -----------------------------------------------------------------------------\nexport * from \"./auth\";\n\n// -----------------------------------------------------------------------------\n// Phase 3 — RelayService (prepareMint / prepareBurn) + FeeManager\n// -----------------------------------------------------------------------------\nexport * from \"./relay\";\n\n// -----------------------------------------------------------------------------\n// Phase 5 — On-chain mint event indexer\n// -----------------------------------------------------------------------------\nexport * from \"./indexer\";\n\n// -----------------------------------------------------------------------------\n// Phase 6 — Framework-agnostic HTTP handlers\n// -----------------------------------------------------------------------------\nexport * from \"./api\";\n\n// -----------------------------------------------------------------------------\n// Pools — optional subgraph-backed PoolsProvider helper\n// -----------------------------------------------------------------------------\nexport * from \"./pools\";\n\n// -----------------------------------------------------------------------------\n// v1.4 — Balance aggregation (off-chain ledger + on-chain balanceOf)\n// -----------------------------------------------------------------------------\nexport * from \"./balance\";\n\n// -----------------------------------------------------------------------------\n// v1.4 — PAFI Backend paymaster proxy client (replaces direct Coinbase)\n// -----------------------------------------------------------------------------\nexport * from \"./pafi-backend\";\n\n// -----------------------------------------------------------------------------\n// Phase 7 — Top-level factory + IssuerService type\n// -----------------------------------------------------------------------------\nexport {\n createIssuerService,\n type IssuerServiceConfig,\n type IssuerService,\n} from \"./config\";\n\n// -----------------------------------------------------------------------------\n// v1.4 — Mobile prepare/submit pattern: pending UserOp storage types\n// -----------------------------------------------------------------------------\nexport * from \"./userop-store\";\n\n// -----------------------------------------------------------------------------\n// v1.4 — IssuerStateValidator: pre-validate mint cap + active status off-chain\n// -----------------------------------------------------------------------------\nexport * from \"./issuer-state\";\n\n// -----------------------------------------------------------------------------\n// Redemption restriction (settlement-fed policy + per-user history)\n// -----------------------------------------------------------------------------\nexport * from \"./redemption\";\n"],"mappings":"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","names":["randomBytes","getAddress","getAddress","randomBytes","getAddress","getAddress","getAddress","parseAbiItem","TRANSFER_EVENT","parseAbiItem","ZERO_ADDRESS","DEFAULT_CONFIRMATIONS","DEFAULT_BATCH_SIZE","DEFAULT_POLL_INTERVAL_MS","getAddress","getAddress","getAddress","result","getAddress","POINT_TOKEN_V2_ABI","getPointTokenBalance","getContractAddresses","getAddress","POINT_TOKEN_V2_ABI","getContractAddresses","getPointTokenBalance","getAddress","getAddress","getAddress","getContractAddresses","isNoWrapper","DEFAULT_SIG_DEADLINE_SEC","getAddress","getContractAddresses","decodeBatchExecuteCalls","getContractAddresses","getContractAddresses","decodeBatchExecuteCalls","ENTRY_POINT_V08","computeUserOpHash","buildUserOpTypedData","getContractAddresses","serializeUserOpToJsonRpc","getAddress","getContractAddresses","computeUserOpHash","buildUserOpTypedData","ENTRY_POINT_V08","getAddress","decodeBatchExecuteCalls","ENTRY_POINT_V08","getContractAddresses","parseEip7702DelegatedAddress","getAddress","decodeBatchExecuteCalls","getContractAddresses","parseEip7702DelegatedAddress","DEFAULT_CACHE_TTL_MS","getPointTokenBalance","getAddress","getContractAddresses","getPafiServiceUrls","DEFAULT_CACHE_TTL_MS","getAddress","getContractAddresses","getAddress","POINT_TOKEN_V2_ABI","getContractAddresses","getContractAddresses","getAddress","POINT_TOKEN_V2_ABI"]}
|