@pafi-dev/issuer 0.5.28 → 0.5.30

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../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/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/handlers/topUpRedemptionHandler.ts","../src/pools/subgraphPoolsProvider.ts","../src/pools/subgraphNativeUsdtQuoter.ts","../src/pools/nativePtQuoter.ts","../src/balance/balanceAggregator.ts","../src/pafi-backend/types.ts","../src/pafi-backend/client.ts","../src/config.ts","../src/userop-store/serialize.ts","../src/issuer-state/validator.ts","../src/issuer-state/types.ts"],"sourcesContent":["// @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\n/** SDK package version — bumped on every release */\nexport const PAFI_ISSUER_SDK_VERSION = \"0.4.0\";\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","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\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 (process.env.NODE_ENV === 'production') {\n console.error(\n '[PAFI] MemorySessionStore is not safe for multi-process/K8s deployments. ' +\n 'Session revocations are NOT propagated across pods. ' +\n 'Use a Redis-backed session store in production.'\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\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 /** 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 nonceManager: NonceManager;\n private readonly now: () => Date;\n\n constructor(config: AuthServiceConfig) {\n if (!config.jwtSecret || config.jwtSecret.length < 32) {\n throw new Error(\"AuthService: jwtSecret must be at least 32 characters for HS256 security\");\n }\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.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 const token = await 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 .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 // Best-effort: if the token is malformed or already expired we still\n // try to decode enough to find a jti and revoke it. If we can't find a\n // jti at all, silently succeed — logout should be idempotent.\n try {\n const { payload } = await jwtVerify(token, this.jwtSecret, {\n clockTolerance: 60, // allow logout right after expiry\n });\n if (payload.jti) {\n await this.sessionStore.revokeSession(payload.jti);\n }\n } catch (err) {\n // Swallow \"not found\" silently — token may already be expired/revoked\n // Re-throw or log genuine storage errors\n const msg = err instanceof Error ? err.message : String(err);\n if (!msg.includes('not found') && !msg.includes('expired')) {\n console.error('[PAFI] AuthService logout: session store error', err);\n }\n }\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 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","/**\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\nexport class AuthError extends Error {\n readonly code: AuthErrorCode;\n\n constructor(code: AuthErrorCode, message: string) {\n super(message);\n this.name = \"AuthError\";\n this.code = 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 * Errors raised by RelayService carry a `code` so callers (handlers /\n * gateway-less HTTP wrappers) can decide how to map them to HTTP status.\n *\n * v1.4 trimmed the error space to just encoding failures — the service\n * no longer broadcasts transactions, so `SUBMIT_FAILED`, `TX_REVERTED`,\n * `SIMULATION_FAILED`, and `TIMEOUT` all went away with the operator\n * wallet. Paymaster/Bundler errors surface out-of-band on the FE.\n */\nexport type RelayErrorCode =\n | \"NOT_CONFIGURED\" // required field missing\n | \"ENCODE_FAILED\"; // calldata encoding / signing threw\n\nexport class RelayError extends Error {\n readonly code: RelayErrorCode;\n readonly cause?: unknown;\n\n constructor(code: RelayErrorCode, message: string, cause?: unknown) {\n super(message);\n this.name = \"RelayError\";\n this.code = code;\n if (cause !== undefined) this.cause = cause;\n }\n}\n","import {\n encodeFunctionData,\n erc20Abi,\n type Address,\n type Hex,\n type WalletClient,\n} from \"viem\";\nimport {\n POINT_TOKEN_V2_ABI,\n buildPartialUserOperation,\n signMintRequest,\n type Operation,\n type PartialUserOperation,\n type PointTokenDomainConfig,\n type BurnRequest,\n} from \"@pafi-dev/core\";\nimport { RelayError } from \"./types\";\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 /**\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 MintRequest with the issuer minter signer.\n let minterSig: Hex;\n try {\n const sig = await signMintRequest(\n params.issuerSignerWallet,\n params.domain,\n {\n to: params.userAddress,\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 MintRequest: ${errorMessage(err)}`,\n err,\n );\n }\n\n // 2. Encode sig-gated mint: mint(address,uint256,uint256,bytes).\n let mintCallData: Hex;\n try {\n mintCallData = encodeFunctionData({\n abi: POINT_TOKEN_V2_ABI,\n functionName: \"mint\",\n args: [params.userAddress, params.amount, params.deadline, minterSig],\n });\n } catch (err) {\n throw new RelayError(\n \"ENCODE_FAILED\",\n `prepareMint: failed to encode PointToken.mint: ${errorMessage(err)}`,\n err,\n );\n }\n\n const operations: Operation[] = [\n {\n target: params.pointTokenAddress,\n value: 0n,\n data: mintCallData,\n },\n ];\n\n // 3. Optional fee transfer (application-level, no Relayer).\n if (params.feeAmount && params.feeAmount > 0n) {\n if (!params.feeRecipient) {\n throw new RelayError(\n \"ENCODE_FAILED\",\n \"prepareMint: feeRecipient required when feeAmount > 0\",\n );\n }\n if (params.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: [params.feeRecipient, params.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).\n *\n * Two modes:\n * - `mode: 'burn'` — direct `PointToken.burn(from, amount)`; only\n * usable if the caller (via EIP-7702) is whitelisted as a burner.\n * Rare in v1.4; kept for admin/operator tools.\n * - `mode: 'burnWithSig'` — `PointToken.burn(from, amount, deadline,\n * burnerSig)`. Caller provides a pre-signed `BurnRequest` + sig\n * bytes (typically from `PTRedeemHandler`).\n */\n prepareBurn(params: PrepareBurnParams): 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\n let burnCallData: Hex;\n try {\n if (params.mode === \"burnWithSig\") {\n if (!params.burnRequest || !params.burnerSignature) {\n throw new Error(\"burnWithSig requires burnRequest + burnerSignature\");\n }\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 } else {\n burnCallData = encodeFunctionData({\n abi: POINT_TOKEN_V2_ABI,\n functionName: \"burn\",\n args: [params.userAddress, params.amount],\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 if (params.feeAmount && params.feeAmount > 0n) {\n if (!params.feeRecipient) {\n throw new RelayError(\n \"ENCODE_FAILED\",\n \"prepareBurn: feeRecipient required when feeAmount > 0\",\n );\n }\n if (params.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: [params.feeRecipient, params.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 — 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\nexport type PrepareBurnParams =\n | PrepareBurnDirectParams\n | PrepareBurnWithSigParams;\n\ninterface PrepareBurnCommonParams {\n userAddress: Address;\n aaNonce: bigint;\n pointTokenAddress: Address;\n batchExecutorAddress: Address;\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\nexport interface PrepareBurnDirectParams extends PrepareBurnCommonParams {\n mode: \"burn\";\n amount: bigint;\n}\n\nexport interface PrepareBurnWithSigParams extends PrepareBurnCommonParams {\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\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 Coinbase Paymaster 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, Hex, 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 * 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\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 * Watches `PointToken.Transfer(from=0x0 → to)` events and finalizes the\n * issuer ledger on each confirmed mint.\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 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\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 = config.pointTokenAddress;\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 }\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()` schedules the next one at the end.\n void this.tick();\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 console.error('[PAFI] PointIndexer tick error:', err);\n }\n this.scheduleNext();\n }\n\n private scheduleNext(): void {\n if (!this.running) return;\n this.timer = setTimeout(() => void this.tick(), this.pollIntervalMs);\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 logs = await this.provider.getLogs({\n address: this.pointTokenAddress,\n event: TRANSFER_EVENT,\n args: { from: ZERO_ADDRESS },\n fromBlock: cursor,\n toBlock: chunkEnd,\n });\n\n const events = this.decodeMintEvents(logs);\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 // Internals\n // -------------------------------------------------------------------------\n\n private decodeMintEvents(\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 * 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\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\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\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 void this.tick();\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 console.error('[PAFI] BurnIndexer tick error:', err);\n }\n this.scheduleNext();\n }\n\n private scheduleNext(): void {\n if (!this.running) return;\n this.timer = setTimeout(() => void this.tick(), this.pollIntervalMs);\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, WalletClient } from \"viem\";\nimport {\n getMintRequestNonce,\n getPointTokenBalance,\n getReceiverConsentNonce,\n getTokenName,\n isMinter,\n} from \"@pafi-dev/core\";\nimport type { PointTokenDomainConfig } from \"@pafi-dev/core\";\nimport type { AuthService } from \"../auth/loginVerifier\";\nimport type { IPointLedger } from \"../ledger/types\";\nimport type { FeeManager } from \"../relay/feeManager\";\nimport type { RelayService } from \"../relay/relayService\";\nimport type { IPolicyEngine } from \"../policy/types\";\nimport type {\n ApiClaimRequest,\n ApiClaimResponse,\n ApiConfigResponse,\n ApiGasFeeResponse,\n ApiLoginRequest,\n ApiLoginResponse,\n ApiNonceResponse,\n ApiPoolsRequest,\n ApiPoolsResponse,\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 // -- 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 `handleClaim`; omit to disable the endpoint.\n * Wires policy evaluation + ledger locking + MintRequest signing\n * into a single atomic handler so callers cannot accidentally skip\n * the policy check.\n */\n claim?: {\n policy: IPolicyEngine;\n relayService: RelayService;\n issuerSignerWallet: WalletClient;\n batchExecutorAddress: Address;\n /** How long to hold the off-chain lock while the UserOp is in flight. Default: 15 min. */\n lockDurationMs?: number;\n };\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 claim?: IssuerApiHandlersConfig[\"claim\"];\n\n constructor(config: IssuerApiHandlersConfig) {\n this.authService = config.authService;\n this.ledger = config.ledger;\n this.provider = config.provider;\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 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.claim) this.claim = config.claim;\n }\n\n // =========================================================================\n // Public handlers (no auth required)\n // =========================================================================\n\n /** `GET /auth/nonce` */\n async handleGetNonce(): Promise<ApiNonceResponse> {\n const nonce = await this.authService.getNonce();\n return { nonce };\n }\n\n /** `POST /auth/login` */\n async handleLogin(\n body: ApiLoginRequest,\n ): Promise<ApiLoginResponse> {\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 Error(\"handleLogin: message and signature are required\");\n }\n if (body.message.length > 4096) {\n throw new Error(\"message too long\");\n }\n if (body.signature.length > 260) {\n throw new Error(\"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 Error(\"invalid chainId\");\n }\n if (chainId !== this.chainId) {\n throw new Error(\n `handleConfig: unsupported chainId ${chainId}`,\n );\n }\n const contracts: ApiConfigResponse[\"contracts\"] = {\n ...this.contracts,\n pointTokens: Array.from(this.supportedTokens),\n };\n const response: ApiConfigResponse = {\n chainId: this.chainId,\n contracts,\n };\n if (this.pafiWebUrl) response.pafiWebUrl = this.pafiWebUrl;\n return response;\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 Error(\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 Error(\n \"handlePools: poolsProvider is not configured on this issuer\",\n );\n }\n if (request.chainId !== this.chainId) {\n throw new Error(\n `handlePools: unsupported chainId ${request.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 Error(\n `handleUser: unsupported chainId ${request.chainId}`,\n );\n }\n const normalizedAuthed = getAddress(userAddress);\n const normalizedRequest = getAddress(request.userAddress);\n if (normalizedAuthed !== normalizedRequest) {\n throw new Error(\n \"handleUser: request userAddress must match authenticated user\",\n );\n }\n const pointToken = getAddress(request.pointTokenAddress);\n if (!this.supportedTokens.has(pointToken)) {\n throw new Error(\n `handleUser: unsupported pointToken ${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 /**\n * `POST /claim`\n *\n * Policy gate + ledger lock + MintRequest signing in a single atomic\n * step. Returns an unsigned UserOp the frontend attaches paymaster data\n * to and submits via EIP-7702 + Bundler.\n *\n * Order of operations:\n * 1. Validate request fields.\n * 2. policy.evaluate() — throws if denied; cannot be bypassed.\n * 3. ledger.lockForMinting() — reserves the balance.\n * 4. Read on-chain mintRequestNonce + token name in parallel.\n * 5. relayService.prepareMint() — sign MintRequest + encode UserOp.\n * 6. On any error after step 3, release the lock before re-throwing.\n */\n async handleClaim(\n userAddress: Address,\n request: ApiClaimRequest,\n ): Promise<ApiClaimResponse> {\n if (!this.claim) {\n throw new Error(\"handleClaim: claim is not configured on this issuer\");\n }\n if (request.chainId !== this.chainId) {\n throw new Error(`handleClaim: unsupported chainId ${request.chainId}`);\n }\n const pointToken = getAddress(request.pointTokenAddress);\n if (!this.supportedTokens.has(pointToken)) {\n throw new Error(`handleClaim: unsupported pointToken ${pointToken}`);\n }\n if (request.amount <= 0n) {\n throw new Error(\"handleClaim: amount must be positive\");\n }\n const nowSecs = BigInt(Math.floor(Date.now() / 1000));\n if (request.deadline <= nowSecs) {\n throw new Error(\"handleClaim: deadline is in the past\");\n }\n\n const { policy, relayService, issuerSignerWallet, batchExecutorAddress } = this.claim;\n const lockDurationMs = this.claim.lockDurationMs ?? 15 * 60 * 1000;\n const normalizedUser = getAddress(userAddress);\n\n const decision = await policy.evaluate({\n userAddress: normalizedUser,\n amount: request.amount,\n pointTokenAddress: pointToken,\n chainId: this.chainId,\n });\n if (!decision.approved) {\n throw new Error(`handleClaim: policy denied — ${decision.reason ?? \"no reason given\"}`);\n }\n\n const lockId = await this.ledger.lockForMinting(\n normalizedUser,\n request.amount,\n lockDurationMs,\n pointToken,\n );\n\n try {\n const [mintRequestNonce, tokenName] = await Promise.all([\n getMintRequestNonce(this.provider, pointToken, normalizedUser),\n getTokenName(this.provider, pointToken),\n ]);\n\n const domain: PointTokenDomainConfig = {\n name: tokenName,\n verifyingContract: pointToken,\n chainId: this.chainId,\n };\n\n const userOp = await relayService.prepareMint({\n userAddress: normalizedUser,\n aaNonce: request.aaNonce,\n batchExecutorAddress,\n pointTokenAddress: pointToken,\n amount: request.amount,\n issuerSignerWallet,\n domain,\n mintRequestNonce,\n deadline: request.deadline,\n feeAmount: request.feeAmount,\n feeRecipient: request.feeRecipient,\n });\n\n return {\n lockId,\n userOp,\n expiresInSeconds: Math.floor(lockDurationMs / 1000),\n };\n } catch (err) {\n await this.ledger.releaseLock(lockId).catch(() => {});\n throw err;\n }\n }\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 { signBurnRequest, POINT_TOKEN_V2_ABI, getPointTokenBalance } from \"@pafi-dev/core\";\nimport type { IPointLedger } from \"../../ledger/types\";\nimport type { RelayService } from \"../../relay/relayService\";\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 + Coinbase Paymaster. 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 * 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\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 PT fee transfer appended after the burn (sponsored path).\n * User must hold `amount + feeAmount` PT. Both fields together or neither.\n */\n feeAmount?: bigint;\n feeRecipient?: Address;\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 /**\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 class PTRedeemError extends Error {\n constructor(\n public code:\n | \"UNAUTHORIZED\"\n | \"INVALID_AMOUNT\"\n | \"NONCE_READ_FAILED\"\n | \"NONCE_IN_FLIGHT\"\n | \"LEDGER_NOT_SUPPORTED\"\n | \"SIGNING_FAILED\",\n message: string,\n ) {\n super(message);\n this.name = \"PTRedeemError\";\n }\n}\n\nexport class PTRedeemHandler {\n private readonly ledger: IPointLedger;\n private readonly relayService: RelayService;\n private readonly provider: PublicClient;\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\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.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 }\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 // 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 const fee = request.feeAmount && request.feeAmount > 0n ? request.feeAmount : 0n;\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 const sponsoredUserOp = 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: request.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 const fallbackUserOp = 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 // No feeAmount/feeRecipient — fallback is fee-free.\n });\n\n fallback = {\n lockId: fallbackLockId,\n userOp: fallbackUserOp,\n netCreditAmount: request.amount,\n };\n }\n\n return {\n lockId: sponsoredLockId,\n userOp: sponsoredUserOp,\n netCreditAmount: sponsoredBurnAmount,\n fallback,\n expiresInSeconds: Math.floor(this.redeemLockDurationMs / 1000),\n signatureDeadline: deadline,\n };\n }\n}\n","import type { Address, PublicClient } from \"viem\";\nimport { getAddress } from \"viem\";\nimport { getPointTokenBalance } from \"@pafi-dev/core\";\nimport type { IPointLedger } from \"../../ledger/types\";\nimport type { PTRedeemHandler, PTRedeemResponse } from \"./ptRedeemHandler\";\n\n/**\n * v1.4 reverse flow — **Variant B**: auto top-up on voucher redemption.\n *\n * User tries to redeem a voucher for `requiredAmount` off-chain points\n * but their off-chain balance is short. If their on-chain PT balance is\n * enough to cover the shortfall, this handler auto-triggers a burn for\n * exactly the shortfall amount so the voucher can proceed.\n *\n * Required off-chain: 500\n * Available off-chain: 300\n * Shortfall: 200\n * On-chain PT: 250 ← enough, top-up fires\n * → burn 200 PT, credit 200 off-chain, voucher proceeds with 500\n *\n * Delegates the actual burn construction to {@link PTRedeemHandler}\n * — this handler is pure business logic (shortfall math + on-chain\n * balance check) on top.\n *\n * v1.4 note: user no longer pre-signs a `BurnConsent`. The issuer\n * backend signs a `BurnRequest` itself (see `PTRedeemHandler`), so\n * this handler only needs `userAddress + requiredAmount + aaNonce`.\n */\nexport interface TopUpRedemptionHandlerConfig {\n ledger: IPointLedger;\n ptRedeemHandler: PTRedeemHandler;\n provider: PublicClient;\n\n /** PointToken contract address (chain-specific). */\n pointTokenAddress: Address;\n}\n\nexport interface TopUpRedemptionRequest {\n /** Address extracted from the verified JWT — must match `userAddress`. */\n authenticatedAddress: Address;\n userAddress: Address;\n /** Total points the voucher redemption requires off-chain. */\n requiredAmount: bigint;\n /** ERC-4337 account nonce for the user's EOA. */\n aaNonce: bigint;\n}\n\nexport type TopUpRedemptionResponse =\n | {\n action: \"NO_TOP_UP_NEEDED\";\n offChainBalance: bigint;\n }\n | {\n action: \"INSUFFICIENT_ONCHAIN\";\n offChainBalance: bigint;\n onChainBalance: bigint;\n shortfall: bigint;\n }\n | {\n action: \"TOP_UP_STARTED\";\n shortfall: bigint;\n redeem: PTRedeemResponse;\n };\n\nexport class TopUpRedemptionError extends Error {\n constructor(\n public code: \"UNAUTHORIZED\" | \"INSUFFICIENT_ONCHAIN_BALANCE\" | \"LEDGER_NOT_SUPPORTED\",\n message: string,\n ) {\n super(message);\n this.name = \"TopUpRedemptionError\";\n }\n}\n\nexport class TopUpRedemptionHandler {\n private readonly ledger: IPointLedger;\n private readonly ptRedeemHandler: PTRedeemHandler;\n private readonly provider: PublicClient;\n private readonly pointTokenAddress: Address;\n\n constructor(config: TopUpRedemptionHandlerConfig) {\n this.ledger = config.ledger;\n this.ptRedeemHandler = config.ptRedeemHandler;\n this.provider = config.provider;\n this.pointTokenAddress = getAddress(config.pointTokenAddress);\n }\n\n async handle(\n request: TopUpRedemptionRequest,\n ): Promise<TopUpRedemptionResponse> {\n if (getAddress(request.authenticatedAddress) !== getAddress(request.userAddress)) {\n throw new TopUpRedemptionError(\n \"UNAUTHORIZED\",\n `userAddress (${request.userAddress}) does not match authenticated session (${request.authenticatedAddress})`,\n );\n }\n const offChainBalance = await this.ledger.getBalance(\n request.userAddress,\n this.pointTokenAddress,\n );\n\n // Happy path: plenty of off-chain balance — voucher proceeds with\n // no on-chain burn needed.\n if (offChainBalance >= request.requiredAmount) {\n return { action: \"NO_TOP_UP_NEEDED\", offChainBalance };\n }\n\n const shortfall = request.requiredAmount - offChainBalance;\n\n const onChainBalance = await getPointTokenBalance(\n this.provider,\n this.pointTokenAddress,\n request.userAddress,\n );\n\n if (onChainBalance < shortfall) {\n // Even after burning all on-chain PT the user is still short —\n // can't fulfill the voucher from what we have. Bail with a\n // descriptive response so the FE can show a precise message.\n return {\n action: \"INSUFFICIENT_ONCHAIN\",\n offChainBalance,\n onChainBalance,\n shortfall,\n };\n }\n\n const redeem = await this.ptRedeemHandler.handle({\n authenticatedAddress: request.authenticatedAddress,\n userAddress: request.userAddress,\n amount: shortfall,\n aaNonce: request.aaNonce,\n });\n\n return {\n action: \"TOP_UP_STARTED\",\n shortfall,\n redeem,\n };\n }\n}\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","export interface RetryConfig {\n maxAttempts?: number;\n initialDelayMs?: number;\n maxDelayMs?: number;\n maxRetryAfterMs?: number;\n}\n\nexport interface PafiBackendConfig {\n url: string;\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 { 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\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\n constructor(config: PafiBackendConfig) {\n if (!config.url) throw new Error(\"PafiBackendClient: url is required\");\n if (!config.issuerId) throw new Error(\"PafiBackendClient: issuerId is required\");\n this.config = config;\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.config.url}/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.config.url}/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.config.url}/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\";\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 /**\n * Enables `handleClaim`. The factory combines these with the shared\n * `policy` + `relayService` instances already wired by the factory.\n * Omit to disable the `/claim` endpoint.\n */\n claim?: {\n issuerSignerWallet: WalletClient;\n /** Defaults to the PAFI-deployed BatchExecutor for the target chain. */\n batchExecutorAddress?: Address;\n lockDurationMs?: number;\n };\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}\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\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.\n const relayService = new RelayService();\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 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 (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\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 (config.claim) {\n handlersConfig.claim = {\n policy,\n relayService,\n issuerSignerWallet: config.claim.issuerSignerWallet,\n batchExecutorAddress: config.claim.batchExecutorAddress ?? chainAddresses.batchExecutor,\n lockDurationMs: config.claim.lockDurationMs,\n };\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 };\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): Record<string, string | null> {\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 { getAddress } from \"viem\";\nimport type { Address, PublicClient } from \"viem\";\nimport {\n POINT_TOKEN_V2_ABI,\n issuerRegistryGetIssuerFlatAbi,\n getContractAddresses,\n} from \"@pafi-dev/core\";\nimport {\n IssuerStateError,\n type IssuerRegistryRecord,\n type PreValidateMintResult,\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 const [issuerTuple, totalSupply] = await Promise.all([\n this.provider.readContract({\n address: this.registryAddress,\n abi: issuerRegistryGetIssuerFlatAbi,\n functionName: \"getIssuer\",\n args: [issuerAddr],\n }) as Promise<\n readonly [\n Address,\n Address,\n string,\n string,\n bigint,\n number,\n boolean,\n Address,\n Address,\n ]\n >,\n this.provider.readContract({\n address: tokenAddr,\n abi: POINT_TOKEN_V2_ABI,\n functionName: \"totalSupply\",\n }) as Promise<bigint>,\n ]);\n\n // Workaround: viem ≤2.48 mis-decodes `returns (Struct)` with mixed\n // static + dynamic fields. The flat-output ABI returns an array; we\n // rebuild the named record here.\n const issuer: IssuerRegistryRecord = {\n issuerAddress: issuerTuple[0],\n signerAddress: issuerTuple[1],\n name: issuerTuple[2],\n symbol: issuerTuple[3],\n declaredTotalSupply: issuerTuple[4],\n capBasisPoints: issuerTuple[5],\n active: issuerTuple[6],\n pointToken: issuerTuple[7],\n mintingOracle: issuerTuple[8],\n };\n\n const hardCap = (issuer.declaredTotalSupply * BigInt(issuer.capBasisPoints)) / 10000n;\n const remaining = hardCap > totalSupply ? hardCap - totalSupply : 0n;\n\n return { issuer, totalSupply, hardCap, remaining };\n }\n}\n","import type { Address } from \"viem\";\n\nexport interface IssuerRegistryRecord {\n issuerAddress: Address;\n signerAddress: Address;\n name: string;\n symbol: string;\n declaredTotalSupply: bigint;\n capBasisPoints: number;\n active: boolean;\n pointToken: Address;\n mintingOracle: Address;\n}\n\nexport interface PreValidateMintResult {\n /** Registry record read at pre-validation time. */\n issuer: IssuerRegistryRecord;\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 */\nexport class IssuerStateError extends Error {\n constructor(\n public readonly code:\n | \"ISSUER_NOT_REGISTERED\"\n | \"ISSUER_INACTIVE\"\n | \"MINT_CAP_EXCEEDED\",\n message: string,\n public readonly details?: Record<string, unknown>,\n ) {\n super(message);\n this.name = \"IssuerStateError\";\n }\n}\n"],"mappings":"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","names":["import_node_crypto","import_viem","joseErrors","import_viem","import_core","import_viem","import_viem","TRANSFER_EVENT","ZERO_ADDRESS","DEFAULT_CONFIRMATIONS","DEFAULT_BATCH_SIZE","DEFAULT_POLL_INTERVAL_MS","import_viem","import_core","import_viem","import_core","import_viem","import_core","import_viem","import_core","DEFAULT_CACHE_TTL_MS","import_viem","import_core","import_viem","import_core","import_core","import_viem","import_core"]}
1
+ {"version":3,"sources":["../src/index.ts","../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/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/handlers/topUpRedemptionHandler.ts","../src/pools/subgraphPoolsProvider.ts","../src/pools/subgraphNativeUsdtQuoter.ts","../src/pools/nativePtQuoter.ts","../src/balance/balanceAggregator.ts","../src/pafi-backend/types.ts","../src/pafi-backend/client.ts","../src/config.ts","../src/userop-store/serialize.ts","../src/issuer-state/validator.ts","../src/issuer-state/types.ts"],"sourcesContent":["// @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\n/** SDK package version — bumped on every release */\nexport const PAFI_ISSUER_SDK_VERSION = \"0.4.0\";\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","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\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 (process.env.NODE_ENV === 'production') {\n console.error(\n '[PAFI] MemorySessionStore is not safe for multi-process/K8s deployments. ' +\n 'Session revocations are NOT propagated across pods. ' +\n 'Use a Redis-backed session store in production.'\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\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 /** 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 nonceManager: NonceManager;\n private readonly now: () => Date;\n\n constructor(config: AuthServiceConfig) {\n if (!config.jwtSecret || config.jwtSecret.length < 32) {\n throw new Error(\"AuthService: jwtSecret must be at least 32 characters for HS256 security\");\n }\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.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 const token = await 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 .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 // Best-effort: if the token is malformed or already expired we still\n // try to decode enough to find a jti and revoke it. If we can't find a\n // jti at all, silently succeed — logout should be idempotent.\n try {\n const { payload } = await jwtVerify(token, this.jwtSecret, {\n clockTolerance: 60, // allow logout right after expiry\n });\n if (payload.jti) {\n await this.sessionStore.revokeSession(payload.jti);\n }\n } catch (err) {\n // Swallow \"not found\" silently — token may already be expired/revoked\n // Re-throw or log genuine storage errors\n const msg = err instanceof Error ? err.message : String(err);\n if (!msg.includes('not found') && !msg.includes('expired')) {\n console.error('[PAFI] AuthService logout: session store error', err);\n }\n }\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 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","/**\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\nexport class AuthError extends Error {\n readonly code: AuthErrorCode;\n\n constructor(code: AuthErrorCode, message: string) {\n super(message);\n this.name = \"AuthError\";\n this.code = 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 * Errors raised by RelayService carry a `code` so callers (handlers /\n * gateway-less HTTP wrappers) can decide how to map them to HTTP status.\n *\n * v1.4 trimmed the error space to just encoding failures — the service\n * no longer broadcasts transactions, so `SUBMIT_FAILED`, `TX_REVERTED`,\n * `SIMULATION_FAILED`, and `TIMEOUT` all went away with the operator\n * wallet. Paymaster/Bundler errors surface out-of-band on the FE.\n */\nexport type RelayErrorCode =\n | \"NOT_CONFIGURED\" // required field missing\n | \"ENCODE_FAILED\"; // calldata encoding / signing threw\n\nexport class RelayError extends Error {\n readonly code: RelayErrorCode;\n readonly cause?: unknown;\n\n constructor(code: RelayErrorCode, message: string, cause?: unknown) {\n super(message);\n this.name = \"RelayError\";\n this.code = code;\n if (cause !== undefined) this.cause = cause;\n }\n}\n","import {\n encodeFunctionData,\n erc20Abi,\n type Address,\n type Hex,\n type WalletClient,\n} from \"viem\";\nimport {\n POINT_TOKEN_V2_ABI,\n buildPartialUserOperation,\n signMintRequest,\n type Operation,\n type PartialUserOperation,\n type PointTokenDomainConfig,\n type BurnRequest,\n} from \"@pafi-dev/core\";\nimport { RelayError } from \"./types\";\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 /**\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 MintRequest with the issuer minter signer.\n let minterSig: Hex;\n try {\n const sig = await signMintRequest(\n params.issuerSignerWallet,\n params.domain,\n {\n to: params.userAddress,\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 MintRequest: ${errorMessage(err)}`,\n err,\n );\n }\n\n // 2. Encode sig-gated mint: mint(address,uint256,uint256,bytes).\n let mintCallData: Hex;\n try {\n mintCallData = encodeFunctionData({\n abi: POINT_TOKEN_V2_ABI,\n functionName: \"mint\",\n args: [params.userAddress, params.amount, params.deadline, minterSig],\n });\n } catch (err) {\n throw new RelayError(\n \"ENCODE_FAILED\",\n `prepareMint: failed to encode PointToken.mint: ${errorMessage(err)}`,\n err,\n );\n }\n\n const operations: Operation[] = [\n {\n target: params.pointTokenAddress,\n value: 0n,\n data: mintCallData,\n },\n ];\n\n // 3. Optional fee transfer (application-level, no Relayer).\n if (params.feeAmount && params.feeAmount > 0n) {\n if (!params.feeRecipient) {\n throw new RelayError(\n \"ENCODE_FAILED\",\n \"prepareMint: feeRecipient required when feeAmount > 0\",\n );\n }\n if (params.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: [params.feeRecipient, params.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).\n *\n * Two modes:\n * - `mode: 'burn'` — direct `PointToken.burn(from, amount)`; only\n * usable if the caller (via EIP-7702) is whitelisted as a burner.\n * Rare in v1.4; kept for admin/operator tools.\n * - `mode: 'burnWithSig'` — `PointToken.burn(from, amount, deadline,\n * burnerSig)`. Caller provides a pre-signed `BurnRequest` + sig\n * bytes (typically from `PTRedeemHandler`).\n */\n prepareBurn(params: PrepareBurnParams): 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\n let burnCallData: Hex;\n try {\n if (params.mode === \"burnWithSig\") {\n if (!params.burnRequest || !params.burnerSignature) {\n throw new Error(\"burnWithSig requires burnRequest + burnerSignature\");\n }\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 } else {\n burnCallData = encodeFunctionData({\n abi: POINT_TOKEN_V2_ABI,\n functionName: \"burn\",\n args: [params.userAddress, params.amount],\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 if (params.feeAmount && params.feeAmount > 0n) {\n if (!params.feeRecipient) {\n throw new RelayError(\n \"ENCODE_FAILED\",\n \"prepareBurn: feeRecipient required when feeAmount > 0\",\n );\n }\n if (params.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: [params.feeRecipient, params.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 — 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\nexport type PrepareBurnParams =\n | PrepareBurnDirectParams\n | PrepareBurnWithSigParams;\n\ninterface PrepareBurnCommonParams {\n userAddress: Address;\n aaNonce: bigint;\n pointTokenAddress: Address;\n batchExecutorAddress: Address;\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\nexport interface PrepareBurnDirectParams extends PrepareBurnCommonParams {\n mode: \"burn\";\n amount: bigint;\n}\n\nexport interface PrepareBurnWithSigParams extends PrepareBurnCommonParams {\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\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 Coinbase Paymaster 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, Hex, 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 * 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\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 * Watches `PointToken.Transfer(from=0x0 → to)` events and finalizes the\n * issuer ledger on each confirmed mint.\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 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\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 = config.pointTokenAddress;\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 }\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()` schedules the next one at the end.\n void this.tick();\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 console.error('[PAFI] PointIndexer tick error:', err);\n }\n this.scheduleNext();\n }\n\n private scheduleNext(): void {\n if (!this.running) return;\n this.timer = setTimeout(() => void this.tick(), this.pollIntervalMs);\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 logs = await this.provider.getLogs({\n address: this.pointTokenAddress,\n event: TRANSFER_EVENT,\n args: { from: ZERO_ADDRESS },\n fromBlock: cursor,\n toBlock: chunkEnd,\n });\n\n const events = this.decodeMintEvents(logs);\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 // Internals\n // -------------------------------------------------------------------------\n\n private decodeMintEvents(\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 * 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\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\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\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 void this.tick();\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 console.error('[PAFI] BurnIndexer tick error:', err);\n }\n this.scheduleNext();\n }\n\n private scheduleNext(): void {\n if (!this.running) return;\n this.timer = setTimeout(() => void this.tick(), this.pollIntervalMs);\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, WalletClient } from \"viem\";\nimport {\n getMintRequestNonce,\n getPointTokenBalance,\n getReceiverConsentNonce,\n getTokenName,\n isMinter,\n} from \"@pafi-dev/core\";\nimport type { PointTokenDomainConfig } from \"@pafi-dev/core\";\nimport type { AuthService } from \"../auth/loginVerifier\";\nimport type { IPointLedger } from \"../ledger/types\";\nimport type { FeeManager } from \"../relay/feeManager\";\nimport type { RelayService } from \"../relay/relayService\";\nimport type { IPolicyEngine } from \"../policy/types\";\nimport type {\n ApiClaimRequest,\n ApiClaimResponse,\n ApiConfigResponse,\n ApiGasFeeResponse,\n ApiLoginRequest,\n ApiLoginResponse,\n ApiNonceResponse,\n ApiPoolsRequest,\n ApiPoolsResponse,\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 // -- 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 `handleClaim`; omit to disable the endpoint.\n * Wires policy evaluation + ledger locking + MintRequest signing\n * into a single atomic handler so callers cannot accidentally skip\n * the policy check.\n */\n claim?: {\n policy: IPolicyEngine;\n relayService: RelayService;\n issuerSignerWallet: WalletClient;\n batchExecutorAddress: Address;\n /** How long to hold the off-chain lock while the UserOp is in flight. Default: 15 min. */\n lockDurationMs?: number;\n };\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 claim?: IssuerApiHandlersConfig[\"claim\"];\n\n constructor(config: IssuerApiHandlersConfig) {\n this.authService = config.authService;\n this.ledger = config.ledger;\n this.provider = config.provider;\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 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.claim) this.claim = config.claim;\n }\n\n // =========================================================================\n // Public handlers (no auth required)\n // =========================================================================\n\n /** `GET /auth/nonce` */\n async handleGetNonce(): Promise<ApiNonceResponse> {\n const nonce = await this.authService.getNonce();\n return { nonce };\n }\n\n /** `POST /auth/login` */\n async handleLogin(\n body: ApiLoginRequest,\n ): Promise<ApiLoginResponse> {\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 Error(\"handleLogin: message and signature are required\");\n }\n if (body.message.length > 4096) {\n throw new Error(\"message too long\");\n }\n if (body.signature.length > 260) {\n throw new Error(\"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 Error(\"invalid chainId\");\n }\n if (chainId !== this.chainId) {\n throw new Error(\n `handleConfig: unsupported chainId ${chainId}`,\n );\n }\n const contracts: ApiConfigResponse[\"contracts\"] = {\n ...this.contracts,\n pointTokens: Array.from(this.supportedTokens),\n };\n const response: ApiConfigResponse = {\n chainId: this.chainId,\n contracts,\n };\n if (this.pafiWebUrl) response.pafiWebUrl = this.pafiWebUrl;\n return response;\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 Error(\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 Error(\n \"handlePools: poolsProvider is not configured on this issuer\",\n );\n }\n if (request.chainId !== this.chainId) {\n throw new Error(\n `handlePools: unsupported chainId ${request.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 Error(\n `handleUser: unsupported chainId ${request.chainId}`,\n );\n }\n const normalizedAuthed = getAddress(userAddress);\n const normalizedRequest = getAddress(request.userAddress);\n if (normalizedAuthed !== normalizedRequest) {\n throw new Error(\n \"handleUser: request userAddress must match authenticated user\",\n );\n }\n const pointToken = getAddress(request.pointTokenAddress);\n if (!this.supportedTokens.has(pointToken)) {\n throw new Error(\n `handleUser: unsupported pointToken ${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 /**\n * `POST /claim`\n *\n * Policy gate + ledger lock + MintRequest signing in a single atomic\n * step. Returns an unsigned UserOp the frontend attaches paymaster data\n * to and submits via EIP-7702 + Bundler.\n *\n * Order of operations:\n * 1. Validate request fields.\n * 2. policy.evaluate() — throws if denied; cannot be bypassed.\n * 3. ledger.lockForMinting() — reserves the balance.\n * 4. Read on-chain mintRequestNonce + token name in parallel.\n * 5. relayService.prepareMint() — sign MintRequest + encode UserOp.\n * 6. On any error after step 3, release the lock before re-throwing.\n */\n async handleClaim(\n userAddress: Address,\n request: ApiClaimRequest,\n ): Promise<ApiClaimResponse> {\n if (!this.claim) {\n throw new Error(\"handleClaim: claim is not configured on this issuer\");\n }\n if (request.chainId !== this.chainId) {\n throw new Error(`handleClaim: unsupported chainId ${request.chainId}`);\n }\n const pointToken = getAddress(request.pointTokenAddress);\n if (!this.supportedTokens.has(pointToken)) {\n throw new Error(`handleClaim: unsupported pointToken ${pointToken}`);\n }\n if (request.amount <= 0n) {\n throw new Error(\"handleClaim: amount must be positive\");\n }\n const nowSecs = BigInt(Math.floor(Date.now() / 1000));\n if (request.deadline <= nowSecs) {\n throw new Error(\"handleClaim: deadline is in the past\");\n }\n\n const { policy, relayService, issuerSignerWallet, batchExecutorAddress } = this.claim;\n const lockDurationMs = this.claim.lockDurationMs ?? 15 * 60 * 1000;\n const normalizedUser = getAddress(userAddress);\n\n const decision = await policy.evaluate({\n userAddress: normalizedUser,\n amount: request.amount,\n pointTokenAddress: pointToken,\n chainId: this.chainId,\n });\n if (!decision.approved) {\n throw new Error(`handleClaim: policy denied — ${decision.reason ?? \"no reason given\"}`);\n }\n\n const lockId = await this.ledger.lockForMinting(\n normalizedUser,\n request.amount,\n lockDurationMs,\n pointToken,\n );\n\n try {\n const [mintRequestNonce, tokenName] = await Promise.all([\n getMintRequestNonce(this.provider, pointToken, normalizedUser),\n getTokenName(this.provider, pointToken),\n ]);\n\n const domain: PointTokenDomainConfig = {\n name: tokenName,\n verifyingContract: pointToken,\n chainId: this.chainId,\n };\n\n const userOp = await relayService.prepareMint({\n userAddress: normalizedUser,\n aaNonce: request.aaNonce,\n batchExecutorAddress,\n pointTokenAddress: pointToken,\n amount: request.amount,\n issuerSignerWallet,\n domain,\n mintRequestNonce,\n deadline: request.deadline,\n feeAmount: request.feeAmount,\n feeRecipient: request.feeRecipient,\n });\n\n return {\n lockId,\n userOp,\n expiresInSeconds: Math.floor(lockDurationMs / 1000),\n };\n } catch (err) {\n await this.ledger.releaseLock(lockId).catch(() => {});\n throw err;\n }\n }\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 { signBurnRequest, POINT_TOKEN_V2_ABI, getPointTokenBalance } from \"@pafi-dev/core\";\nimport type { IPointLedger } from \"../../ledger/types\";\nimport type { RelayService } from \"../../relay/relayService\";\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 + Coinbase Paymaster. 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 * 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\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 PT fee transfer appended after the burn (sponsored path).\n * User must hold `amount + feeAmount` PT. Both fields together or neither.\n */\n feeAmount?: bigint;\n feeRecipient?: Address;\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 /**\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 class PTRedeemError extends Error {\n constructor(\n public code:\n | \"UNAUTHORIZED\"\n | \"INVALID_AMOUNT\"\n | \"NONCE_READ_FAILED\"\n | \"NONCE_IN_FLIGHT\"\n | \"LEDGER_NOT_SUPPORTED\"\n | \"SIGNING_FAILED\",\n message: string,\n ) {\n super(message);\n this.name = \"PTRedeemError\";\n }\n}\n\nexport class PTRedeemHandler {\n private readonly ledger: IPointLedger;\n private readonly relayService: RelayService;\n private readonly provider: PublicClient;\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\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.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 }\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 // 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 const fee = request.feeAmount && request.feeAmount > 0n ? request.feeAmount : 0n;\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 const sponsoredUserOp = 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: request.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 const fallbackUserOp = 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 // No feeAmount/feeRecipient — fallback is fee-free.\n });\n\n fallback = {\n lockId: fallbackLockId,\n userOp: fallbackUserOp,\n netCreditAmount: request.amount,\n };\n }\n\n return {\n lockId: sponsoredLockId,\n userOp: sponsoredUserOp,\n netCreditAmount: sponsoredBurnAmount,\n fallback,\n expiresInSeconds: Math.floor(this.redeemLockDurationMs / 1000),\n signatureDeadline: deadline,\n };\n }\n}\n","import type { Address, PublicClient } from \"viem\";\nimport { getAddress } from \"viem\";\nimport { getPointTokenBalance } from \"@pafi-dev/core\";\nimport type { IPointLedger } from \"../../ledger/types\";\nimport type { PTRedeemHandler, PTRedeemResponse } from \"./ptRedeemHandler\";\n\n/**\n * v1.4 reverse flow — **Variant B**: auto top-up on voucher redemption.\n *\n * User tries to redeem a voucher for `requiredAmount` off-chain points\n * but their off-chain balance is short. If their on-chain PT balance is\n * enough to cover the shortfall, this handler auto-triggers a burn for\n * exactly the shortfall amount so the voucher can proceed.\n *\n * Required off-chain: 500\n * Available off-chain: 300\n * Shortfall: 200\n * On-chain PT: 250 ← enough, top-up fires\n * → burn 200 PT, credit 200 off-chain, voucher proceeds with 500\n *\n * Delegates the actual burn construction to {@link PTRedeemHandler}\n * — this handler is pure business logic (shortfall math + on-chain\n * balance check) on top.\n *\n * v1.4 note: user no longer pre-signs a `BurnConsent`. The issuer\n * backend signs a `BurnRequest` itself (see `PTRedeemHandler`), so\n * this handler only needs `userAddress + requiredAmount + aaNonce`.\n */\nexport interface TopUpRedemptionHandlerConfig {\n ledger: IPointLedger;\n ptRedeemHandler: PTRedeemHandler;\n provider: PublicClient;\n\n /** PointToken contract address (chain-specific). */\n pointTokenAddress: Address;\n}\n\nexport interface TopUpRedemptionRequest {\n /** Address extracted from the verified JWT — must match `userAddress`. */\n authenticatedAddress: Address;\n userAddress: Address;\n /** Total points the voucher redemption requires off-chain. */\n requiredAmount: bigint;\n /** ERC-4337 account nonce for the user's EOA. */\n aaNonce: bigint;\n}\n\nexport type TopUpRedemptionResponse =\n | {\n action: \"NO_TOP_UP_NEEDED\";\n offChainBalance: bigint;\n }\n | {\n action: \"INSUFFICIENT_ONCHAIN\";\n offChainBalance: bigint;\n onChainBalance: bigint;\n shortfall: bigint;\n }\n | {\n action: \"TOP_UP_STARTED\";\n shortfall: bigint;\n redeem: PTRedeemResponse;\n };\n\nexport class TopUpRedemptionError extends Error {\n constructor(\n public code: \"UNAUTHORIZED\" | \"INSUFFICIENT_ONCHAIN_BALANCE\" | \"LEDGER_NOT_SUPPORTED\",\n message: string,\n ) {\n super(message);\n this.name = \"TopUpRedemptionError\";\n }\n}\n\nexport class TopUpRedemptionHandler {\n private readonly ledger: IPointLedger;\n private readonly ptRedeemHandler: PTRedeemHandler;\n private readonly provider: PublicClient;\n private readonly pointTokenAddress: Address;\n\n constructor(config: TopUpRedemptionHandlerConfig) {\n this.ledger = config.ledger;\n this.ptRedeemHandler = config.ptRedeemHandler;\n this.provider = config.provider;\n this.pointTokenAddress = getAddress(config.pointTokenAddress);\n }\n\n async handle(\n request: TopUpRedemptionRequest,\n ): Promise<TopUpRedemptionResponse> {\n if (getAddress(request.authenticatedAddress) !== getAddress(request.userAddress)) {\n throw new TopUpRedemptionError(\n \"UNAUTHORIZED\",\n `userAddress (${request.userAddress}) does not match authenticated session (${request.authenticatedAddress})`,\n );\n }\n const offChainBalance = await this.ledger.getBalance(\n request.userAddress,\n this.pointTokenAddress,\n );\n\n // Happy path: plenty of off-chain balance — voucher proceeds with\n // no on-chain burn needed.\n if (offChainBalance >= request.requiredAmount) {\n return { action: \"NO_TOP_UP_NEEDED\", offChainBalance };\n }\n\n const shortfall = request.requiredAmount - offChainBalance;\n\n const onChainBalance = await getPointTokenBalance(\n this.provider,\n this.pointTokenAddress,\n request.userAddress,\n );\n\n if (onChainBalance < shortfall) {\n // Even after burning all on-chain PT the user is still short —\n // can't fulfill the voucher from what we have. Bail with a\n // descriptive response so the FE can show a precise message.\n return {\n action: \"INSUFFICIENT_ONCHAIN\",\n offChainBalance,\n onChainBalance,\n shortfall,\n };\n }\n\n const redeem = await this.ptRedeemHandler.handle({\n authenticatedAddress: request.authenticatedAddress,\n userAddress: request.userAddress,\n amount: shortfall,\n aaNonce: request.aaNonce,\n });\n\n return {\n action: \"TOP_UP_STARTED\",\n shortfall,\n redeem,\n };\n }\n}\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","export interface RetryConfig {\n maxAttempts?: number;\n initialDelayMs?: number;\n maxDelayMs?: number;\n maxRetryAfterMs?: number;\n}\n\nexport interface PafiBackendConfig {\n url: string;\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 { 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\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\n constructor(config: PafiBackendConfig) {\n if (!config.url) throw new Error(\"PafiBackendClient: url is required\");\n if (!config.issuerId) throw new Error(\"PafiBackendClient: issuerId is required\");\n this.config = config;\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.config.url}/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.config.url}/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.config.url}/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\";\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 /**\n * Enables `handleClaim`. The factory combines these with the shared\n * `policy` + `relayService` instances already wired by the factory.\n * Omit to disable the `/claim` endpoint.\n */\n claim?: {\n issuerSignerWallet: WalletClient;\n /** Defaults to the PAFI-deployed BatchExecutor for the target chain. */\n batchExecutorAddress?: Address;\n lockDurationMs?: number;\n };\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}\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\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.\n const relayService = new RelayService();\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 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 (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\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 (config.claim) {\n handlersConfig.claim = {\n policy,\n relayService,\n issuerSignerWallet: config.claim.issuerSignerWallet,\n batchExecutorAddress: config.claim.batchExecutorAddress ?? chainAddresses.batchExecutor,\n lockDurationMs: config.claim.lockDurationMs,\n };\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 };\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 { getAddress } from \"viem\";\nimport type { Address, PublicClient } from \"viem\";\nimport {\n POINT_TOKEN_V2_ABI,\n issuerRegistryGetIssuerFlatAbi,\n getContractAddresses,\n} from \"@pafi-dev/core\";\nimport {\n IssuerStateError,\n type IssuerRegistryRecord,\n type PreValidateMintResult,\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 const [issuerTuple, totalSupply] = await Promise.all([\n this.provider.readContract({\n address: this.registryAddress,\n abi: issuerRegistryGetIssuerFlatAbi,\n functionName: \"getIssuer\",\n args: [issuerAddr],\n }) as Promise<\n readonly [\n Address,\n Address,\n string,\n string,\n bigint,\n number,\n boolean,\n Address,\n Address,\n ]\n >,\n this.provider.readContract({\n address: tokenAddr,\n abi: POINT_TOKEN_V2_ABI,\n functionName: \"totalSupply\",\n }) as Promise<bigint>,\n ]);\n\n // Workaround: viem ≤2.48 mis-decodes `returns (Struct)` with mixed\n // static + dynamic fields. The flat-output ABI returns an array; we\n // rebuild the named record here.\n const issuer: IssuerRegistryRecord = {\n issuerAddress: issuerTuple[0],\n signerAddress: issuerTuple[1],\n name: issuerTuple[2],\n symbol: issuerTuple[3],\n declaredTotalSupply: issuerTuple[4],\n capBasisPoints: issuerTuple[5],\n active: issuerTuple[6],\n pointToken: issuerTuple[7],\n mintingOracle: issuerTuple[8],\n };\n\n const hardCap = (issuer.declaredTotalSupply * BigInt(issuer.capBasisPoints)) / 10000n;\n const remaining = hardCap > totalSupply ? hardCap - totalSupply : 0n;\n\n return { issuer, totalSupply, hardCap, remaining };\n }\n}\n","import type { Address } from \"viem\";\n\nexport interface IssuerRegistryRecord {\n issuerAddress: Address;\n signerAddress: Address;\n name: string;\n symbol: string;\n declaredTotalSupply: bigint;\n capBasisPoints: number;\n active: boolean;\n pointToken: Address;\n mintingOracle: Address;\n}\n\nexport interface PreValidateMintResult {\n /** Registry record read at pre-validation time. */\n issuer: IssuerRegistryRecord;\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 */\nexport class IssuerStateError extends Error {\n constructor(\n public readonly code:\n | \"ISSUER_NOT_REGISTERED\"\n | \"ISSUER_INACTIVE\"\n | \"MINT_CAP_EXCEEDED\",\n message: string,\n public readonly details?: Record<string, unknown>,\n ) {\n super(message);\n this.name = \"IssuerStateError\";\n }\n}\n"],"mappings":"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","names":["import_node_crypto","import_viem","joseErrors","import_viem","import_core","import_viem","import_viem","TRANSFER_EVENT","ZERO_ADDRESS","DEFAULT_CONFIRMATIONS","DEFAULT_BATCH_SIZE","DEFAULT_POLL_INTERVAL_MS","import_viem","import_core","import_viem","import_core","import_viem","import_core","import_viem","import_core","DEFAULT_CACHE_TTL_MS","import_viem","import_core","import_viem","import_core","import_core","import_viem","import_core"]}