@pafi-dev/issuer 0.3.0-beta.4 → 0.3.0-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +21 -479
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +52 -331
- package/dist/index.d.ts +52 -331
- package/dist/index.js +6 -468
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/ledger/memoryLedger.ts","../src/policy/defaultPolicy.ts","../src/signer/privateKeySigner.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/gateway/types.ts","../src/gateway/mintingGateway.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/balance/balanceAggregator.ts","../src/pafi-backend/types.ts","../src/pafi-backend/pafiBackendClient.ts","../src/config.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.1.0\";\n\n// -----------------------------------------------------------------------------\n// Phase 1 — core interfaces + default in-memory implementations\n// -----------------------------------------------------------------------------\nexport * from \"./ledger\";\nexport * from \"./policy\";\nexport * from \"./signer\";\n\n// -----------------------------------------------------------------------------\n// Phase 2 — Auth\n// (includes ISessionStore, MemorySessionStore, AuthService, AuthError,\n// NonceManager, authenticateRequest)\n// -----------------------------------------------------------------------------\nexport * from \"./auth\";\n\n// -----------------------------------------------------------------------------\n// Phase 3 — Relay + fees\n// -----------------------------------------------------------------------------\nexport * from \"./relay\";\n\n// -----------------------------------------------------------------------------\n// Phase 4 — Minting gateway\n// -----------------------------------------------------------------------------\nexport * from \"./gateway\";\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","import { getAddress } from \"viem\";\nimport type { Address, Hex } from \"viem\";\nimport type { IPointLedger, LockedMintRequest, MintingStatus } from \"./types\";\n\n/**\n * In-memory IPointLedger implementation for development and tests.\n *\n * NOT for production — state is lost on restart. Issuers should ship their\n * own database-backed implementation.\n *\n * Concurrency model: single-process, single-threaded (Node.js event loop).\n * The lock check + insert is atomic within a tick because no awaits sit\n * between balance read and lock write.\n *\n * **Multi-token (0.2.0):** Balances are keyed by `(user, token)`. If callers\n * omit `tokenAddress`, the literal string \"default\" is used — that keeps\n * single-token usage working exactly like 0.1.x.\n */\nexport class MemoryPointLedger implements IPointLedger {\n private balances = new Map<string, bigint>();\n private locks = new Map<string, LockedMintRequest>();\n private nextLockId = 1;\n private now: () => number;\n\n constructor(opts: { now?: () => number } = {}) {\n this.now = opts.now ?? (() => Date.now());\n }\n\n // -------------------------------------------------------------------------\n // Read\n // -------------------------------------------------------------------------\n\n async getBalance(\n userAddress: Address,\n tokenAddress?: Address,\n ): Promise<bigint> {\n const user = getAddress(userAddress);\n const token = normalizeToken(tokenAddress);\n this.purgeExpired();\n const total = this.balances.get(balanceKey(user, token)) ?? 0n;\n const locked = this.lockedTotalFor(user, token);\n return total - locked;\n }\n\n async getLockedRequests(\n userAddress: Address,\n tokenAddress?: Address,\n ): Promise<LockedMintRequest[]> {\n const user = getAddress(userAddress);\n const token = normalizeToken(tokenAddress);\n this.purgeExpired();\n const out: LockedMintRequest[] = [];\n for (const lock of this.locks.values()) {\n if (\n lock.userAddress === user &&\n lock.status === \"PENDING\" &&\n (lock.tokenAddress ?? DEFAULT_TOKEN_KEY) === token\n ) {\n out.push({ ...lock });\n }\n }\n return out;\n }\n\n // -------------------------------------------------------------------------\n // Write\n // -------------------------------------------------------------------------\n\n async creditBalance(\n userAddress: Address,\n amount: bigint,\n _reason: string,\n tokenAddress?: Address,\n ): Promise<void> {\n if (amount <= 0n) {\n throw new Error(\"MemoryPointLedger: credit amount must be positive\");\n }\n const user = getAddress(userAddress);\n const token = normalizeToken(tokenAddress);\n const key = balanceKey(user, token);\n const current = this.balances.get(key) ?? 0n;\n this.balances.set(key, current + amount);\n }\n\n async lockForMinting(\n userAddress: Address,\n amount: bigint,\n lockDurationMs: number,\n tokenAddress?: Address,\n ): Promise<string> {\n if (amount <= 0n) {\n throw new Error(\"MemoryPointLedger: lock amount must be positive\");\n }\n if (lockDurationMs <= 0) {\n throw new Error(\"MemoryPointLedger: lockDurationMs must be positive\");\n }\n\n const user = getAddress(userAddress);\n const token = normalizeToken(tokenAddress);\n this.purgeExpired();\n\n const total = this.balances.get(balanceKey(user, token)) ?? 0n;\n const alreadyLocked = this.lockedTotalFor(user, token);\n const available = total - alreadyLocked;\n if (available < amount) {\n throw new Error(\n `MemoryPointLedger: insufficient balance — available=${available}, requested=${amount}`,\n );\n }\n\n const lockId = `lock-${this.nextLockId++}`;\n const now = this.now();\n const lock: LockedMintRequest = {\n lockId,\n userAddress: user,\n amount,\n status: \"PENDING\",\n createdAt: now,\n expiresAt: now + lockDurationMs,\n };\n if (tokenAddress !== undefined) {\n lock.tokenAddress = getAddress(tokenAddress);\n }\n this.locks.set(lockId, lock);\n return lockId;\n }\n\n async releaseLock(lockId: string): Promise<void> {\n const lock = this.locks.get(lockId);\n if (!lock) return;\n // Releasing a still-pending lock removes it entirely (no audit trail in\n // this dev impl). Already-resolved locks are left as-is so the indexer\n // can still inspect them.\n if (lock.status === \"PENDING\") {\n this.locks.delete(lockId);\n }\n }\n\n async deductBalance(\n userAddress: Address,\n amount: bigint,\n txHash: Hex,\n tokenAddress?: Address,\n ): Promise<void> {\n if (amount <= 0n) {\n throw new Error(\"MemoryPointLedger: deduct amount must be positive\");\n }\n const user = getAddress(userAddress);\n const token = normalizeToken(tokenAddress);\n const key = balanceKey(user, token);\n const current = this.balances.get(key) ?? 0n;\n if (current < amount) {\n throw new Error(\n `MemoryPointLedger: cannot deduct ${amount} from balance ${current}`,\n );\n }\n this.balances.set(key, current - amount);\n\n // Resolve the matching pending lock so the locked total drops too.\n for (const lock of this.locks.values()) {\n if (\n lock.userAddress === user &&\n lock.status === \"PENDING\" &&\n lock.amount === amount &&\n (lock.tokenAddress ?? DEFAULT_TOKEN_KEY) === token\n ) {\n lock.status = \"MINTED\";\n lock.txHash = txHash;\n return;\n }\n }\n }\n\n async updateMintStatus(\n lockId: string,\n status: MintingStatus,\n txHash?: Hex,\n ): Promise<void> {\n const lock = this.locks.get(lockId);\n if (!lock) {\n throw new Error(`MemoryPointLedger: unknown lockId ${lockId}`);\n }\n lock.status = status;\n if (txHash) lock.txHash = txHash;\n }\n\n // -------------------------------------------------------------------------\n // v1.4 — Reverse flow (PT burn → off-chain credit)\n // -------------------------------------------------------------------------\n\n private pendingCredits = new Map<\n string,\n {\n lockId: string;\n userAddress: Address;\n amount: bigint;\n tokenAddress: Address | undefined;\n createdAt: number;\n expiresAt: number;\n status: \"PENDING\" | \"RESOLVED\";\n txHash?: Hex;\n }\n >();\n private nextCreditId = 1;\n\n async reservePendingCredit(\n userAddress: Address,\n amount: bigint,\n durationMs: number,\n tokenAddress?: Address,\n ): Promise<string> {\n if (amount <= 0n) {\n throw new Error(\n \"MemoryPointLedger: pending credit amount must be positive\",\n );\n }\n if (durationMs <= 0) {\n throw new Error(\"MemoryPointLedger: durationMs must be positive\");\n }\n const user = getAddress(userAddress);\n const lockId = `credit-${this.nextCreditId++}`;\n const now = this.now();\n this.pendingCredits.set(lockId, {\n lockId,\n userAddress: user,\n amount,\n tokenAddress:\n tokenAddress !== undefined ? getAddress(tokenAddress) : undefined,\n createdAt: now,\n expiresAt: now + durationMs,\n status: \"PENDING\",\n });\n return lockId;\n }\n\n async resolveCreditByBurnTx(\n lockId: string,\n txHash: Hex,\n ): Promise<void> {\n const credit = this.pendingCredits.get(lockId);\n if (!credit) {\n throw new Error(\n `MemoryPointLedger: unknown pending credit lockId ${lockId}`,\n );\n }\n // Idempotent — already resolved with this tx is fine.\n if (credit.status === \"RESOLVED\") {\n if (credit.txHash === txHash) return;\n throw new Error(\n `MemoryPointLedger: credit ${lockId} already resolved with a different txHash`,\n );\n }\n\n // Apply the credit to the user's balance.\n const token = normalizeToken(credit.tokenAddress);\n const key = balanceKey(credit.userAddress, token);\n const current = this.balances.get(key) ?? 0n;\n this.balances.set(key, current + credit.amount);\n\n credit.status = \"RESOLVED\";\n credit.txHash = txHash;\n }\n\n // -------------------------------------------------------------------------\n // Internal helpers\n // -------------------------------------------------------------------------\n\n /**\n * Auto-expire any PENDING lock past its expiry. Called lazily on every\n * read/write so the in-memory state stays self-cleaning without a timer.\n */\n private purgeExpired(): void {\n const now = this.now();\n for (const lock of this.locks.values()) {\n if (lock.status === \"PENDING\" && lock.expiresAt <= now) {\n lock.status = \"EXPIRED\";\n }\n }\n }\n\n private lockedTotalFor(userAddress: Address, tokenKey: string): bigint {\n let total = 0n;\n for (const lock of this.locks.values()) {\n if (\n lock.userAddress === userAddress &&\n lock.status === \"PENDING\" &&\n (lock.tokenAddress ?? DEFAULT_TOKEN_KEY) === tokenKey\n ) {\n total += lock.amount;\n }\n }\n return total;\n }\n}\n\n// Sentinel used when a caller doesn't pass `tokenAddress` — legacy\n// single-token mode. Using a string that can never collide with a real\n// checksum address (no 0x prefix, not 40 hex chars).\nconst DEFAULT_TOKEN_KEY = \"default\";\n\nfunction normalizeToken(tokenAddress: Address | undefined): string {\n return tokenAddress === undefined ? DEFAULT_TOKEN_KEY : getAddress(tokenAddress);\n}\n\nfunction balanceKey(user: Address, tokenKey: string): string {\n return `${user}|${tokenKey}`;\n}\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\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 { createWalletClient, http } from \"viem\";\nimport { privateKeyToAccount } from \"viem/accounts\";\nimport type { Address, Chain, Hex } from \"viem\";\nimport {\n signMintRequest,\n type EIP712Signature,\n type MintRequest,\n type PointTokenDomainConfig,\n} from \"@pafi-dev/core\";\nimport type { IIssuerSigner } from \"./types\";\n\nexport interface PrivateKeySignerOptions {\n /** 0x-prefixed 32-byte hex private key */\n privateKey: Hex;\n /**\n * Chain metadata for the viem WalletClient. Only the chain id is actually\n * used for signing; a minimal stub is acceptable (it does not need to\n * match the deployed chain config beyond id).\n */\n chain: Chain;\n /**\n * Optional RPC URL. `signTypedData` is offline, so this can usually be\n * left unset — viem only requires a transport to construct the client.\n */\n rpcUrl?: string;\n}\n\n/**\n * Local-key implementation of `IIssuerSigner`. Wraps viem's `signTypedData`\n * via the shared `@pafi/core` `signMintRequest` helper.\n *\n * ⚠️ **NOT for production use.** The private key lives in process memory\n * and is trivially extractable from a compromised host. Replace with an\n * HSM/KMS/MPC-backed `IIssuerSigner` before deployment.\n */\nexport class PrivateKeySigner implements IIssuerSigner {\n private readonly account: ReturnType<typeof privateKeyToAccount>;\n private readonly walletClient: ReturnType<typeof createWalletClient>;\n\n constructor(opts: PrivateKeySignerOptions) {\n this.account = privateKeyToAccount(opts.privateKey);\n this.walletClient = createWalletClient({\n account: this.account,\n chain: opts.chain,\n transport: http(opts.rpcUrl),\n });\n }\n\n async signMintRequest(\n domain: PointTokenDomainConfig,\n message: MintRequest,\n ): Promise<EIP712Signature> {\n return signMintRequest(this.walletClient, domain, message);\n }\n\n async getAddress(): Promise<Address> {\n return this.account.address;\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 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 < 16) {\n throw new Error(\"AuthService: jwtSecret must be at least 16 chars\");\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.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 {\n // Swallow — logout is idempotent and never throws.\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","import type { Address, Hex } from \"viem\";\nimport type { MintParams, SwapParams } from \"@pafi-dev/core\";\n\n/**\n * Parameters for a single `mintAndSwap` relay submission. This is the\n * exact shape the Relay contract expects, with the two structs the\n * `@pafi/core` calldata helpers build. The RelayService wires these\n * through viem's `writeContract`.\n */\nexport interface SubmitMintAndSwapParams {\n mint: MintParams;\n swap: SwapParams;\n}\n\n/**\n * Result of a relay submission. `txHash` is returned immediately after\n * the tx is broadcast; `receipt` is present only when the caller opted to\n * wait for confirmation.\n */\nexport interface RelayResult {\n txHash: Hex;\n blockNumber?: bigint;\n gasUsed?: bigint;\n status?: \"success\" | \"reverted\";\n}\n\n/**\n * Errors raised by RelayService carry a `code` so the MintingGateway (or\n * any caller) can decide whether to release the ledger lock.\n */\nexport type RelayErrorCode =\n | \"NOT_CONFIGURED\" // operator wallet / relay address missing\n | \"ENCODE_FAILED\" // calldata encoding threw\n | \"SIMULATION_FAILED\" // pre-flight simulate reverted\n | \"SUBMIT_FAILED\" // writeContract threw\n | \"TX_REVERTED\" // tx mined with status != success\n | \"TIMEOUT\"; // waitForTransactionReceipt hit its deadline\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\n/**\n * Interface an operator wallet must satisfy for the RelayService. Matches\n * the subset of viem's `WalletClient` we actually call, so tests can pass\n * a minimal mock instead of a full client.\n */\nexport interface OperatorWalletLike {\n writeContract: (args: {\n address: Address;\n abi: readonly unknown[];\n functionName: string;\n args: readonly unknown[];\n account?: { address: Address };\n }) => Promise<Hex>;\n account?: { address: Address } | undefined;\n}\n","import {\n encodeFunctionData,\n erc20Abi,\n type Address,\n type Hex,\n type PublicClient,\n type WalletClient,\n} from \"viem\";\nimport {\n relayAbi,\n encodeMintAndSwap,\n simulateMintAndSwap as coreSimulateMintAndSwap,\n SimulationError,\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 {\n RelayError,\n type OperatorWalletLike,\n type RelayResult,\n type SubmitMintAndSwapParams,\n} from \"./types\";\n\nexport interface RelayServiceConfig {\n /** Address of the deployed Relay contract (chain-specific). */\n relayAddress: Address;\n /** Operator wallet that pays gas and receives the operator fee. */\n operatorWallet: OperatorWalletLike;\n /**\n * Provider used for pre-flight simulation and receipt waiting. Optional —\n * if omitted, the service still broadcasts but returns only `txHash`\n * (caller is responsible for confirmation tracking).\n */\n provider?: PublicClient;\n /**\n * If a provider is supplied, wait up to this many milliseconds for a\n * receipt before timing out. Default: 60_000 (one minute).\n */\n confirmationTimeoutMs?: number;\n /**\n * Whether to run `simulateContract` before submitting. Catches most\n * reverts locally without wasting gas. Default: true (when provider is\n * supplied).\n */\n simulateBeforeSubmit?: boolean;\n}\n\nconst DEFAULT_CONFIRMATION_TIMEOUT_MS = 60_000;\n\n/**\n * Submits `mintAndSwap` transactions to the Relay contract (legacy\n * v0.2 path) and builds unsigned UserOps for the v1.4 sponsored flow.\n *\n * v1.4 flow (post-Relayer-removal):\n * - `prepareMint` — signs `MintRequest` with the issuer signer and\n * encodes `PointToken.mint(to, amount, deadline, minterSig)` into\n * a UserOp the user submits via EIP-7702 + Paymaster.\n * - `prepareBurn` — mirrors on the burn side using `BurnRequest` +\n * `PointToken.burn(from, amount, deadline, burnerSig)`.\n *\n * Legacy v0.2 `submitMintAndSwap` still broadcasts via operator wallet\n * for the deprecated `/claim-and-swap` endpoint.\n */\nexport class RelayService {\n private readonly relayAddress: Address;\n private readonly operatorWallet: OperatorWalletLike;\n private readonly provider?: PublicClient;\n private readonly confirmationTimeoutMs: number;\n private readonly simulateBeforeSubmit: boolean;\n\n constructor(config: RelayServiceConfig) {\n if (!config.relayAddress) {\n throw new Error(\"RelayService: relayAddress is required\");\n }\n if (!config.operatorWallet) {\n throw new Error(\"RelayService: operatorWallet is required\");\n }\n this.relayAddress = config.relayAddress;\n this.operatorWallet = config.operatorWallet;\n if (config.provider) this.provider = config.provider;\n this.confirmationTimeoutMs =\n config.confirmationTimeoutMs ?? DEFAULT_CONFIRMATION_TIMEOUT_MS;\n this.simulateBeforeSubmit =\n config.simulateBeforeSubmit ?? config.provider !== undefined;\n }\n\n /** Address the operator wallet is broadcasting from (for logging). */\n operatorAddress(): Address | undefined {\n return this.operatorWallet.account?.address;\n }\n\n /**\n * Build calldata for the Relay `mintAndSwap` function. Kept public so\n * callers (e.g. the MintingGateway) can log or persist the encoded call\n * for audit before broadcasting.\n */\n encodeCall(params: SubmitMintAndSwapParams): Hex {\n try {\n return encodeMintAndSwap(params.mint, params.swap);\n } catch (err) {\n throw new RelayError(\n \"ENCODE_FAILED\",\n `Failed to encode mintAndSwap calldata: ${errorMessage(err)}`,\n err,\n );\n }\n }\n\n /**\n * Submit a `mintAndSwap` transaction (legacy v0.2 `/claim-and-swap`).\n *\n * @deprecated Since 0.3.0 — replaced by `prepareMint` / `prepareBurn`\n * in the v1.4 sponsored-UserOp flow. Kept for v0.2.x consumers;\n * scheduled removal in 2.0.\n */\n async submitMintAndSwap(\n params: SubmitMintAndSwapParams,\n ): Promise<RelayResult> {\n if (this.simulateBeforeSubmit && this.provider) {\n const operatorAddr = this.operatorWallet.account?.address;\n if (operatorAddr) {\n try {\n await coreSimulateMintAndSwap(\n this.provider,\n this.relayAddress,\n params.mint,\n params.swap,\n operatorAddr,\n );\n } catch (err) {\n const reason =\n err instanceof SimulationError\n ? err.reason\n : errorMessage(err);\n throw new RelayError(\n \"SIMULATION_FAILED\",\n `mintAndSwap would revert: ${reason}`,\n err,\n );\n }\n }\n }\n\n let txHash: Hex;\n try {\n txHash = await this.operatorWallet.writeContract({\n address: this.relayAddress,\n abi: relayAbi,\n functionName: \"mintAndSwap\",\n args: [params.mint, params.swap],\n ...(this.operatorWallet.account\n ? { account: this.operatorWallet.account }\n : {}),\n });\n } catch (err) {\n throw new RelayError(\n \"SUBMIT_FAILED\",\n `Failed to broadcast mintAndSwap: ${errorMessage(err)}`,\n err,\n );\n }\n\n if (!this.provider) {\n return { txHash };\n }\n\n try {\n const receipt = await this.provider.waitForTransactionReceipt({\n hash: txHash,\n timeout: this.confirmationTimeoutMs,\n });\n if (receipt.status !== \"success\") {\n throw new RelayError(\n \"TX_REVERTED\",\n `mintAndSwap reverted on-chain (tx=${txHash})`,\n );\n }\n return {\n txHash,\n blockNumber: receipt.blockNumber,\n gasUsed: receipt.gasUsed,\n status: receipt.status,\n };\n } catch (err) {\n if (err instanceof RelayError) throw err;\n throw new RelayError(\n \"TIMEOUT\",\n `Timed out waiting for mintAndSwap receipt (tx=${txHash}): ${errorMessage(err)}`,\n err,\n );\n }\n }\n\n // ==========================================================================\n // v1.4 — Sponsored UserOp preparation (sig-gated mint + burn)\n // ==========================================================================\n\n /**\n * Build an unsigned UserOp for Scenario 1 (Mint) — sig-gated\n * `PointToken.mint(to, amount, deadline, minterSig)`.\n *\n * Flow:\n * 1. Issuer backend signs `MintRequest(to=user, amount, nonce, deadline)`\n * with its minter signer (HSM/KMS) → `minterSig`.\n * 2. Encode `PointToken.mint(user, amount, deadline, minterSig)`.\n * On-chain, `msg.sender` must equal `to` — satisfied by EIP-7702\n * delegating the user EOA to BatchExecutor.\n * 3. Optional PT fee transfer appended after mint (application-level\n * fee recovery since Relayer v2 no longer exists).\n * 4. Return `PartialUserOperation` ready for Bundler gas estimate +\n * Paymaster sponsorship + user signature.\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\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 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 user is a whitelisted burner. Not the typical\n * v1.4 path (users aren't burners); kept for admin/operator tools.\n * - `mode: 'burnWithSig'` — `PointToken.burn(from, amount, deadline,\n * burnerSig)`. Issuer signs `BurnRequest` off-chain; user submits\n * via EIP-7702. `msg.sender == from` enforced on-chain. This is\n * the user-initiated redeem path in v1.4.\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 const operations: Operation[] = [\n {\n target: params.pointTokenAddress,\n value: 0n,\n data: burnCallData,\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// ===========================================================================\n// Parameter types for the v1.4 prepareMint / prepareBurn methods\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 (name + chainId + verifyingContract). */\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 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\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 gasPrice = await this.provider.getGasPrice();\n const nativeCost = gasPrice * this.gasUnits;\n const withPremium =\n (nativeCost * BigInt(this.gasPremiumBps)) / 10_000n;\n return this.quoteNativeToFee(withPremium);\n }\n}\n","import type { Address, Hex } from \"viem\";\nimport type {\n PathKey,\n PointTokenDomainConfig,\n ReceiverConsent,\n} from \"@pafi-dev/core\";\n\n/**\n * End-user request for a full \"burn points → receive USDT\" flow. The\n * receiver has already signed the `ReceiverConsent` EIP-712 message on\n * the frontend; the issuer backend runs everything else.\n */\nexport interface MintAndCashOutRequest {\n /** Address owning the off-chain points (== receiver in the consent). */\n userAddress: Address;\n\n /** Point token contract to mint. */\n pointTokenAddress: Address;\n\n /** Chain id the relay will submit on. */\n chainId: number;\n\n /**\n * EIP-712 domain for `pointTokenAddress`. The gateway passes this\n * straight through to `@pafi/core` `verifyReceiverConsent` and the\n * issuer signer. Callers typically fetch it once via\n * `PafiSDK.getDomain()` and cache it.\n */\n domain: PointTokenDomainConfig;\n\n /**\n * Receiver consent message + signature, pre-built by the frontend. The\n * message specifies `onBehalfOf = relay contract` and\n * `originalReceiver = userAddress`, plus the amount, nonce, deadline,\n * and encoded extData.\n */\n receiverConsent: ReceiverConsent;\n receiverSignature: Hex;\n\n /**\n * Swap path for the USDT output. Empty array = \"no swap\" (rare — only\n * useful for testing). Normally a single hop pointToken → USDT.\n */\n swapPath: PathKey[];\n\n /** Swap deadline (unix seconds). */\n swapDeadline: bigint;\n\n /**\n * Lock TTL (ms) to apply to the off-chain balance. The gateway computes\n * a safe default from `receiverConsent.deadline` if omitted.\n */\n lockDurationMs?: number;\n}\n\n/**\n * Result returned to the caller after a successful `processMintAndCashOut`.\n * The `lockId` is preserved so the indexer can correlate the on-chain\n * Mint event back to the ledger row (though that correlation is typically\n * done by `(userAddress, amount)` in the default `MemoryPointLedger`).\n */\nexport interface MintAndCashOutResponse {\n txHash: Hex;\n lockId: string;\n blockNumber?: bigint;\n gasUsed?: bigint;\n}\n\n/**\n * Error codes a MintingGateway can surface. Callers (API handlers) map\n * these to HTTP status codes. The `SAFE_TO_RETRY` field tells the caller\n * whether the underlying lock was released — if not, the user should\n * wait before retrying to avoid double-spend.\n */\nexport type MintingGatewayErrorCode =\n | \"INVALID_REQUEST\" // missing / malformed fields\n | \"INVALID_CONSENT_SIGNATURE\" // ReceiverConsent sig didn't recover to userAddress\n | \"CONSENT_EXPIRED\" // deadline already passed\n | \"POLICY_REJECTED\" // IPolicyEngine.evaluate returned approved=false\n | \"INSUFFICIENT_BALANCE\" // ledger lock failed (no funds / all locked)\n | \"SIGNER_FAILED\" // IIssuerSigner threw\n | \"RELAY_SIMULATION_FAILED\" // pre-flight simulate reverted\n | \"RELAY_SUBMIT_FAILED\" // writeContract threw\n | \"RELAY_REVERTED\" // tx mined but reverted\n | \"RELAY_TIMEOUT\"; // waited past confirmation timeout\n\nexport class MintingGatewayError extends Error {\n readonly code: MintingGatewayErrorCode;\n /**\n * True if the ledger lock was released before this error was thrown,\n * meaning the user can safely retry. False means the funds are still\n * locked (e.g. tx may still land on-chain) and retry would double-spend.\n */\n readonly safeToRetry: boolean;\n readonly cause?: unknown;\n\n constructor(\n code: MintingGatewayErrorCode,\n message: string,\n opts: { safeToRetry: boolean; cause?: unknown },\n ) {\n super(message);\n this.name = \"MintingGatewayError\";\n this.code = code;\n this.safeToRetry = opts.safeToRetry;\n if (opts.cause !== undefined) this.cause = opts.cause;\n }\n}\n","import {\n verifyReceiverConsent,\n encodeExtData,\n type MintParams,\n type SwapParams,\n} from \"@pafi-dev/core\";\nimport type { IPointLedger } from \"../ledger/types\";\nimport type { IPolicyEngine } from \"../policy/types\";\nimport type { IIssuerSigner } from \"../signer/types\";\nimport type { RelayService } from \"../relay/relayService\";\nimport { RelayError } from \"../relay/types\";\nimport {\n MintingGatewayError,\n type MintAndCashOutRequest,\n type MintAndCashOutResponse,\n} from \"./types\";\n\nexport interface MintingGatewayConfig {\n ledger: IPointLedger;\n policy: IPolicyEngine;\n signer: IIssuerSigner;\n relayService: RelayService;\n /**\n * Clock override for tests. Defaults to `() => Date.now()`. Used to\n * compute safe lock TTLs and to check consent deadlines.\n */\n now?: () => number;\n /**\n * Extra buffer (ms) added on top of `(consent.deadline - now)` when the\n * caller doesn't supply a `lockDurationMs`. Default: 60_000 (one minute\n * — roughly 2× the Base L2 block confirmation time).\n */\n defaultLockBufferMs?: number;\n}\n\nconst DEFAULT_LOCK_BUFFER_MS = 60_000;\n\n/**\n * The MintingGateway is the central orchestrator that turns a user's\n * signed ReceiverConsent into an on-chain `mintAndSwap` tx and a\n * consistent off-chain ledger update.\n *\n * 11-step flow (per `PAFI_ISSUER_SDK_SPEC.md` §4.3):\n *\n * 1. Field validation (cheap rejects before any crypto)\n * 2. Verify ReceiverConsent signature via `@pafi/core`\n * 3. Check off-chain balance via ledger\n * 4. Check locked requests via ledger\n * 5. Run policy engine (balance + on-chain cap + issuer rules)\n * 6. Lock the requested amount in the ledger\n * 7. Sign MintRequest as issuer\n * 8. Build MintParams + SwapParams\n * 9. Submit via RelayService (encode + simulate + broadcast + wait)\n * 10. Return { txHash, lockId, receipt fields }\n * 11. On any failure, release the lock IF it's safe to retry — i.e. we\n * know the tx cannot still land on-chain. If the tx may have made\n * it (`TX_REVERTED` or `TIMEOUT`), we leave the lock alone and\n * surface `safeToRetry: false` so the caller doesn't double-spend.\n *\n * The gateway deliberately does NOT deduct the balance here. Deduction\n * happens in the `PointIndexer` when the on-chain Mint event is observed,\n * which is what makes the system crash-safe: if the gateway dies between\n * broadcast and receipt, the indexer will still finalize the ledger.\n */\nexport class MintingGateway {\n private readonly ledger: IPointLedger;\n private readonly policy: IPolicyEngine;\n private readonly signer: IIssuerSigner;\n private readonly relayService: RelayService;\n private readonly now: () => number;\n private readonly defaultLockBufferMs: number;\n\n constructor(config: MintingGatewayConfig) {\n if (!config.ledger) throw new Error(\"MintingGateway: ledger required\");\n if (!config.policy) throw new Error(\"MintingGateway: policy required\");\n if (!config.signer) throw new Error(\"MintingGateway: signer required\");\n if (!config.relayService)\n throw new Error(\"MintingGateway: relayService required\");\n\n this.ledger = config.ledger;\n this.policy = config.policy;\n this.signer = config.signer;\n this.relayService = config.relayService;\n this.now = config.now ?? (() => Date.now());\n this.defaultLockBufferMs =\n config.defaultLockBufferMs ?? DEFAULT_LOCK_BUFFER_MS;\n }\n\n /**\n * @deprecated Since 0.3.0 — will be renamed to `processMint()` once\n * the SC team finalizes Relayer v2 ABI. The new flow drops the\n * swap steps entirely (no more single-call mint+swap); users swap\n * separately on PAFI Web. Kept here for v0.2.x consumers. Removed in 2.0.\n */\n async processMintAndCashOut(\n request: MintAndCashOutRequest,\n ): Promise<MintAndCashOutResponse> {\n // -----------------------------------------------------------------------\n // Step 1 — Field validation (cheap + synchronous)\n // -----------------------------------------------------------------------\n const { receiverConsent, receiverSignature } = request;\n if (!receiverConsent || !receiverSignature) {\n throw new MintingGatewayError(\n \"INVALID_REQUEST\",\n \"receiverConsent and receiverSignature are required\",\n { safeToRetry: true },\n );\n }\n if (receiverConsent.amount <= 0n) {\n throw new MintingGatewayError(\n \"INVALID_REQUEST\",\n \"consent amount must be positive\",\n { safeToRetry: true },\n );\n }\n if (receiverConsent.originalReceiver !== request.userAddress) {\n throw new MintingGatewayError(\n \"INVALID_REQUEST\",\n \"consent.originalReceiver must equal request.userAddress\",\n { safeToRetry: true },\n );\n }\n\n const nowSec = BigInt(Math.floor(this.now() / 1000));\n if (receiverConsent.deadline <= nowSec) {\n throw new MintingGatewayError(\n \"CONSENT_EXPIRED\",\n \"ReceiverConsent deadline has already passed\",\n { safeToRetry: true },\n );\n }\n\n // -----------------------------------------------------------------------\n // Step 2 — Verify ReceiverConsent signature\n // -----------------------------------------------------------------------\n const consentResult = await verifyReceiverConsent(\n request.domain,\n receiverConsent,\n receiverSignature,\n request.userAddress,\n );\n if (!consentResult.isValid) {\n throw new MintingGatewayError(\n \"INVALID_CONSENT_SIGNATURE\",\n `ReceiverConsent signature did not recover to ${request.userAddress}`,\n { safeToRetry: true },\n );\n }\n\n // -----------------------------------------------------------------------\n // Steps 3 + 4 + 5 — Policy check (default engine reads balance + locks\n // via the ledger, then calls MintingOracle.verifyMintCap if wired).\n // -----------------------------------------------------------------------\n const policyDecision = await this.policy.evaluate({\n userAddress: request.userAddress,\n amount: receiverConsent.amount,\n pointTokenAddress: request.pointTokenAddress,\n chainId: request.chainId,\n });\n if (!policyDecision.approved) {\n const code =\n policyDecision.reason?.toLowerCase().includes(\"insufficient\")\n ? \"INSUFFICIENT_BALANCE\"\n : \"POLICY_REJECTED\";\n throw new MintingGatewayError(\n code,\n policyDecision.reason ?? \"Minting request rejected by policy engine\",\n { safeToRetry: true },\n );\n }\n\n // -----------------------------------------------------------------------\n // Step 6 — Lock the amount in the ledger\n // -----------------------------------------------------------------------\n const lockDurationMs =\n request.lockDurationMs ?? this.computeLockDurationMs(receiverConsent.deadline);\n\n let lockId: string;\n try {\n lockId = await this.ledger.lockForMinting(\n request.userAddress,\n receiverConsent.amount,\n lockDurationMs,\n request.pointTokenAddress,\n );\n } catch (err) {\n // Race between policy check and lock — someone else locked the\n // funds in between. Surface as INSUFFICIENT_BALANCE so the user can\n // retry once the other request resolves.\n throw new MintingGatewayError(\n \"INSUFFICIENT_BALANCE\",\n `Failed to lock ledger balance: ${errorMessage(err)}`,\n { safeToRetry: true, cause: err },\n );\n }\n\n // Beyond this point, every error path MUST either (a) release the\n // lock before throwing, or (b) set safeToRetry=false so the caller\n // knows the lock is still live.\n try {\n // ---------------------------------------------------------------------\n // Step 7 — Sign MintRequest as the issuer\n // ---------------------------------------------------------------------\n let minterSignature;\n try {\n minterSignature = await this.signer.signMintRequest(request.domain, {\n to: request.userAddress,\n amount: receiverConsent.amount,\n nonce: receiverConsent.nonce,\n deadline: receiverConsent.deadline,\n });\n } catch (err) {\n await this.releaseLockSafely(lockId);\n throw new MintingGatewayError(\n \"SIGNER_FAILED\",\n `Issuer signer failed: ${errorMessage(err)}`,\n { safeToRetry: true, cause: err },\n );\n }\n\n // ---------------------------------------------------------------------\n // Step 8 — Build MintParams + SwapParams\n //\n // We re-encode extData from the consent's own bytes so the structs\n // the Relay verifies against are byte-identical to what the user\n // signed. (encodeExtData is only used here if callers want a\n // typed helper — in the normal flow they already built it client\n // side and it's carried through receiverConsent.extData.)\n // ---------------------------------------------------------------------\n const mintParams: MintParams = {\n pointToken: request.pointTokenAddress,\n receiver: request.userAddress,\n amount: receiverConsent.amount,\n deadline: receiverConsent.deadline,\n minterSig: minterSignature.serialized,\n receiverSig: receiverSignature,\n extData: receiverConsent.extData,\n };\n const swapParams: SwapParams = {\n path: request.swapPath,\n deadline: request.swapDeadline,\n };\n\n // ---------------------------------------------------------------------\n // Step 9 — Submit via RelayService\n // ---------------------------------------------------------------------\n let relayResult;\n try {\n relayResult = await this.relayService.submitMintAndSwap({\n mint: mintParams,\n swap: swapParams,\n });\n } catch (err) {\n await this.handleRelayFailure(err, lockId);\n }\n\n // ---------------------------------------------------------------------\n // Step 10 — Return result\n // ---------------------------------------------------------------------\n // `handleRelayFailure` always throws, so `relayResult` is defined here.\n const result: MintAndCashOutResponse = {\n txHash: relayResult!.txHash,\n lockId,\n };\n if (relayResult!.blockNumber !== undefined) {\n result.blockNumber = relayResult!.blockNumber;\n }\n if (relayResult!.gasUsed !== undefined) {\n result.gasUsed = relayResult!.gasUsed;\n }\n return result;\n } catch (err) {\n // Already-typed errors propagate as-is (handleRelayFailure throws\n // these). Anything else is an unexpected bug — release the lock\n // and wrap it.\n if (err instanceof MintingGatewayError) throw err;\n await this.releaseLockSafely(lockId);\n throw new MintingGatewayError(\n \"RELAY_SUBMIT_FAILED\",\n `Unexpected error: ${errorMessage(err)}`,\n { safeToRetry: true, cause: err },\n );\n }\n }\n\n // ---------------------------------------------------------------------------\n // Internals\n // ---------------------------------------------------------------------------\n\n private computeLockDurationMs(consentDeadlineSec: bigint): number {\n const nowMs = this.now();\n const deadlineMs = Number(consentDeadlineSec) * 1000;\n const remaining = Math.max(0, deadlineMs - nowMs);\n return remaining + this.defaultLockBufferMs;\n }\n\n /**\n * Map a RelayError to a MintingGatewayError, releasing the lock only\n * when the tx definitely did not land. `TX_REVERTED` and `TIMEOUT`\n * leave the lock in place because the tx may still be in the mempool\n * or already mined — releasing would enable a double-spend on retry.\n * Always throws.\n */\n private async handleRelayFailure(err: unknown, lockId: string): Promise<never> {\n if (err instanceof RelayError) {\n switch (err.code) {\n case \"ENCODE_FAILED\":\n case \"SIMULATION_FAILED\":\n case \"SUBMIT_FAILED\":\n case \"NOT_CONFIGURED\":\n await this.releaseLockSafely(lockId);\n throw new MintingGatewayError(\n err.code === \"SIMULATION_FAILED\"\n ? \"RELAY_SIMULATION_FAILED\"\n : \"RELAY_SUBMIT_FAILED\",\n err.message,\n { safeToRetry: true, cause: err },\n );\n case \"TX_REVERTED\":\n throw new MintingGatewayError(\"RELAY_REVERTED\", err.message, {\n safeToRetry: false,\n cause: err,\n });\n case \"TIMEOUT\":\n throw new MintingGatewayError(\"RELAY_TIMEOUT\", err.message, {\n safeToRetry: false,\n cause: err,\n });\n }\n }\n await this.releaseLockSafely(lockId);\n throw new MintingGatewayError(\n \"RELAY_SUBMIT_FAILED\",\n `Unexpected relay error: ${errorMessage(err)}`,\n { safeToRetry: true, cause: err },\n );\n }\n\n /**\n * Release a lock, swallowing any secondary error. We never want a lock\n * release failure to mask the original error — the lock will auto-expire\n * via TTL anyway.\n */\n private async releaseLockSafely(lockId: string): Promise<void> {\n try {\n await this.ledger.releaseLock(lockId);\n } catch {\n // Intentionally swallowed.\n }\n }\n}\n\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n// -----------------------------------------------------------------------------\n// Re-export encodeExtData for issuers who want to build extData on the backend\n// (e.g. for testing) rather than receiving it from the client.\n// -----------------------------------------------------------------------------\nexport { encodeExtData };\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 {\n // Intentionally swallow — indexing is best-effort and retried.\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\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 /**\n * Caller-supplied matcher. Return the lockId to resolve for a given\n * burn event, or `undefined` to skip. Runs synchronously via the\n * ledger's query path.\n *\n * Default: try `ledger.resolveCreditByBurnTx` keyed on a synthetic\n * lock id `burn-${from}-${amount}` — the in-memory ledger assigns\n * incrementing IDs so callers with the memory ledger must provide a\n * custom matcher. Real DB-backed ledgers override this to JOIN on\n * their `pending_credits` table.\n */\n matchLockId: (\n event: BurnEvent,\n ) => Promise<string | undefined> = async () => 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\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 {\n // Indexing is best-effort; next tick retries.\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 lockId = await this.matchLockId(evt);\n if (!lockId) return;\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 {\n // Next tick will retry (cursor not advanced past this event yet\n // within the current chunk; subsequent chunks re-advance anyway\n // so worst case: a second pass that idempotently no-ops).\n }\n }\n}\n","import { getAddress } from \"viem\";\nimport type { Address, PublicClient } from \"viem\";\nimport {\n getMintRequestNonce,\n getPointTokenBalance,\n getReceiverConsentNonce,\n getTokenName,\n isMinter,\n buildReceiverConsentTypedData,\n} from \"@pafi-dev/core\";\nimport type { ReceiverConsent, PointTokenDomainConfig } from \"@pafi-dev/core\";\nimport type { AuthService } from \"../auth/loginVerifier\";\nimport type { MintingGateway } from \"../gateway/mintingGateway\";\nimport type { IPointLedger } from \"../ledger/types\";\nimport type { FeeManager } from \"../relay/feeManager\";\nimport type {\n ApiBuildConsentTypedDataRequest,\n ApiBuildConsentTypedDataResponse,\n ApiClaimAndSwapRequest,\n ApiClaimAndSwapResponse,\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 + flow --------------------------------------------------------\n authService: AuthService;\n gateway: MintingGateway;\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\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` / `MintingGatewayError` surfaced as typed\n * 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 gateway: MintingGateway;\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 /** First supported token — used as default when a handler doesn't\n * receive a `pointTokenAddress` in the request (shouldn't happen in\n * practice, but keeps type-narrowing happy). */\n private readonly defaultToken: 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\n constructor(config: IssuerApiHandlersConfig) {\n this.authService = config.authService;\n this.gateway = config.gateway;\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 this.defaultToken = normalized[0]!;\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 }\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 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 (chainId !== this.chainId) {\n throw new Error(\n `handleConfig: unsupported chainId ${chainId}, issuer is configured for ${this.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 /build-consent-typed-data`\n *\n * Backend builds the full EIP-712 typed data payload for a\n * ReceiverConsent message. The domain (name, version, chainId,\n * verifyingContract) is read from the PointToken contract — mobile\n * never needs to know or hardcode these values. Forward the\n * response directly to `wallet.signTypedData()`.\n *\n * This ensures a single source of truth for domain + types, and\n * makes contract upgrades (domain version bump) transparent to\n * mobile apps — no app store review needed.\n */\n async handleBuildConsentTypedData(\n userAddress: Address,\n request: ApiBuildConsentTypedDataRequest,\n ): Promise<ApiBuildConsentTypedDataResponse> {\n if (request.chainId !== this.chainId) {\n throw new Error(\n `handleBuildConsentTypedData: unsupported chainId ${request.chainId}`,\n );\n }\n const pointToken = getAddress(request.pointTokenAddress);\n if (!this.supportedTokens.has(pointToken)) {\n throw new Error(\n `handleBuildConsentTypedData: unsupported pointToken ${pointToken}`,\n );\n }\n\n const name = await getTokenName(this.provider, pointToken);\n const domain: PointTokenDomainConfig = {\n name,\n verifyingContract: pointToken,\n chainId: this.chainId,\n };\n\n const typedData = buildReceiverConsentTypedData(domain, request.receiverConsent);\n\n return {\n typedData: {\n domain: typedData.domain as unknown as ApiBuildConsentTypedDataResponse[\"typedData\"][\"domain\"],\n types: typedData.types as unknown as Record<string, { name: string; type: string }[]>,\n primaryType: typedData.primaryType,\n message: typedData.message as unknown as Record<string, unknown>,\n },\n };\n }\n\n /**\n * `POST /claim-and-swap`\n *\n * @deprecated Since 0.3.0 — the single-call mint-then-swap flow is\n * retired in v1.4. Use the new `handleClaim()` (mint only) and let\n * the user swap separately on PAFI Web. See\n * [V1.4_V1.5_OVERVIEW.md §4] for the new scenario model. Will be\n * removed in 2.0.\n *\n * Legacy behavior: the terminal handler forwards the verified\n * consent to the MintingGateway, which runs the 11-step flow.\n */\n async handleClaimAndSwap(\n userAddress: Address,\n request: ApiClaimAndSwapRequest,\n ): Promise<ApiClaimAndSwapResponse> {\n if (request.chainId !== this.chainId) {\n throw new Error(\n `handleClaimAndSwap: unsupported chainId ${request.chainId}`,\n );\n }\n const pointToken = getAddress(request.pointTokenAddress);\n if (!this.supportedTokens.has(pointToken)) {\n throw new Error(\n `handleClaimAndSwap: unsupported pointToken ${pointToken}`,\n );\n }\n\n const result = await this.gateway.processMintAndCashOut({\n userAddress: getAddress(userAddress),\n pointTokenAddress: pointToken,\n chainId: request.chainId,\n domain: request.domain,\n receiverConsent: request.receiverConsent,\n receiverSignature: request.receiverSignature,\n swapPath: request.swapPath,\n swapDeadline: request.swapDeadline,\n });\n\n const response: ApiClaimAndSwapResponse = {\n txHash: result.txHash,\n lockId: result.lockId,\n };\n if (result.blockNumber !== undefined)\n response.blockNumber = result.blockNumber;\n if (result.gasUsed !== undefined) response.gasUsed = result.gasUsed;\n return response;\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 } 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 userAddress: Address;\n amount: bigint;\n /** ERC-4337 account nonce for the user's EOA. */\n aaNonce: bigint;\n}\n\nexport interface PTRedeemResponse {\n /** Lock id from the ledger — client polls status with this. */\n lockId: string;\n /** Unsigned UserOp — FE attaches paymaster + user signature + submits. */\n userOp: PartialUserOperation;\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 | \"INVALID_AMOUNT\"\n | \"NONCE_READ_FAILED\"\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 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 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 (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 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 const burnRequest: BurnRequest = {\n from: request.userAddress,\n amount: request.amount,\n nonce: burnNonce,\n deadline,\n };\n\n // Sign with issuer burner signer.\n let burnerSignature: Hex;\n try {\n const sig = await signBurnRequest(\n this.burnerSignerWallet,\n domain,\n burnRequest,\n );\n burnerSignature = sig.serialized;\n } catch (err) {\n throw new PTRedeemError(\n \"SIGNING_FAILED\",\n `failed to sign BurnRequest: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n // Reserve the off-chain credit BEFORE returning the unsigned UserOp.\n // BurnIndexer resolves it when the burn lands. Abandoned tx → lock\n // expires and cleanup sweeps it.\n const lockId = await this.ledger.reservePendingCredit!(\n request.userAddress,\n request.amount,\n this.redeemLockDurationMs,\n this.pointTokenAddress,\n );\n\n const userOp = this.relayService.prepareBurn({\n mode: \"burnWithSig\",\n userAddress: request.userAddress,\n aaNonce: request.aaNonce,\n pointTokenAddress: this.pointTokenAddress,\n batchExecutorAddress: this.batchExecutorAddress,\n burnRequest,\n burnerSignature,\n });\n\n return {\n lockId,\n userOp,\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 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: \"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 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 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 type { Address } from \"viem\";\nimport type { PoolKey } from \"@pafi-dev/core\";\nimport type {\n ApiPoolsRequest,\n ApiPoolsResponse,\n PoolsProvider,\n} from \"../api/types\";\n\n/**\n * Config for `createSubgraphPoolsProvider`.\n */\nexport interface SubgraphPoolsProviderConfig {\n /**\n * Fully qualified subgraph GraphQL endpoint. Example:\n * `https://graph.pacificfinance.org/subgraphs/name/pafi`\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 if (!config.subgraphUrl) {\n throw new Error(\n \"createSubgraphPoolsProvider: subgraphUrl is required\",\n );\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 config.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 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","/**\n * Config for `createSubgraphNativeUsdtQuoter`.\n */\nexport interface SubgraphNativeUsdtQuoterConfig {\n /**\n * Fully qualified subgraph GraphQL endpoint. Same URL used by\n * `createSubgraphPoolsProvider`.\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 if (!config.subgraphUrl) {\n throw new Error(\n \"createSubgraphNativeUsdtQuoter: subgraphUrl is required\",\n );\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 config.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 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 type { Address, PublicClient } from \"viem\";\nimport { getPointTokenBalance } from \"@pafi-dev/core\";\nimport type { IPointLedger } from \"../ledger/types\";\n\n/**\n * Combined off-chain + on-chain balance for a single user / token pair.\n *\n * - `offChain` — the issuer's ledger balance (available, excluding locks)\n * - `onChain` — the user's ERC-20 balance from `PointToken.balanceOf`\n * - `total` — `offChain + onChain` (what the Issuer App displays)\n */\nexport interface CombinedBalance {\n offChain: bigint;\n onChain: bigint;\n total: bigint;\n}\n\nexport interface BalanceAggregatorConfig {\n provider: PublicClient;\n ledger: IPointLedger;\n}\n\n/**\n * v1.4 utility — aggregates off-chain + on-chain point balances into a\n * single view for the \"combined balance\" UI in the Issuer App.\n *\n * The `/user` API handler uses this internally; the helper is exposed\n * publicly so Issuer Apps can call it directly without going through\n * the HTTP layer (e.g., for server-rendered pages or admin dashboards).\n *\n * See [REQUIREMENTS_V2.md] §1 — \"The Issuer App displays a combined\n * balance (off-chain points + on-chain PT) and does not surface USDT.\"\n */\nexport class BalanceAggregator {\n private readonly provider: PublicClient;\n private readonly ledger: IPointLedger;\n\n constructor(config: BalanceAggregatorConfig) {\n if (!config.provider) {\n throw new Error(\"BalanceAggregator: provider is required\");\n }\n if (!config.ledger) {\n throw new Error(\"BalanceAggregator: ledger is required\");\n }\n this.provider = config.provider;\n this.ledger = config.ledger;\n }\n\n /**\n * Combined balance for a single (user, token) pair. Fetches off-chain\n * + on-chain in parallel.\n */\n async getCombinedBalance(\n user: Address,\n pointToken: Address,\n ): Promise<CombinedBalance> {\n const [offChain, onChain] = await Promise.all([\n this.ledger.getBalance(user, pointToken),\n getPointTokenBalance(this.provider, pointToken, user),\n ]);\n return {\n offChain,\n onChain,\n total: offChain + onChain,\n };\n }\n\n /**\n * Combined balance for multiple tokens owned by the same user. Runs\n * all lookups in parallel. Returns a Map keyed by the token address\n * (same casing as supplied — caller should normalize if needed).\n */\n async getCombinedBalanceMulti(\n user: Address,\n pointTokens: Address[],\n ): Promise<Map<Address, CombinedBalance>> {\n const entries = await Promise.all(\n pointTokens.map(async (token) => {\n const balance = await this.getCombinedBalance(user, token);\n return [token, balance] as const;\n }),\n );\n return new Map(entries);\n }\n}\n","import type { Address, Hex } from \"viem\";\nimport type { PartialUserOperation, SponsorshipScenario } from \"@pafi-dev/core\";\n\nexport interface RetryConfig {\n /**\n * Max total attempts including the first try. Default: 1 (no retry).\n * Set to 3 to get 2 retries after the initial call.\n *\n * Only applies when the server error body carries `safeToRetry: true`\n * or the failure is a transient network/timeout error.\n */\n maxAttempts?: number;\n /**\n * Initial backoff delay in ms. Default: 500. Each subsequent retry\n * doubles this (exponential backoff) and adds ±20% jitter.\n */\n initialDelayMs?: number;\n /**\n * Hard ceiling for a single backoff (ms). Default: 10_000.\n */\n maxDelayMs?: number;\n /**\n * Upper bound on `retryAfter` from the server. If the server asks us\n * to wait longer than this (e.g. rate limit until UTC midnight), the\n * client gives up rather than blocking. Default: 30_000.\n */\n maxRetryAfterMs?: number;\n}\n\nexport interface PafiBackendConfig {\n /**\n * PAFI Backend API base URL. Example:\n * https://api.pacificfinance.org\n * https://staging-api.pacificfinance.org\n */\n url: string;\n /** PAFI-assigned issuer ID (e.g., \"gg56\"). Sent in X-Issuer-Id header. */\n issuerId: string;\n /** Per-issuer API key (or JWT) for the Authorization header. */\n apiKey: string;\n /** Optional fetch override for tests. */\n fetchImpl?: typeof fetch;\n /**\n * Timeout (ms) for each request. Default: 10_000. PAFI Backend should\n * respond in <1s for the happy path; this is just the sanity bound.\n */\n timeoutMs?: number;\n /**\n * Retry policy for transient failures (5xx, 429, timeouts, network).\n * Omit or pass `{ maxAttempts: 1 }` to disable retry entirely.\n */\n retry?: RetryConfig;\n}\n\n/** Paired with `POST /paymaster/sponsor`. See SPONSORED_PATH_FLOW.md §4.1 */\nexport interface SponsorshipRequest {\n chainId: number;\n scenario: SponsorshipScenario;\n userOp: PartialUserOperation;\n target: {\n /** The allowlisted contract this batch call targets. */\n contract: Address;\n /** Function selector / name — validated against allowlist. */\n function: string;\n /** The PointToken involved (for scenario context). */\n pointToken?: Address;\n };\n}\n\nexport interface SponsorshipResponse {\n paymaster: Address;\n paymasterData: Hex;\n paymasterVerificationGasLimit: bigint;\n paymasterPostOpGasLimit: bigint;\n /** Unix seconds when this sponsorship expires. Re-request after. */\n expiresAt: number;\n}\n\n/**\n * Machine-readable error codes returned by PAFI Backend.\n *\n * Source of truth: `apps/paymaster-proxy` `CalldataValidationError`,\n * `RateLimitError`, `CoinbaseClientError`. Keep in sync.\n */\nexport type PafiBackendErrorCode =\n // Auth (paymaster-proxy guard)\n | \"MISSING_ISSUER_ID\"\n | \"MISSING_API_KEY\"\n | \"ISSUER_UNAUTHORIZED\"\n // Calldata validation\n | \"CALLDATA_INVALID\"\n | \"CALLDATA_EMPTY_BATCH\"\n | \"TARGET_NOT_ALLOWLISTED\"\n | \"FUNCTION_NOT_ALLOWED\"\n | \"SCENARIO_MISMATCH\"\n // Legacy alias — kept for back-compat with 0.2.x consumers\n | \"SCENARIO_DISABLED\"\n // Rate limit\n | \"RATE_LIMIT_EXCEEDED\"\n | \"RATE_LIMIT_EXCEEDED_DAILY\"\n | \"RATE_LIMIT_EXCEEDED_PER_USER\"\n | \"RATE_LIMITER_UNAVAILABLE\"\n // Coinbase upstream\n | \"PAYMASTER_REJECTED\"\n | \"PAYMASTER_UNAVAILABLE\"\n | \"PAYMASTER_TIMEOUT\"\n // Client-side failures\n | \"BAD_REQUEST\"\n | \"INTERNAL_ERROR\"\n | \"TIMEOUT\"\n | \"NETWORK_ERROR\";\n\nexport class PafiBackendError extends Error {\n /**\n * Seconds to wait before retry. Populated from the server body\n * (e.g. rate limit returns the number of seconds until UTC midnight).\n */\n public readonly retryAfter?: number;\n /**\n * `safeToRetry` as reported by the server body. Prefer this over the\n * code-based heuristic when available — the server knows more about\n * whether the same request will succeed on retry.\n */\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 /**\n * Whether the caller can safely retry the same request.\n *\n * If the server provided `safeToRetry` in the body, trust that.\n * Otherwise fall back to a code-based heuristic.\n */\n get safeToRetry(): boolean {\n if (this.serverSafeToRetry !== undefined) return this.serverSafeToRetry;\n switch (this.code) {\n case \"PAYMASTER_UNAVAILABLE\":\n case \"PAYMASTER_TIMEOUT\":\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 return true; // after retryAfter\n default:\n return false;\n }\n }\n}\n","import {\n PafiBackendError,\n type PafiBackendConfig,\n type PafiBackendErrorCode,\n type RetryConfig,\n type SponsorshipRequest,\n type SponsorshipResponse,\n} from \"./types\";\n\nconst DEFAULT_TIMEOUT_MS = 10_000;\n\nconst RETRY_DEFAULTS: Required<RetryConfig> = {\n maxAttempts: 1,\n initialDelayMs: 500,\n maxDelayMs: 10_000,\n maxRetryAfterMs: 30_000,\n};\n\n/**\n * HTTP client for the PAFI Backend paymaster proxy service. See\n * [SPONSORED_PATH_FLOW.md] for the full flow + API contract.\n *\n * This client sits between `@pafi/issuer`'s RelayService and the\n * PAFI Backend. It does NOT talk to Coinbase Paymaster directly —\n * PAFI Backend holds that integration.\n */\nexport class PafiBackendClient {\n private readonly url: string;\n private readonly issuerId: string;\n private readonly apiKey: string;\n private readonly fetchImpl: typeof fetch;\n private readonly timeoutMs: number;\n private readonly retry: Required<RetryConfig>;\n\n constructor(config: PafiBackendConfig) {\n if (!config.url) {\n throw new Error(\"PafiBackendClient: url is required\");\n }\n if (!config.issuerId) {\n throw new Error(\"PafiBackendClient: issuerId is required\");\n }\n if (!config.apiKey) {\n throw new Error(\"PafiBackendClient: apiKey is required\");\n }\n this.url = config.url.replace(/\\/+$/, \"\"); // strip trailing slash\n this.issuerId = config.issuerId;\n this.apiKey = config.apiKey;\n this.fetchImpl = config.fetchImpl ?? globalThis.fetch;\n this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.retry = { ...RETRY_DEFAULTS, ...(config.retry ?? {}) };\n\n if (!this.fetchImpl) {\n throw new Error(\n \"PafiBackendClient: no fetch implementation available — pass `fetchImpl` or run on Node 18+\",\n );\n }\n if (this.retry.maxAttempts < 1) {\n throw new Error(\"PafiBackendClient: retry.maxAttempts must be >= 1\");\n }\n }\n\n /**\n * Request paymaster sponsorship for a pre-built UserOperation.\n * See [SPONSORED_PATH_FLOW.md §4.1] for the API contract.\n *\n * Retries automatically on transient failures (5xx, timeouts, network\n * errors, and errors the server flags with `safeToRetry: true`) up to\n * `retry.maxAttempts`. 4xx errors that are not `safeToRetry` fail fast.\n *\n * @throws PafiBackendError on final failure after exhausting retries\n */\n async requestSponsorship(\n req: SponsorshipRequest,\n ): Promise<SponsorshipResponse> {\n return this.postWithRetry<SponsorshipResponse, SponsorshipRequest>(\n \"/paymaster/sponsor\",\n req,\n );\n }\n\n // -------------------------------------------------------------------------\n // Internals\n // -------------------------------------------------------------------------\n\n private async postWithRetry<TRes, TReq>(\n path: string,\n body: TReq,\n ): Promise<TRes> {\n let lastError: PafiBackendError | undefined;\n\n for (let attempt = 1; attempt <= this.retry.maxAttempts; attempt++) {\n try {\n return await this.post<TRes, TReq>(path, body);\n } catch (err) {\n if (!(err instanceof PafiBackendError)) throw err;\n lastError = err;\n\n const isLastAttempt = attempt >= this.retry.maxAttempts;\n if (isLastAttempt || !err.safeToRetry) throw err;\n\n const delay = this.computeBackoff(attempt, err.retryAfter);\n if (delay === null) throw err; // server asked to wait longer than cap\n await this.sleep(delay);\n }\n }\n\n // Unreachable — loop either returns or throws. Keeps TS happy.\n throw lastError!;\n }\n\n /**\n * Pick the delay before the next retry.\n * - If the server sent `retryAfter` (seconds), honor it (capped by\n * `maxRetryAfterMs`) — returns null if the server wait exceeds the\n * cap, signalling the caller should give up.\n * - Otherwise: exponential backoff with ±20% jitter, capped at\n * `maxDelayMs`.\n */\n private computeBackoff(attempt: number, retryAfter?: number): number | null {\n if (retryAfter !== undefined) {\n const serverMs = retryAfter * 1000;\n if (serverMs > this.retry.maxRetryAfterMs) return null;\n return serverMs;\n }\n const exp = this.retry.initialDelayMs * 2 ** (attempt - 1);\n const capped = Math.min(exp, this.retry.maxDelayMs);\n const jitter = capped * (0.8 + Math.random() * 0.4); // ±20%\n return Math.round(jitter);\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n private async post<TRes, TReq>(path: string, body: TReq): Promise<TRes> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);\n\n let response: Response;\n try {\n response = await this.fetchImpl(`${this.url}${path}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${this.apiKey}`,\n \"X-Issuer-Id\": this.issuerId,\n },\n body: JSON.stringify(body, this.bigintReplacer),\n signal: controller.signal,\n });\n } catch (err) {\n if ((err as Error).name === \"AbortError\") {\n throw new PafiBackendError(\n \"TIMEOUT\",\n `PAFI Backend request timed out after ${this.timeoutMs}ms`,\n 0,\n );\n }\n throw new PafiBackendError(\n \"NETWORK_ERROR\",\n `PAFI Backend unreachable: ${(err as Error).message}`,\n 0,\n );\n } finally {\n clearTimeout(timeoutId);\n }\n\n const text = await response.text();\n\n if (!response.ok) {\n // Try to parse structured error; fall back to raw text.\n let code: PafiBackendErrorCode = \"INTERNAL_ERROR\";\n let message = text || response.statusText;\n let details: unknown;\n let retryAfter: number | undefined;\n let serverSafeToRetry: boolean | undefined;\n try {\n const parsed = JSON.parse(text);\n code = (parsed.code as PafiBackendErrorCode) ?? code;\n message = parsed.message ?? message;\n details = parsed.details;\n if (typeof parsed.retryAfter === \"number\") retryAfter = parsed.retryAfter;\n if (typeof parsed.safeToRetry === \"boolean\") serverSafeToRetry = parsed.safeToRetry;\n } catch {\n // body wasn't JSON — keep raw text\n }\n throw new PafiBackendError(code, message, response.status, details, {\n ...(retryAfter !== undefined ? { retryAfter } : {}),\n ...(serverSafeToRetry !== undefined ? { safeToRetry: serverSafeToRetry } : {}),\n });\n }\n\n // BigInt revival — PAFI Backend returns gas limits as decimal strings\n // to avoid JS precision loss. Parse and coerce on the way in.\n return JSON.parse(text, this.bigintReviver) as TRes;\n }\n\n /** JSON replacer that stringifies bigints. Paired with bigintReviver. */\n private bigintReplacer = (_key: string, value: unknown): unknown => {\n return typeof value === \"bigint\" ? value.toString() : value;\n };\n\n /**\n * JSON reviver that coerces specific numeric-string fields back to\n * bigint. The server must send these fields as decimal strings.\n */\n private bigintReviver = (key: string, value: unknown): unknown => {\n if (\n typeof value === \"string\" &&\n (key.endsWith(\"GasLimit\") ||\n key === \"nonce\" ||\n key === \"callGasLimit\" ||\n key === \"verificationGasLimit\" ||\n key === \"preVerificationGas\" ||\n key === \"maxFeePerGas\" ||\n key === \"maxPriorityFeePerGas\" ||\n key === \"paymasterVerificationGasLimit\" ||\n key === \"paymasterPostOpGasLimit\") &&\n /^\\d+$/.test(value)\n ) {\n return BigInt(value);\n }\n return value;\n };\n}\n","import { getAddress } from \"viem\";\nimport type { Address, PublicClient } from \"viem\";\nimport { MemoryPointLedger } from \"./ledger/memoryLedger\";\nimport type { IPointLedger } from \"./ledger/types\";\nimport { DefaultPolicyEngine } from \"./policy/defaultPolicy\";\nimport type { IPolicyEngine } from \"./policy/types\";\nimport type { IIssuerSigner } from \"./signer/types\";\nimport { MemorySessionStore } from \"./auth/memorySessionStore\";\nimport type { ISessionStore } from \"./auth/types\";\nimport { AuthService } from \"./auth/loginVerifier\";\nimport { RelayService } from \"./relay/relayService\";\nimport type { OperatorWalletLike } from \"./relay/types\";\nimport { FeeManager, type FeeManagerConfig } from \"./relay/feeManager\";\nimport { MintingGateway } from \"./gateway/mintingGateway\";\nimport { PointIndexer } from \"./indexer/pointIndexer\";\nimport type {\n IIndexerCursorStore,\n} 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`. Everything except\n * the chain metadata, wallets, auth secret, and `signer` is optional and\n * falls back to the in-memory dev defaults — that makes the happy path\n * a single-call wire-up while still letting production issuers plug in\n * their own ledger, session store, policy engine, and KMS signer.\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 /**\n * Address of the deployed PointToken. Legacy single-token shortcut;\n * prefer `pointTokenAddresses` for multi-token issuers.\n */\n pointTokenAddress?: Address;\n /**\n * All PointToken addresses this issuer supports. Takes precedence over\n * `pointTokenAddress`. Factory creates one `PointIndexer` per address.\n */\n pointTokenAddresses?: Address[];\n /** Address of the deployed Relay contract. */\n relayAddress: Address;\n /**\n * Full contract address bundle returned verbatim by `handleConfig` so\n * the frontend SDK can pick them up.\n */\n contracts: ApiConfigResponse[\"contracts\"];\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 /** Operator wallet — pays gas, receives the operator fee. */\n operatorWallet: OperatorWalletLike;\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 // -- Required crypto signer --------------------------------------------\n\n /**\n * Issuer signer. No default — production issuers MUST plug in an\n * HSM/KMS-backed implementation. For local development use\n * `PrivateKeySigner` directly.\n */\n signer: IIssuerSigner;\n\n // -- Pluggable storage (optional — default = in-memory) ----------------\n\n ledger?: IPointLedger;\n policy?: IPolicyEngine;\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. Pass any subset of fields\n * to opt in — `provider` is inherited from the outer config.\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 // -- 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 // -- Relay service tuning (optional) -----------------------------------\n\n relay?: {\n simulateBeforeSubmit?: boolean;\n confirmationTimeoutMs?: number;\n };\n\n // -- Gateway tuning (optional) -----------------------------------------\n\n gateway?: {\n /** Extra lock TTL buffer beyond consent deadline. Default 60_000 ms. */\n defaultLockBufferMs?: number;\n };\n}\n\n// ---------------------------------------------------------------------------\n// Output shape — everything the factory wires\n// ---------------------------------------------------------------------------\n\nexport interface IssuerService {\n authService: AuthService;\n sessionStore: ISessionStore;\n ledger: IPointLedger;\n policy: IPolicyEngine;\n signer: IIssuerSigner;\n relayService: RelayService;\n feeManager: FeeManager | undefined;\n gateway: MintingGateway;\n /**\n * All indexers keyed by PointToken address. For multi-token issuers there\n * is one per configured token. Single-token issuers will find one entry.\n */\n indexers: Map<Address, PointIndexer>;\n /**\n * First indexer. Kept for backward compat with 0.1.x callers that\n * expected `service.indexer` to be a single instance.\n * @deprecated use `indexers.get(tokenAddress)` for multi-token.\n */\n indexer: PointIndexer;\n handlers: IssuerApiHandlers;\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Wire a fully-functional issuer service from a single config object.\n * Returns every constructed collaborator so the caller can also use the\n * indexer or relay service directly outside the HTTP layer.\n *\n * Defaults:\n * - `ledger` → `MemoryPointLedger`\n * - `sessionStore` → `MemorySessionStore`\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 (`signer`, `provider`,\n * `operatorWallet`, `auth.jwtSecret`, at least one point token) is missing.\n */\nexport function createIssuerService(\n config: IssuerServiceConfig,\n): IssuerService {\n // -- Required field guards ----------------------------------------------\n if (!config.provider) {\n throw new Error(\"createIssuerService: provider is required\");\n }\n if (!config.operatorWallet) {\n throw new Error(\"createIssuerService: operatorWallet is required\");\n }\n if (!config.signer) {\n throw new Error(\"createIssuerService: signer is required\");\n }\n if (!config.relayAddress) {\n throw new Error(\"createIssuerService: relayAddress 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 // -- Normalize point token addresses (array-first, legacy-compatible) --\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 // -- Resolve pluggables -------------------------------------------------\n const ledger: IPointLedger = config.ledger ?? new MemoryPointLedger();\n const sessionStore: ISessionStore =\n config.sessionStore ?? new MemorySessionStore();\n const policy: IPolicyEngine =\n config.policy ?? new DefaultPolicyEngine({ ledger });\n\n // -- AuthService --------------------------------------------------------\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 -------------------------------------------------------\n const relayServiceConfig: ConstructorParameters<typeof RelayService>[0] = {\n relayAddress: config.relayAddress,\n operatorWallet: config.operatorWallet,\n provider: config.provider,\n };\n if (config.relay?.simulateBeforeSubmit !== undefined) {\n relayServiceConfig.simulateBeforeSubmit =\n config.relay.simulateBeforeSubmit;\n }\n if (config.relay?.confirmationTimeoutMs !== undefined) {\n relayServiceConfig.confirmationTimeoutMs =\n config.relay.confirmationTimeoutMs;\n }\n const relayService = new RelayService(relayServiceConfig);\n\n // -- FeeManager (optional) ---------------------------------------------\n let feeManager: FeeManager | undefined;\n if (config.fee) {\n feeManager = new FeeManager({\n ...config.fee,\n provider: config.provider,\n });\n }\n\n // -- MintingGateway -----------------------------------------------------\n const gatewayConfig: ConstructorParameters<typeof MintingGateway>[0] = {\n ledger,\n policy,\n signer: config.signer,\n relayService,\n };\n if (config.gateway?.defaultLockBufferMs !== undefined) {\n gatewayConfig.defaultLockBufferMs = config.gateway.defaultLockBufferMs;\n }\n const gateway = new MintingGateway(gatewayConfig);\n\n // -- PointIndexers (one per token) --------------------------------------\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 // -- IssuerApiHandlers --------------------------------------------------\n const handlersConfig: ConstructorParameters<typeof IssuerApiHandlers>[0] = {\n authService,\n gateway,\n ledger,\n provider: config.provider,\n pointTokenAddresses: tokenAddresses,\n chainId: config.chainId,\n contracts: config.contracts,\n };\n if (feeManager) handlersConfig.feeManager = feeManager;\n if (config.poolsProvider) handlersConfig.poolsProvider = config.poolsProvider;\n const handlers = new IssuerApiHandlers(handlersConfig);\n\n // -- Optionally start polling ------------------------------------------\n if (config.indexer?.autoStart) {\n for (const idx of indexers.values()) {\n idx.start();\n }\n }\n\n return {\n authService,\n sessionStore,\n ledger,\n policy,\n signer: config.signer,\n relayService,\n feeManager,\n gateway,\n indexers,\n indexer: firstIndexer,\n handlers,\n };\n}\n"],"mappings":"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","names":["import_viem","import_viem","import_node_crypto","import_viem","import_core","joseErrors","import_viem","import_core","coreSimulateMintAndSwap","import_core","errorMessage","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","DEFAULT_CACHE_TTL_MS","import_core","import_viem"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/ledger/memoryLedger.ts","../src/policy/defaultPolicy.ts","../src/signer/privateKeySigner.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/balance/balanceAggregator.ts","../src/pafi-backend/types.ts","../src/pafi-backend/pafiBackendClient.ts","../src/config.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.1.0\";\n\n// -----------------------------------------------------------------------------\n// Phase 1 — core interfaces + default in-memory implementations\n// -----------------------------------------------------------------------------\nexport * from \"./ledger\";\nexport * from \"./policy\";\nexport * from \"./signer\";\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","import { getAddress } from \"viem\";\nimport type { Address, Hex } from \"viem\";\nimport type { IPointLedger, LockedMintRequest, MintingStatus } from \"./types\";\n\n/**\n * In-memory IPointLedger implementation for development and tests.\n *\n * NOT for production — state is lost on restart. Issuers should ship their\n * own database-backed implementation.\n *\n * Concurrency model: single-process, single-threaded (Node.js event loop).\n * The lock check + insert is atomic within a tick because no awaits sit\n * between balance read and lock write.\n *\n * **Multi-token (0.2.0):** Balances are keyed by `(user, token)`. If callers\n * omit `tokenAddress`, the literal string \"default\" is used — that keeps\n * single-token usage working exactly like 0.1.x.\n */\nexport class MemoryPointLedger implements IPointLedger {\n private balances = new Map<string, bigint>();\n private locks = new Map<string, LockedMintRequest>();\n private nextLockId = 1;\n private now: () => number;\n\n constructor(opts: { now?: () => number } = {}) {\n this.now = opts.now ?? (() => Date.now());\n }\n\n // -------------------------------------------------------------------------\n // Read\n // -------------------------------------------------------------------------\n\n async getBalance(\n userAddress: Address,\n tokenAddress?: Address,\n ): Promise<bigint> {\n const user = getAddress(userAddress);\n const token = normalizeToken(tokenAddress);\n this.purgeExpired();\n const total = this.balances.get(balanceKey(user, token)) ?? 0n;\n const locked = this.lockedTotalFor(user, token);\n return total - locked;\n }\n\n async getLockedRequests(\n userAddress: Address,\n tokenAddress?: Address,\n ): Promise<LockedMintRequest[]> {\n const user = getAddress(userAddress);\n const token = normalizeToken(tokenAddress);\n this.purgeExpired();\n const out: LockedMintRequest[] = [];\n for (const lock of this.locks.values()) {\n if (\n lock.userAddress === user &&\n lock.status === \"PENDING\" &&\n (lock.tokenAddress ?? DEFAULT_TOKEN_KEY) === token\n ) {\n out.push({ ...lock });\n }\n }\n return out;\n }\n\n // -------------------------------------------------------------------------\n // Write\n // -------------------------------------------------------------------------\n\n async creditBalance(\n userAddress: Address,\n amount: bigint,\n _reason: string,\n tokenAddress?: Address,\n ): Promise<void> {\n if (amount <= 0n) {\n throw new Error(\"MemoryPointLedger: credit amount must be positive\");\n }\n const user = getAddress(userAddress);\n const token = normalizeToken(tokenAddress);\n const key = balanceKey(user, token);\n const current = this.balances.get(key) ?? 0n;\n this.balances.set(key, current + amount);\n }\n\n async lockForMinting(\n userAddress: Address,\n amount: bigint,\n lockDurationMs: number,\n tokenAddress?: Address,\n ): Promise<string> {\n if (amount <= 0n) {\n throw new Error(\"MemoryPointLedger: lock amount must be positive\");\n }\n if (lockDurationMs <= 0) {\n throw new Error(\"MemoryPointLedger: lockDurationMs must be positive\");\n }\n\n const user = getAddress(userAddress);\n const token = normalizeToken(tokenAddress);\n this.purgeExpired();\n\n const total = this.balances.get(balanceKey(user, token)) ?? 0n;\n const alreadyLocked = this.lockedTotalFor(user, token);\n const available = total - alreadyLocked;\n if (available < amount) {\n throw new Error(\n `MemoryPointLedger: insufficient balance — available=${available}, requested=${amount}`,\n );\n }\n\n const lockId = `lock-${this.nextLockId++}`;\n const now = this.now();\n const lock: LockedMintRequest = {\n lockId,\n userAddress: user,\n amount,\n status: \"PENDING\",\n createdAt: now,\n expiresAt: now + lockDurationMs,\n };\n if (tokenAddress !== undefined) {\n lock.tokenAddress = getAddress(tokenAddress);\n }\n this.locks.set(lockId, lock);\n return lockId;\n }\n\n async releaseLock(lockId: string): Promise<void> {\n const lock = this.locks.get(lockId);\n if (!lock) return;\n // Releasing a still-pending lock removes it entirely (no audit trail in\n // this dev impl). Already-resolved locks are left as-is so the indexer\n // can still inspect them.\n if (lock.status === \"PENDING\") {\n this.locks.delete(lockId);\n }\n }\n\n async deductBalance(\n userAddress: Address,\n amount: bigint,\n txHash: Hex,\n tokenAddress?: Address,\n ): Promise<void> {\n if (amount <= 0n) {\n throw new Error(\"MemoryPointLedger: deduct amount must be positive\");\n }\n const user = getAddress(userAddress);\n const token = normalizeToken(tokenAddress);\n const key = balanceKey(user, token);\n const current = this.balances.get(key) ?? 0n;\n if (current < amount) {\n throw new Error(\n `MemoryPointLedger: cannot deduct ${amount} from balance ${current}`,\n );\n }\n this.balances.set(key, current - amount);\n\n // Resolve the matching pending lock so the locked total drops too.\n for (const lock of this.locks.values()) {\n if (\n lock.userAddress === user &&\n lock.status === \"PENDING\" &&\n lock.amount === amount &&\n (lock.tokenAddress ?? DEFAULT_TOKEN_KEY) === token\n ) {\n lock.status = \"MINTED\";\n lock.txHash = txHash;\n return;\n }\n }\n }\n\n async updateMintStatus(\n lockId: string,\n status: MintingStatus,\n txHash?: Hex,\n ): Promise<void> {\n const lock = this.locks.get(lockId);\n if (!lock) {\n throw new Error(`MemoryPointLedger: unknown lockId ${lockId}`);\n }\n lock.status = status;\n if (txHash) lock.txHash = txHash;\n }\n\n // -------------------------------------------------------------------------\n // v1.4 — Reverse flow (PT burn → off-chain credit)\n // -------------------------------------------------------------------------\n\n private pendingCredits = new Map<\n string,\n {\n lockId: string;\n userAddress: Address;\n amount: bigint;\n tokenAddress: Address | undefined;\n createdAt: number;\n expiresAt: number;\n status: \"PENDING\" | \"RESOLVED\";\n txHash?: Hex;\n }\n >();\n private nextCreditId = 1;\n\n async reservePendingCredit(\n userAddress: Address,\n amount: bigint,\n durationMs: number,\n tokenAddress?: Address,\n ): Promise<string> {\n if (amount <= 0n) {\n throw new Error(\n \"MemoryPointLedger: pending credit amount must be positive\",\n );\n }\n if (durationMs <= 0) {\n throw new Error(\"MemoryPointLedger: durationMs must be positive\");\n }\n const user = getAddress(userAddress);\n const lockId = `credit-${this.nextCreditId++}`;\n const now = this.now();\n this.pendingCredits.set(lockId, {\n lockId,\n userAddress: user,\n amount,\n tokenAddress:\n tokenAddress !== undefined ? getAddress(tokenAddress) : undefined,\n createdAt: now,\n expiresAt: now + durationMs,\n status: \"PENDING\",\n });\n return lockId;\n }\n\n async resolveCreditByBurnTx(\n lockId: string,\n txHash: Hex,\n ): Promise<void> {\n const credit = this.pendingCredits.get(lockId);\n if (!credit) {\n throw new Error(\n `MemoryPointLedger: unknown pending credit lockId ${lockId}`,\n );\n }\n // Idempotent — already resolved with this tx is fine.\n if (credit.status === \"RESOLVED\") {\n if (credit.txHash === txHash) return;\n throw new Error(\n `MemoryPointLedger: credit ${lockId} already resolved with a different txHash`,\n );\n }\n\n // Apply the credit to the user's balance.\n const token = normalizeToken(credit.tokenAddress);\n const key = balanceKey(credit.userAddress, token);\n const current = this.balances.get(key) ?? 0n;\n this.balances.set(key, current + credit.amount);\n\n credit.status = \"RESOLVED\";\n credit.txHash = txHash;\n }\n\n // -------------------------------------------------------------------------\n // Internal helpers\n // -------------------------------------------------------------------------\n\n /**\n * Auto-expire any PENDING lock past its expiry. Called lazily on every\n * read/write so the in-memory state stays self-cleaning without a timer.\n */\n private purgeExpired(): void {\n const now = this.now();\n for (const lock of this.locks.values()) {\n if (lock.status === \"PENDING\" && lock.expiresAt <= now) {\n lock.status = \"EXPIRED\";\n }\n }\n }\n\n private lockedTotalFor(userAddress: Address, tokenKey: string): bigint {\n let total = 0n;\n for (const lock of this.locks.values()) {\n if (\n lock.userAddress === userAddress &&\n lock.status === \"PENDING\" &&\n (lock.tokenAddress ?? DEFAULT_TOKEN_KEY) === tokenKey\n ) {\n total += lock.amount;\n }\n }\n return total;\n }\n}\n\n// Sentinel used when a caller doesn't pass `tokenAddress` — legacy\n// single-token mode. Using a string that can never collide with a real\n// checksum address (no 0x prefix, not 40 hex chars).\nconst DEFAULT_TOKEN_KEY = \"default\";\n\nfunction normalizeToken(tokenAddress: Address | undefined): string {\n return tokenAddress === undefined ? DEFAULT_TOKEN_KEY : getAddress(tokenAddress);\n}\n\nfunction balanceKey(user: Address, tokenKey: string): string {\n return `${user}|${tokenKey}`;\n}\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\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 { createWalletClient, http } from \"viem\";\nimport { privateKeyToAccount } from \"viem/accounts\";\nimport type { Address, Chain, Hex } from \"viem\";\nimport {\n signMintRequest,\n type EIP712Signature,\n type MintRequest,\n type PointTokenDomainConfig,\n} from \"@pafi-dev/core\";\nimport type { IIssuerSigner } from \"./types\";\n\nexport interface PrivateKeySignerOptions {\n /** 0x-prefixed 32-byte hex private key */\n privateKey: Hex;\n /**\n * Chain metadata for the viem WalletClient. Only the chain id is actually\n * used for signing; a minimal stub is acceptable (it does not need to\n * match the deployed chain config beyond id).\n */\n chain: Chain;\n /**\n * Optional RPC URL. `signTypedData` is offline, so this can usually be\n * left unset — viem only requires a transport to construct the client.\n */\n rpcUrl?: string;\n}\n\n/**\n * Local-key implementation of `IIssuerSigner`. Wraps viem's `signTypedData`\n * via the shared `@pafi/core` `signMintRequest` helper.\n *\n * ⚠️ **NOT for production use.** The private key lives in process memory\n * and is trivially extractable from a compromised host. Replace with an\n * HSM/KMS/MPC-backed `IIssuerSigner` before deployment.\n */\nexport class PrivateKeySigner implements IIssuerSigner {\n private readonly account: ReturnType<typeof privateKeyToAccount>;\n private readonly walletClient: ReturnType<typeof createWalletClient>;\n\n constructor(opts: PrivateKeySignerOptions) {\n this.account = privateKeyToAccount(opts.privateKey);\n this.walletClient = createWalletClient({\n account: this.account,\n chain: opts.chain,\n transport: http(opts.rpcUrl),\n });\n }\n\n async signMintRequest(\n domain: PointTokenDomainConfig,\n message: MintRequest,\n ): Promise<EIP712Signature> {\n return signMintRequest(this.walletClient, domain, message);\n }\n\n async getAddress(): Promise<Address> {\n return this.account.address;\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 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 < 16) {\n throw new Error(\"AuthService: jwtSecret must be at least 16 chars\");\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.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 {\n // Swallow — logout is idempotent and never throws.\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\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 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 const operations: Operation[] = [\n {\n target: params.pointTokenAddress,\n value: 0n,\n data: burnCallData,\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// ===========================================================================\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 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\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 gasPrice = await this.provider.getGasPrice();\n const nativeCost = gasPrice * this.gasUnits;\n const withPremium =\n (nativeCost * BigInt(this.gasPremiumBps)) / 10_000n;\n return this.quoteNativeToFee(withPremium);\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 {\n // Intentionally swallow — indexing is best-effort and retried.\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\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 /**\n * Caller-supplied matcher. Return the lockId to resolve for a given\n * burn event, or `undefined` to skip. Runs synchronously via the\n * ledger's query path.\n *\n * Default: try `ledger.resolveCreditByBurnTx` keyed on a synthetic\n * lock id `burn-${from}-${amount}` — the in-memory ledger assigns\n * incrementing IDs so callers with the memory ledger must provide a\n * custom matcher. Real DB-backed ledgers override this to JOIN on\n * their `pending_credits` table.\n */\n matchLockId: (\n event: BurnEvent,\n ) => Promise<string | undefined> = async () => 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\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 {\n // Indexing is best-effort; next tick retries.\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 lockId = await this.matchLockId(evt);\n if (!lockId) return;\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 {\n // Next tick will retry (cursor not advanced past this event yet\n // within the current chunk; subsequent chunks re-advance anyway\n // so worst case: a second pass that idempotently no-ops).\n }\n }\n}\n","import { getAddress } from \"viem\";\nimport type { Address, PublicClient } from \"viem\";\nimport {\n getMintRequestNonce,\n getPointTokenBalance,\n getReceiverConsentNonce,\n getTokenName,\n isMinter,\n buildReceiverConsentTypedData,\n} from \"@pafi-dev/core\";\nimport type { ReceiverConsent, 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 {\n ApiBuildConsentTypedDataRequest,\n ApiBuildConsentTypedDataResponse,\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\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 /** First supported token — used as default when a handler doesn't\n * receive a `pointTokenAddress` in the request (shouldn't happen in\n * practice, but keeps type-narrowing happy). */\n private readonly defaultToken: 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\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 this.defaultToken = normalized[0]!;\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 }\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 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 (chainId !== this.chainId) {\n throw new Error(\n `handleConfig: unsupported chainId ${chainId}, issuer is configured for ${this.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 /build-consent-typed-data`\n *\n * Backend builds the full EIP-712 typed data payload for a\n * ReceiverConsent message. The domain (name, version, chainId,\n * verifyingContract) is read from the PointToken contract — mobile\n * never needs to know or hardcode these values. Forward the\n * response directly to `wallet.signTypedData()`.\n *\n * This ensures a single source of truth for domain + types, and\n * makes contract upgrades (domain version bump) transparent to\n * mobile apps — no app store review needed.\n */\n async handleBuildConsentTypedData(\n userAddress: Address,\n request: ApiBuildConsentTypedDataRequest,\n ): Promise<ApiBuildConsentTypedDataResponse> {\n if (request.chainId !== this.chainId) {\n throw new Error(\n `handleBuildConsentTypedData: unsupported chainId ${request.chainId}`,\n );\n }\n const pointToken = getAddress(request.pointTokenAddress);\n if (!this.supportedTokens.has(pointToken)) {\n throw new Error(\n `handleBuildConsentTypedData: unsupported pointToken ${pointToken}`,\n );\n }\n\n const name = await getTokenName(this.provider, pointToken);\n const domain: PointTokenDomainConfig = {\n name,\n verifyingContract: pointToken,\n chainId: this.chainId,\n };\n\n const typedData = buildReceiverConsentTypedData(domain, request.receiverConsent);\n\n return {\n typedData: {\n domain: typedData.domain as unknown as ApiBuildConsentTypedDataResponse[\"typedData\"][\"domain\"],\n types: typedData.types as unknown as Record<string, { name: string; type: string }[]>,\n primaryType: typedData.primaryType,\n message: typedData.message as unknown as Record<string, unknown>,\n },\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 } 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 userAddress: Address;\n amount: bigint;\n /** ERC-4337 account nonce for the user's EOA. */\n aaNonce: bigint;\n}\n\nexport interface PTRedeemResponse {\n /** Lock id from the ledger — client polls status with this. */\n lockId: string;\n /** Unsigned UserOp — FE attaches paymaster + user signature + submits. */\n userOp: PartialUserOperation;\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 | \"INVALID_AMOUNT\"\n | \"NONCE_READ_FAILED\"\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 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 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 (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 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 const burnRequest: BurnRequest = {\n from: request.userAddress,\n amount: request.amount,\n nonce: burnNonce,\n deadline,\n };\n\n // Sign with issuer burner signer.\n let burnerSignature: Hex;\n try {\n const sig = await signBurnRequest(\n this.burnerSignerWallet,\n domain,\n burnRequest,\n );\n burnerSignature = sig.serialized;\n } catch (err) {\n throw new PTRedeemError(\n \"SIGNING_FAILED\",\n `failed to sign BurnRequest: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n // Reserve the off-chain credit BEFORE returning the unsigned UserOp.\n // BurnIndexer resolves it when the burn lands. Abandoned tx → lock\n // expires and cleanup sweeps it.\n const lockId = await this.ledger.reservePendingCredit!(\n request.userAddress,\n request.amount,\n this.redeemLockDurationMs,\n this.pointTokenAddress,\n );\n\n const userOp = this.relayService.prepareBurn({\n mode: \"burnWithSig\",\n userAddress: request.userAddress,\n aaNonce: request.aaNonce,\n pointTokenAddress: this.pointTokenAddress,\n batchExecutorAddress: this.batchExecutorAddress,\n burnRequest,\n burnerSignature,\n });\n\n return {\n lockId,\n userOp,\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 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: \"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 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 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 type { Address } from \"viem\";\nimport type { PoolKey } from \"@pafi-dev/core\";\nimport type {\n ApiPoolsRequest,\n ApiPoolsResponse,\n PoolsProvider,\n} from \"../api/types\";\n\n/**\n * Config for `createSubgraphPoolsProvider`.\n */\nexport interface SubgraphPoolsProviderConfig {\n /**\n * Fully qualified subgraph GraphQL endpoint. Example:\n * `https://graph.pacificfinance.org/subgraphs/name/pafi`\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 if (!config.subgraphUrl) {\n throw new Error(\n \"createSubgraphPoolsProvider: subgraphUrl is required\",\n );\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 config.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 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","/**\n * Config for `createSubgraphNativeUsdtQuoter`.\n */\nexport interface SubgraphNativeUsdtQuoterConfig {\n /**\n * Fully qualified subgraph GraphQL endpoint. Same URL used by\n * `createSubgraphPoolsProvider`.\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 if (!config.subgraphUrl) {\n throw new Error(\n \"createSubgraphNativeUsdtQuoter: subgraphUrl is required\",\n );\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 config.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 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 type { Address, PublicClient } from \"viem\";\nimport { getPointTokenBalance } from \"@pafi-dev/core\";\nimport type { IPointLedger } from \"../ledger/types\";\n\n/**\n * Combined off-chain + on-chain balance for a single user / token pair.\n *\n * - `offChain` — the issuer's ledger balance (available, excluding locks)\n * - `onChain` — the user's ERC-20 balance from `PointToken.balanceOf`\n * - `total` — `offChain + onChain` (what the Issuer App displays)\n */\nexport interface CombinedBalance {\n offChain: bigint;\n onChain: bigint;\n total: bigint;\n}\n\nexport interface BalanceAggregatorConfig {\n provider: PublicClient;\n ledger: IPointLedger;\n}\n\n/**\n * v1.4 utility — aggregates off-chain + on-chain point balances into a\n * single view for the \"combined balance\" UI in the Issuer App.\n *\n * The `/user` API handler uses this internally; the helper is exposed\n * publicly so Issuer Apps can call it directly without going through\n * the HTTP layer (e.g., for server-rendered pages or admin dashboards).\n *\n * See [REQUIREMENTS_V2.md] §1 — \"The Issuer App displays a combined\n * balance (off-chain points + on-chain PT) and does not surface USDT.\"\n */\nexport class BalanceAggregator {\n private readonly provider: PublicClient;\n private readonly ledger: IPointLedger;\n\n constructor(config: BalanceAggregatorConfig) {\n if (!config.provider) {\n throw new Error(\"BalanceAggregator: provider is required\");\n }\n if (!config.ledger) {\n throw new Error(\"BalanceAggregator: ledger is required\");\n }\n this.provider = config.provider;\n this.ledger = config.ledger;\n }\n\n /**\n * Combined balance for a single (user, token) pair. Fetches off-chain\n * + on-chain in parallel.\n */\n async getCombinedBalance(\n user: Address,\n pointToken: Address,\n ): Promise<CombinedBalance> {\n const [offChain, onChain] = await Promise.all([\n this.ledger.getBalance(user, pointToken),\n getPointTokenBalance(this.provider, pointToken, user),\n ]);\n return {\n offChain,\n onChain,\n total: offChain + onChain,\n };\n }\n\n /**\n * Combined balance for multiple tokens owned by the same user. Runs\n * all lookups in parallel. Returns a Map keyed by the token address\n * (same casing as supplied — caller should normalize if needed).\n */\n async getCombinedBalanceMulti(\n user: Address,\n pointTokens: Address[],\n ): Promise<Map<Address, CombinedBalance>> {\n const entries = await Promise.all(\n pointTokens.map(async (token) => {\n const balance = await this.getCombinedBalance(user, token);\n return [token, balance] as const;\n }),\n );\n return new Map(entries);\n }\n}\n","import type { Address, Hex } from \"viem\";\nimport type { PartialUserOperation, SponsorshipScenario } from \"@pafi-dev/core\";\n\nexport interface RetryConfig {\n /**\n * Max total attempts including the first try. Default: 1 (no retry).\n * Set to 3 to get 2 retries after the initial call.\n *\n * Only applies when the server error body carries `safeToRetry: true`\n * or the failure is a transient network/timeout error.\n */\n maxAttempts?: number;\n /**\n * Initial backoff delay in ms. Default: 500. Each subsequent retry\n * doubles this (exponential backoff) and adds ±20% jitter.\n */\n initialDelayMs?: number;\n /**\n * Hard ceiling for a single backoff (ms). Default: 10_000.\n */\n maxDelayMs?: number;\n /**\n * Upper bound on `retryAfter` from the server. If the server asks us\n * to wait longer than this (e.g. rate limit until UTC midnight), the\n * client gives up rather than blocking. Default: 30_000.\n */\n maxRetryAfterMs?: number;\n}\n\nexport interface PafiBackendConfig {\n /**\n * PAFI Backend API base URL. Example:\n * https://api.pacificfinance.org\n * https://staging-api.pacificfinance.org\n */\n url: string;\n /** PAFI-assigned issuer ID (e.g., \"gg56\"). Sent in X-Issuer-Id header. */\n issuerId: string;\n /** Per-issuer API key (or JWT) for the Authorization header. */\n apiKey: string;\n /** Optional fetch override for tests. */\n fetchImpl?: typeof fetch;\n /**\n * Timeout (ms) for each request. Default: 10_000. PAFI Backend should\n * respond in <1s for the happy path; this is just the sanity bound.\n */\n timeoutMs?: number;\n /**\n * Retry policy for transient failures (5xx, 429, timeouts, network).\n * Omit or pass `{ maxAttempts: 1 }` to disable retry entirely.\n */\n retry?: RetryConfig;\n}\n\n/** Paired with `POST /paymaster/sponsor`. See SPONSORED_PATH_FLOW.md §4.1 */\nexport interface SponsorshipRequest {\n chainId: number;\n scenario: SponsorshipScenario;\n userOp: PartialUserOperation;\n target: {\n /** The allowlisted contract this batch call targets. */\n contract: Address;\n /** Function selector / name — validated against allowlist. */\n function: string;\n /** The PointToken involved (for scenario context). */\n pointToken?: Address;\n };\n}\n\nexport interface SponsorshipResponse {\n paymaster: Address;\n paymasterData: Hex;\n paymasterVerificationGasLimit: bigint;\n paymasterPostOpGasLimit: bigint;\n /** Unix seconds when this sponsorship expires. Re-request after. */\n expiresAt: number;\n}\n\n/**\n * Machine-readable error codes returned by PAFI Backend.\n *\n * Source of truth: `apps/paymaster-proxy` `CalldataValidationError`,\n * `RateLimitError`, `CoinbaseClientError`. Keep in sync.\n */\nexport type PafiBackendErrorCode =\n // Auth (paymaster-proxy guard)\n | \"MISSING_ISSUER_ID\"\n | \"MISSING_API_KEY\"\n | \"ISSUER_UNAUTHORIZED\"\n // Calldata validation\n | \"CALLDATA_INVALID\"\n | \"CALLDATA_EMPTY_BATCH\"\n | \"TARGET_NOT_ALLOWLISTED\"\n | \"FUNCTION_NOT_ALLOWED\"\n | \"SCENARIO_MISMATCH\"\n // Legacy alias — kept for back-compat with 0.2.x consumers\n | \"SCENARIO_DISABLED\"\n // Rate limit\n | \"RATE_LIMIT_EXCEEDED\"\n | \"RATE_LIMIT_EXCEEDED_DAILY\"\n | \"RATE_LIMIT_EXCEEDED_PER_USER\"\n | \"RATE_LIMITER_UNAVAILABLE\"\n // Coinbase upstream\n | \"PAYMASTER_REJECTED\"\n | \"PAYMASTER_UNAVAILABLE\"\n | \"PAYMASTER_TIMEOUT\"\n // Client-side failures\n | \"BAD_REQUEST\"\n | \"INTERNAL_ERROR\"\n | \"TIMEOUT\"\n | \"NETWORK_ERROR\";\n\nexport class PafiBackendError extends Error {\n /**\n * Seconds to wait before retry. Populated from the server body\n * (e.g. rate limit returns the number of seconds until UTC midnight).\n */\n public readonly retryAfter?: number;\n /**\n * `safeToRetry` as reported by the server body. Prefer this over the\n * code-based heuristic when available — the server knows more about\n * whether the same request will succeed on retry.\n */\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 /**\n * Whether the caller can safely retry the same request.\n *\n * If the server provided `safeToRetry` in the body, trust that.\n * Otherwise fall back to a code-based heuristic.\n */\n get safeToRetry(): boolean {\n if (this.serverSafeToRetry !== undefined) return this.serverSafeToRetry;\n switch (this.code) {\n case \"PAYMASTER_UNAVAILABLE\":\n case \"PAYMASTER_TIMEOUT\":\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 return true; // after retryAfter\n default:\n return false;\n }\n }\n}\n","import {\n PafiBackendError,\n type PafiBackendConfig,\n type PafiBackendErrorCode,\n type RetryConfig,\n type SponsorshipRequest,\n type SponsorshipResponse,\n} from \"./types\";\n\nconst DEFAULT_TIMEOUT_MS = 10_000;\n\nconst RETRY_DEFAULTS: Required<RetryConfig> = {\n maxAttempts: 1,\n initialDelayMs: 500,\n maxDelayMs: 10_000,\n maxRetryAfterMs: 30_000,\n};\n\n/**\n * HTTP client for the PAFI Backend paymaster proxy service. See\n * [SPONSORED_PATH_FLOW.md] for the full flow + API contract.\n *\n * This client sits between `@pafi/issuer`'s RelayService and the\n * PAFI Backend. It does NOT talk to Coinbase Paymaster directly —\n * PAFI Backend holds that integration.\n */\nexport class PafiBackendClient {\n private readonly url: string;\n private readonly issuerId: string;\n private readonly apiKey: string;\n private readonly fetchImpl: typeof fetch;\n private readonly timeoutMs: number;\n private readonly retry: Required<RetryConfig>;\n\n constructor(config: PafiBackendConfig) {\n if (!config.url) {\n throw new Error(\"PafiBackendClient: url is required\");\n }\n if (!config.issuerId) {\n throw new Error(\"PafiBackendClient: issuerId is required\");\n }\n if (!config.apiKey) {\n throw new Error(\"PafiBackendClient: apiKey is required\");\n }\n this.url = config.url.replace(/\\/+$/, \"\"); // strip trailing slash\n this.issuerId = config.issuerId;\n this.apiKey = config.apiKey;\n this.fetchImpl = config.fetchImpl ?? globalThis.fetch;\n this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.retry = { ...RETRY_DEFAULTS, ...(config.retry ?? {}) };\n\n if (!this.fetchImpl) {\n throw new Error(\n \"PafiBackendClient: no fetch implementation available — pass `fetchImpl` or run on Node 18+\",\n );\n }\n if (this.retry.maxAttempts < 1) {\n throw new Error(\"PafiBackendClient: retry.maxAttempts must be >= 1\");\n }\n }\n\n /**\n * Request paymaster sponsorship for a pre-built UserOperation.\n * See [SPONSORED_PATH_FLOW.md §4.1] for the API contract.\n *\n * Retries automatically on transient failures (5xx, timeouts, network\n * errors, and errors the server flags with `safeToRetry: true`) up to\n * `retry.maxAttempts`. 4xx errors that are not `safeToRetry` fail fast.\n *\n * @throws PafiBackendError on final failure after exhausting retries\n */\n async requestSponsorship(\n req: SponsorshipRequest,\n ): Promise<SponsorshipResponse> {\n return this.postWithRetry<SponsorshipResponse, SponsorshipRequest>(\n \"/paymaster/sponsor\",\n req,\n );\n }\n\n // -------------------------------------------------------------------------\n // Internals\n // -------------------------------------------------------------------------\n\n private async postWithRetry<TRes, TReq>(\n path: string,\n body: TReq,\n ): Promise<TRes> {\n let lastError: PafiBackendError | undefined;\n\n for (let attempt = 1; attempt <= this.retry.maxAttempts; attempt++) {\n try {\n return await this.post<TRes, TReq>(path, body);\n } catch (err) {\n if (!(err instanceof PafiBackendError)) throw err;\n lastError = err;\n\n const isLastAttempt = attempt >= this.retry.maxAttempts;\n if (isLastAttempt || !err.safeToRetry) throw err;\n\n const delay = this.computeBackoff(attempt, err.retryAfter);\n if (delay === null) throw err; // server asked to wait longer than cap\n await this.sleep(delay);\n }\n }\n\n // Unreachable — loop either returns or throws. Keeps TS happy.\n throw lastError!;\n }\n\n /**\n * Pick the delay before the next retry.\n * - If the server sent `retryAfter` (seconds), honor it (capped by\n * `maxRetryAfterMs`) — returns null if the server wait exceeds the\n * cap, signalling the caller should give up.\n * - Otherwise: exponential backoff with ±20% jitter, capped at\n * `maxDelayMs`.\n */\n private computeBackoff(attempt: number, retryAfter?: number): number | null {\n if (retryAfter !== undefined) {\n const serverMs = retryAfter * 1000;\n if (serverMs > this.retry.maxRetryAfterMs) return null;\n return serverMs;\n }\n const exp = this.retry.initialDelayMs * 2 ** (attempt - 1);\n const capped = Math.min(exp, this.retry.maxDelayMs);\n const jitter = capped * (0.8 + Math.random() * 0.4); // ±20%\n return Math.round(jitter);\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n private async post<TRes, TReq>(path: string, body: TReq): Promise<TRes> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);\n\n let response: Response;\n try {\n response = await this.fetchImpl(`${this.url}${path}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${this.apiKey}`,\n \"X-Issuer-Id\": this.issuerId,\n },\n body: JSON.stringify(body, this.bigintReplacer),\n signal: controller.signal,\n });\n } catch (err) {\n if ((err as Error).name === \"AbortError\") {\n throw new PafiBackendError(\n \"TIMEOUT\",\n `PAFI Backend request timed out after ${this.timeoutMs}ms`,\n 0,\n );\n }\n throw new PafiBackendError(\n \"NETWORK_ERROR\",\n `PAFI Backend unreachable: ${(err as Error).message}`,\n 0,\n );\n } finally {\n clearTimeout(timeoutId);\n }\n\n const text = await response.text();\n\n if (!response.ok) {\n // Try to parse structured error; fall back to raw text.\n let code: PafiBackendErrorCode = \"INTERNAL_ERROR\";\n let message = text || response.statusText;\n let details: unknown;\n let retryAfter: number | undefined;\n let serverSafeToRetry: boolean | undefined;\n try {\n const parsed = JSON.parse(text);\n code = (parsed.code as PafiBackendErrorCode) ?? code;\n message = parsed.message ?? message;\n details = parsed.details;\n if (typeof parsed.retryAfter === \"number\") retryAfter = parsed.retryAfter;\n if (typeof parsed.safeToRetry === \"boolean\") serverSafeToRetry = parsed.safeToRetry;\n } catch {\n // body wasn't JSON — keep raw text\n }\n throw new PafiBackendError(code, message, response.status, details, {\n ...(retryAfter !== undefined ? { retryAfter } : {}),\n ...(serverSafeToRetry !== undefined ? { safeToRetry: serverSafeToRetry } : {}),\n });\n }\n\n // BigInt revival — PAFI Backend returns gas limits as decimal strings\n // to avoid JS precision loss. Parse and coerce on the way in.\n return JSON.parse(text, this.bigintReviver) as TRes;\n }\n\n /** JSON replacer that stringifies bigints. Paired with bigintReviver. */\n private bigintReplacer = (_key: string, value: unknown): unknown => {\n return typeof value === \"bigint\" ? value.toString() : value;\n };\n\n /**\n * JSON reviver that coerces specific numeric-string fields back to\n * bigint. The server must send these fields as decimal strings.\n */\n private bigintReviver = (key: string, value: unknown): unknown => {\n if (\n typeof value === \"string\" &&\n (key.endsWith(\"GasLimit\") ||\n key === \"nonce\" ||\n key === \"callGasLimit\" ||\n key === \"verificationGasLimit\" ||\n key === \"preVerificationGas\" ||\n key === \"maxFeePerGas\" ||\n key === \"maxPriorityFeePerGas\" ||\n key === \"paymasterVerificationGasLimit\" ||\n key === \"paymasterPostOpGasLimit\") &&\n /^\\d+$/.test(value)\n ) {\n return BigInt(value);\n }\n return value;\n };\n}\n","import { getAddress } from \"viem\";\nimport type { Address, PublicClient } from \"viem\";\nimport { MemoryPointLedger } from \"./ledger/memoryLedger\";\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 * Full contract address bundle returned verbatim by `handleConfig` so\n * the frontend SDK can pick them up.\n */\n contracts: ApiConfigResponse[\"contracts\"];\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 // -- Pluggable storage (optional — default = in-memory) ----------------\n\n ledger?: IPointLedger;\n policy?: IPolicyEngine;\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 // -- 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: AuthService;\n sessionStore: ISessionStore;\n ledger: IPointLedger;\n policy: IPolicyEngine;\n relayService: RelayService;\n feeManager: 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 handlers: 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 * - `ledger` → `MemoryPointLedger`\n * - `sessionStore` → `MemorySessionStore`\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.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 ?? new MemoryPointLedger();\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 handlersConfig: ConstructorParameters<typeof IssuerApiHandlers>[0] = {\n authService,\n ledger,\n provider: config.provider,\n pointTokenAddresses: tokenAddresses,\n chainId: config.chainId,\n contracts: config.contracts,\n };\n if (feeManager) handlersConfig.feeManager = feeManager;\n if (config.poolsProvider) handlersConfig.poolsProvider = config.poolsProvider;\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 authService,\n sessionStore,\n ledger,\n policy,\n relayService,\n feeManager,\n indexers,\n indexer: firstIndexer,\n handlers,\n };\n}\n"],"mappings":"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","names":["import_viem","import_viem","import_node_crypto","import_viem","import_core","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","DEFAULT_CACHE_TTL_MS","import_core","import_viem"]}
|