@pafi-dev/issuer 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +294 -0
- package/dist/index.cjs +1759 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1303 -0
- package/dist/index.d.ts +1303 -0
- package/dist/index.js +1728 -0
- package/dist/index.js.map +1 -0
- package/package.json +44 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,1303 @@
|
|
|
1
|
+
import { Address, Hex, PublicClient, Chain } from 'viem';
|
|
2
|
+
import { PointTokenDomainConfig, MintRequest, EIP712Signature, MintParams, SwapParams, ReceiverConsent, PathKey, PoolKey } from '@pafi-dev/core';
|
|
3
|
+
export { encodeExtData } from '@pafi-dev/core';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Lifecycle of a minting request as tracked by the issuer's point ledger.
|
|
7
|
+
*
|
|
8
|
+
* PENDING ── on-chain mint confirmed ──▶ MINTED
|
|
9
|
+
* │
|
|
10
|
+
* ├── deadline elapsed without mint ─▶ EXPIRED
|
|
11
|
+
* │
|
|
12
|
+
* └── tx reverted / cancelled ──────▶ FAILED
|
|
13
|
+
*/
|
|
14
|
+
type MintingStatus = "PENDING" | "MINTED" | "EXPIRED" | "FAILED";
|
|
15
|
+
/** A locked-amount entry tracking an in-flight mint request. */
|
|
16
|
+
interface LockedMintRequest {
|
|
17
|
+
/** Opaque lock id (used to release / update later) */
|
|
18
|
+
lockId: string;
|
|
19
|
+
userAddress: Address;
|
|
20
|
+
amount: bigint;
|
|
21
|
+
/** Lifecycle status */
|
|
22
|
+
status: MintingStatus;
|
|
23
|
+
/** When the lock was created (epoch ms) */
|
|
24
|
+
createdAt: number;
|
|
25
|
+
/** When the lock auto-expires if not resolved (epoch ms) */
|
|
26
|
+
expiresAt: number;
|
|
27
|
+
/** On-chain transaction hash, set once the mint is confirmed */
|
|
28
|
+
txHash?: Hex;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Issuer point ledger interface — the source of truth for off-chain user
|
|
32
|
+
* point balances and in-flight minting requests.
|
|
33
|
+
*
|
|
34
|
+
* Issuers replace the in-memory default with their own database-backed
|
|
35
|
+
* implementation (Postgres, Redis, etc.).
|
|
36
|
+
*/
|
|
37
|
+
interface IPointLedger {
|
|
38
|
+
/** Get a user's available off-chain point balance (excluding locked). */
|
|
39
|
+
getBalance(userAddress: Address): Promise<bigint>;
|
|
40
|
+
/**
|
|
41
|
+
* Lock an amount for a pending mint request. Locked amounts are reserved
|
|
42
|
+
* but not yet deducted; they protect against double-spend during the
|
|
43
|
+
* EIP-712 validity window.
|
|
44
|
+
*
|
|
45
|
+
* @param lockDurationMs how long the lock should be held before auto-expiry
|
|
46
|
+
* @returns lockId — opaque handle used by `releaseLock` / `updateMintStatus`
|
|
47
|
+
* @throws if the user's available balance is below `amount`
|
|
48
|
+
*/
|
|
49
|
+
lockForMinting(userAddress: Address, amount: bigint, lockDurationMs: number): Promise<string>;
|
|
50
|
+
/** Release a previously created lock (e.g. on tx failure / cancel). */
|
|
51
|
+
releaseLock(lockId: string): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Permanently deduct an amount from a user's balance after the on-chain
|
|
54
|
+
* mint has been observed by the indexer. Should also resolve any matching
|
|
55
|
+
* lock so the funds aren't double-counted.
|
|
56
|
+
*/
|
|
57
|
+
deductBalance(userAddress: Address, amount: bigint, txHash: Hex): Promise<void>;
|
|
58
|
+
/** Credit points to a user's balance (e.g. from merchant activity). */
|
|
59
|
+
creditBalance(userAddress: Address, amount: bigint, reason: string): Promise<void>;
|
|
60
|
+
/** List currently-pending locked mint requests for a user. */
|
|
61
|
+
getLockedRequests(userAddress: Address): Promise<LockedMintRequest[]>;
|
|
62
|
+
/**
|
|
63
|
+
* Transition a lock to a new lifecycle status. The on-chain tx hash is
|
|
64
|
+
* supplied when the status is `MINTED`.
|
|
65
|
+
*/
|
|
66
|
+
updateMintStatus(lockId: string, status: MintingStatus, txHash?: Hex): Promise<void>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* In-memory IPointLedger implementation for development and tests.
|
|
71
|
+
*
|
|
72
|
+
* NOT for production — state is lost on restart. Issuers should ship their
|
|
73
|
+
* own database-backed implementation.
|
|
74
|
+
*
|
|
75
|
+
* Concurrency model: single-process, single-threaded (Node.js event loop).
|
|
76
|
+
* The lock check + insert is atomic within a tick because no awaits sit
|
|
77
|
+
* between balance read and lock write.
|
|
78
|
+
*/
|
|
79
|
+
declare class MemoryPointLedger implements IPointLedger {
|
|
80
|
+
private balances;
|
|
81
|
+
private locks;
|
|
82
|
+
private nextLockId;
|
|
83
|
+
private now;
|
|
84
|
+
constructor(opts?: {
|
|
85
|
+
now?: () => number;
|
|
86
|
+
});
|
|
87
|
+
getBalance(userAddress: Address): Promise<bigint>;
|
|
88
|
+
getLockedRequests(userAddress: Address): Promise<LockedMintRequest[]>;
|
|
89
|
+
creditBalance(userAddress: Address, amount: bigint, _reason: string): Promise<void>;
|
|
90
|
+
lockForMinting(userAddress: Address, amount: bigint, lockDurationMs: number): Promise<string>;
|
|
91
|
+
releaseLock(lockId: string): Promise<void>;
|
|
92
|
+
deductBalance(userAddress: Address, amount: bigint, txHash: Hex): Promise<void>;
|
|
93
|
+
updateMintStatus(lockId: string, status: MintingStatus, txHash?: Hex): Promise<void>;
|
|
94
|
+
/**
|
|
95
|
+
* Auto-expire any PENDING lock past its expiry. Called lazily on every
|
|
96
|
+
* read/write so the in-memory state stays self-cleaning without a timer.
|
|
97
|
+
*/
|
|
98
|
+
private purgeExpired;
|
|
99
|
+
private lockedTotalFor;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Input to a policy evaluation. Policy engines use this to decide whether
|
|
104
|
+
* a user's mint request should be approved.
|
|
105
|
+
*/
|
|
106
|
+
interface PolicyEvalRequest {
|
|
107
|
+
userAddress: Address;
|
|
108
|
+
amount: bigint;
|
|
109
|
+
pointTokenAddress: Address;
|
|
110
|
+
chainId: number;
|
|
111
|
+
}
|
|
112
|
+
/** Outcome of a policy evaluation. */
|
|
113
|
+
interface PolicyDecision {
|
|
114
|
+
approved: boolean;
|
|
115
|
+
/**
|
|
116
|
+
* Human-readable reason for rejection (empty on approval). Callers surface
|
|
117
|
+
* this back to the user, so keep it short and non-leaky.
|
|
118
|
+
*/
|
|
119
|
+
reason?: string;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Policy engine — evaluates whether a minting request passes issuer rules.
|
|
123
|
+
* Issuers extend the default implementation to add KYC, volume caps, claim
|
|
124
|
+
* budgets, etc.
|
|
125
|
+
*/
|
|
126
|
+
interface IPolicyEngine {
|
|
127
|
+
evaluate(request: PolicyEvalRequest): Promise<PolicyDecision>;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Options for constructing a DefaultPolicyEngine.
|
|
132
|
+
*
|
|
133
|
+
* `mintingOracleAddress` and `provider` are optional — if omitted, the
|
|
134
|
+
* engine skips the on-chain cap check (useful for dev/testing).
|
|
135
|
+
*
|
|
136
|
+
* `verifyMintCap` is injectable so tests can simulate cap rejections
|
|
137
|
+
* without needing a real contract.
|
|
138
|
+
*/
|
|
139
|
+
interface DefaultPolicyEngineOptions {
|
|
140
|
+
ledger: IPointLedger;
|
|
141
|
+
provider?: PublicClient;
|
|
142
|
+
mintingOracleAddress?: Address;
|
|
143
|
+
/**
|
|
144
|
+
* Override the on-chain cap check. Defaults to `@pafi/core`'s
|
|
145
|
+
* `verifyMintCap`, which reverts if the issuer's declared supply would
|
|
146
|
+
* be exceeded. A rejected check should throw; returning normally is pass.
|
|
147
|
+
*/
|
|
148
|
+
verifyMintCap?: (client: PublicClient, oracle: Address, issuer: Address, amount: bigint) => Promise<void>;
|
|
149
|
+
/**
|
|
150
|
+
* Resolve a point-token address to the issuer address for the cap check.
|
|
151
|
+
* Required iff `mintingOracleAddress + provider` are supplied.
|
|
152
|
+
*/
|
|
153
|
+
resolveIssuer?: (pointToken: Address) => Promise<Address>;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Default policy engine — performs two checks:
|
|
157
|
+
*
|
|
158
|
+
* 1. **Off-chain balance** — the user must have at least `amount` of
|
|
159
|
+
* unlocked point balance in the issuer ledger.
|
|
160
|
+
* 2. **On-chain cap** — (optional) calls the MintingOracle via
|
|
161
|
+
* `verifyMintCap` to confirm that minting this amount would not exceed
|
|
162
|
+
* the issuer's declared total supply.
|
|
163
|
+
*
|
|
164
|
+
* Issuers extend this class (or implement `IPolicyEngine` directly) to add
|
|
165
|
+
* KYC, volume caps, claim budgets, or anti-abuse rules.
|
|
166
|
+
*/
|
|
167
|
+
declare class DefaultPolicyEngine implements IPolicyEngine {
|
|
168
|
+
private readonly ledger;
|
|
169
|
+
private readonly provider?;
|
|
170
|
+
private readonly mintingOracleAddress?;
|
|
171
|
+
private readonly verifyMintCap?;
|
|
172
|
+
private readonly resolveIssuer?;
|
|
173
|
+
constructor(opts: DefaultPolicyEngineOptions);
|
|
174
|
+
evaluate(request: PolicyEvalRequest): Promise<PolicyDecision>;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Issuer signer — produces the MintRequest EIP-712 signature that the Relay
|
|
179
|
+
* contract verifies against the issuer's on-chain authorized minter.
|
|
180
|
+
*
|
|
181
|
+
* This is a trust boundary: the default `PrivateKeySigner` holds the key in
|
|
182
|
+
* process memory and is intended for local development only. Production
|
|
183
|
+
* issuers replace this with an HSM/KMS/MPC integration.
|
|
184
|
+
*/
|
|
185
|
+
interface IIssuerSigner {
|
|
186
|
+
/**
|
|
187
|
+
* Sign a `MintRequest` message against the point token's EIP-712 domain.
|
|
188
|
+
* The returned signature is what the Relay contract passes to
|
|
189
|
+
* `PointToken.verify` during `mintAndSwap`.
|
|
190
|
+
*/
|
|
191
|
+
signMintRequest(domain: PointTokenDomainConfig, message: MintRequest): Promise<EIP712Signature>;
|
|
192
|
+
/** Get the signer's on-chain address (used for verification / logging). */
|
|
193
|
+
getAddress(): Promise<Address>;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
interface PrivateKeySignerOptions {
|
|
197
|
+
/** 0x-prefixed 32-byte hex private key */
|
|
198
|
+
privateKey: Hex;
|
|
199
|
+
/**
|
|
200
|
+
* Chain metadata for the viem WalletClient. Only the chain id is actually
|
|
201
|
+
* used for signing; a minimal stub is acceptable (it does not need to
|
|
202
|
+
* match the deployed chain config beyond id).
|
|
203
|
+
*/
|
|
204
|
+
chain: Chain;
|
|
205
|
+
/**
|
|
206
|
+
* Optional RPC URL. `signTypedData` is offline, so this can usually be
|
|
207
|
+
* left unset — viem only requires a transport to construct the client.
|
|
208
|
+
*/
|
|
209
|
+
rpcUrl?: string;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Local-key implementation of `IIssuerSigner`. Wraps viem's `signTypedData`
|
|
213
|
+
* via the shared `@pafi/core` `signMintRequest` helper.
|
|
214
|
+
*
|
|
215
|
+
* ⚠️ **NOT for production use.** The private key lives in process memory
|
|
216
|
+
* and is trivially extractable from a compromised host. Replace with an
|
|
217
|
+
* HSM/KMS/MPC-backed `IIssuerSigner` before deployment.
|
|
218
|
+
*/
|
|
219
|
+
declare class PrivateKeySigner implements IIssuerSigner {
|
|
220
|
+
private readonly account;
|
|
221
|
+
private readonly walletClient;
|
|
222
|
+
constructor(opts: PrivateKeySignerOptions);
|
|
223
|
+
signMintRequest(domain: PointTokenDomainConfig, message: MintRequest): Promise<EIP712Signature>;
|
|
224
|
+
getAddress(): Promise<Address>;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* A server-issued session created after a successful wallet login. The
|
|
229
|
+
* token id is embedded in the JWT so sessions can be revoked without
|
|
230
|
+
* invalidating the JWT signing key.
|
|
231
|
+
*/
|
|
232
|
+
interface Session {
|
|
233
|
+
tokenId: string;
|
|
234
|
+
userAddress: Address;
|
|
235
|
+
chainId: number;
|
|
236
|
+
issuedAt: Date;
|
|
237
|
+
expiresAt: Date;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Backend store for login nonces and active sessions.
|
|
241
|
+
*
|
|
242
|
+
* The default in-memory implementation is fine for local development but
|
|
243
|
+
* production issuers should plug in Redis / a SQL table so sessions
|
|
244
|
+
* survive restarts and are visible across replicas.
|
|
245
|
+
*/
|
|
246
|
+
interface ISessionStore {
|
|
247
|
+
/**
|
|
248
|
+
* Create a single-use login nonce and return it to the caller. Consumers
|
|
249
|
+
* must call {@link consumeNonce} exactly once when the client returns a
|
|
250
|
+
* signed login message quoting this nonce.
|
|
251
|
+
*/
|
|
252
|
+
createNonce(): Promise<string>;
|
|
253
|
+
/**
|
|
254
|
+
* Atomically validate + consume a login nonce. Returns `true` if the
|
|
255
|
+
* nonce was known and unexpired (and is now removed); `false` otherwise.
|
|
256
|
+
*/
|
|
257
|
+
consumeNonce(nonce: string): Promise<boolean>;
|
|
258
|
+
/** Persist a newly issued session. */
|
|
259
|
+
createSession(session: Session): Promise<void>;
|
|
260
|
+
/** Look up a session by token id, or `null` if unknown / revoked / expired. */
|
|
261
|
+
getSession(tokenId: string): Promise<Session | null>;
|
|
262
|
+
/** Revoke a session by token id (logout). No-op if not found. */
|
|
263
|
+
revokeSession(tokenId: string): Promise<void>;
|
|
264
|
+
/**
|
|
265
|
+
* Revoke every session for a user address. Used when an issuer needs to
|
|
266
|
+
* force re-login (e.g. after a permissions change or suspected abuse).
|
|
267
|
+
*/
|
|
268
|
+
revokeAllSessions(userAddress: Address): Promise<void>;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
interface MemorySessionStoreOptions {
|
|
272
|
+
/**
|
|
273
|
+
* How long a login nonce is valid before auto-expiry. Defaults to 5 min,
|
|
274
|
+
* matching typical SIWE recommendations.
|
|
275
|
+
*/
|
|
276
|
+
nonceTtlMs?: number;
|
|
277
|
+
/** Clock override for tests. */
|
|
278
|
+
now?: () => number;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* In-memory `ISessionStore` — Map-backed nonces and sessions with lazy
|
|
282
|
+
* expiry sweeps on every read/write.
|
|
283
|
+
*
|
|
284
|
+
* Not safe across processes or restarts; intended for dev/test only.
|
|
285
|
+
*/
|
|
286
|
+
declare class MemorySessionStore implements ISessionStore {
|
|
287
|
+
private nonces;
|
|
288
|
+
private sessions;
|
|
289
|
+
private readonly nonceTtlMs;
|
|
290
|
+
private readonly now;
|
|
291
|
+
constructor(opts?: MemorySessionStoreOptions);
|
|
292
|
+
createNonce(): Promise<string>;
|
|
293
|
+
consumeNonce(nonce: string): Promise<boolean>;
|
|
294
|
+
createSession(session: Session): Promise<void>;
|
|
295
|
+
getSession(tokenId: string): Promise<Session | null>;
|
|
296
|
+
revokeSession(tokenId: string): Promise<void>;
|
|
297
|
+
revokeAllSessions(userAddress: Address): Promise<void>;
|
|
298
|
+
private purgeExpiredNonces;
|
|
299
|
+
private purgeExpiredSessions;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Thin wrapper around an `ISessionStore` exposing nonce generation and
|
|
304
|
+
* single-use consumption as its own named surface. AuthService uses it
|
|
305
|
+
* internally, and issuers can also use it directly for other flows that
|
|
306
|
+
* need replay protection (e.g. a CSRF-style challenge).
|
|
307
|
+
*/
|
|
308
|
+
declare class NonceManager {
|
|
309
|
+
private readonly store;
|
|
310
|
+
constructor(store: ISessionStore);
|
|
311
|
+
/** Generate a fresh login nonce. The store is responsible for TTL. */
|
|
312
|
+
generate(): Promise<string>;
|
|
313
|
+
/**
|
|
314
|
+
* Atomically validate + consume a nonce. Returns `true` iff the nonce
|
|
315
|
+
* was known and unexpired (and is now removed from the store).
|
|
316
|
+
*/
|
|
317
|
+
consume(nonce: string): Promise<boolean>;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
interface AuthServiceConfig {
|
|
321
|
+
sessionStore: ISessionStore;
|
|
322
|
+
/** HMAC secret for signing JWTs (HS256). At least 32 bytes recommended. */
|
|
323
|
+
jwtSecret: string;
|
|
324
|
+
/**
|
|
325
|
+
* JWT lifetime passed to `jose`. Accepts any of `jose`'s time strings
|
|
326
|
+
* (`"24h"`, `"7d"`, `"45m"`, …). Defaults to `"24h"`.
|
|
327
|
+
*/
|
|
328
|
+
jwtExpiresIn?: string;
|
|
329
|
+
/**
|
|
330
|
+
* Expected domain in the login message, e.g. `"app.example.com"`. The
|
|
331
|
+
* SIWE spec requires the frontend to build the message with this exact
|
|
332
|
+
* domain; the backend rejects any mismatch.
|
|
333
|
+
*/
|
|
334
|
+
domain: string;
|
|
335
|
+
/** Expected chain id. */
|
|
336
|
+
chainId: number;
|
|
337
|
+
/** Clock override for tests. */
|
|
338
|
+
now?: () => Date;
|
|
339
|
+
}
|
|
340
|
+
interface LoginResult {
|
|
341
|
+
token: string;
|
|
342
|
+
userAddress: Address;
|
|
343
|
+
tokenId: string;
|
|
344
|
+
expiresAt: Date;
|
|
345
|
+
}
|
|
346
|
+
interface AuthContext {
|
|
347
|
+
userAddress: Address;
|
|
348
|
+
chainId: number;
|
|
349
|
+
tokenId: string;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Wallet-based authentication service implementing the EIP-4361 (Sign-In
|
|
353
|
+
* With Ethereum) login flow with server-issued JWTs.
|
|
354
|
+
*
|
|
355
|
+
* Flow:
|
|
356
|
+
* 1. `getNonce()` — client fetches a nonce, stores it locally
|
|
357
|
+
* 2. Client constructs a login message with the nonce + signs with wallet
|
|
358
|
+
* 3. `login(message, signature)` — server validates fields + signature,
|
|
359
|
+
* consumes the nonce, persists a session, returns a JWT
|
|
360
|
+
* 4. Protected endpoints call `verifyToken(token)` to resolve the user
|
|
361
|
+
* context; the session is re-checked on every call, so `logout()`
|
|
362
|
+
* revokes access immediately.
|
|
363
|
+
*/
|
|
364
|
+
declare class AuthService {
|
|
365
|
+
private readonly sessionStore;
|
|
366
|
+
private readonly jwtSecret;
|
|
367
|
+
private readonly jwtExpiresIn;
|
|
368
|
+
private readonly domain;
|
|
369
|
+
private readonly chainId;
|
|
370
|
+
private readonly nonceManager;
|
|
371
|
+
private readonly now;
|
|
372
|
+
constructor(config: AuthServiceConfig);
|
|
373
|
+
/** Generate a fresh login nonce. */
|
|
374
|
+
getNonce(): Promise<string>;
|
|
375
|
+
/**
|
|
376
|
+
* Verify a signed login message and issue a JWT on success.
|
|
377
|
+
* Throws an `AuthError` on any validation failure.
|
|
378
|
+
*/
|
|
379
|
+
login(message: string, signature: Hex): Promise<LoginResult>;
|
|
380
|
+
/** Revoke the session backing the given JWT (logout). */
|
|
381
|
+
logout(token: string): Promise<void>;
|
|
382
|
+
/**
|
|
383
|
+
* Verify a JWT and return the authenticated user context. Throws an
|
|
384
|
+
* `AuthError` if the token is missing, malformed, expired, revoked, or
|
|
385
|
+
* signed by a different key.
|
|
386
|
+
*/
|
|
387
|
+
verifyToken(token: string): Promise<AuthContext>;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Extracts a Bearer token from an `Authorization` header value and verifies
|
|
392
|
+
* it via the supplied `AuthService`. Framework-agnostic — issuers wrap this
|
|
393
|
+
* in their framework's middleware pattern (Express, Fastify, Hono, …).
|
|
394
|
+
*
|
|
395
|
+
* Throws an `AuthError` if the header is missing, malformed, or the token
|
|
396
|
+
* fails verification. Callers should translate the `err.code` into an
|
|
397
|
+
* appropriate HTTP status (401 for auth failures, 500 for anything else).
|
|
398
|
+
*/
|
|
399
|
+
declare function authenticateRequest(authHeader: string | undefined, authService: AuthService): Promise<AuthContext>;
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Error codes surfaced by the auth subsystem. Issuers can pattern-match on
|
|
403
|
+
* `err.code` to translate into framework-specific HTTP responses.
|
|
404
|
+
*/
|
|
405
|
+
type AuthErrorCode = "INVALID_MESSAGE" | "DOMAIN_MISMATCH" | "CHAIN_MISMATCH" | "NONCE_INVALID" | "MESSAGE_EXPIRED" | "MESSAGE_NOT_YET_VALID" | "SIGNATURE_INVALID" | "MISSING_TOKEN" | "MALFORMED_TOKEN" | "TOKEN_INVALID" | "TOKEN_EXPIRED" | "SESSION_REVOKED";
|
|
406
|
+
declare class AuthError extends Error {
|
|
407
|
+
readonly code: AuthErrorCode;
|
|
408
|
+
constructor(code: AuthErrorCode, message: string);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Parameters for a single `mintAndSwap` relay submission. This is the
|
|
413
|
+
* exact shape the Relay contract expects, with the two structs the
|
|
414
|
+
* `@pafi/core` calldata helpers build. The RelayService wires these
|
|
415
|
+
* through viem's `writeContract`.
|
|
416
|
+
*/
|
|
417
|
+
interface SubmitMintAndSwapParams {
|
|
418
|
+
mint: MintParams;
|
|
419
|
+
swap: SwapParams;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Result of a relay submission. `txHash` is returned immediately after
|
|
423
|
+
* the tx is broadcast; `receipt` is present only when the caller opted to
|
|
424
|
+
* wait for confirmation.
|
|
425
|
+
*/
|
|
426
|
+
interface RelayResult {
|
|
427
|
+
txHash: Hex;
|
|
428
|
+
blockNumber?: bigint;
|
|
429
|
+
gasUsed?: bigint;
|
|
430
|
+
status?: "success" | "reverted";
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Errors raised by RelayService carry a `code` so the MintingGateway (or
|
|
434
|
+
* any caller) can decide whether to release the ledger lock.
|
|
435
|
+
*/
|
|
436
|
+
type RelayErrorCode = "NOT_CONFIGURED" | "ENCODE_FAILED" | "SIMULATION_FAILED" | "SUBMIT_FAILED" | "TX_REVERTED" | "TIMEOUT";
|
|
437
|
+
declare class RelayError extends Error {
|
|
438
|
+
readonly code: RelayErrorCode;
|
|
439
|
+
readonly cause?: unknown;
|
|
440
|
+
constructor(code: RelayErrorCode, message: string, cause?: unknown);
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Interface an operator wallet must satisfy for the RelayService. Matches
|
|
444
|
+
* the subset of viem's `WalletClient` we actually call, so tests can pass
|
|
445
|
+
* a minimal mock instead of a full client.
|
|
446
|
+
*/
|
|
447
|
+
interface OperatorWalletLike {
|
|
448
|
+
writeContract: (args: {
|
|
449
|
+
address: Address;
|
|
450
|
+
abi: readonly unknown[];
|
|
451
|
+
functionName: string;
|
|
452
|
+
args: readonly unknown[];
|
|
453
|
+
account?: {
|
|
454
|
+
address: Address;
|
|
455
|
+
};
|
|
456
|
+
}) => Promise<Hex>;
|
|
457
|
+
account?: {
|
|
458
|
+
address: Address;
|
|
459
|
+
} | undefined;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
interface RelayServiceConfig {
|
|
463
|
+
/** Address of the deployed Relay contract (chain-specific). */
|
|
464
|
+
relayAddress: Address;
|
|
465
|
+
/** Operator wallet that pays gas and receives the operator fee. */
|
|
466
|
+
operatorWallet: OperatorWalletLike;
|
|
467
|
+
/**
|
|
468
|
+
* Provider used for pre-flight simulation and receipt waiting. Optional —
|
|
469
|
+
* if omitted, the service still broadcasts but returns only `txHash`
|
|
470
|
+
* (caller is responsible for confirmation tracking).
|
|
471
|
+
*/
|
|
472
|
+
provider?: PublicClient;
|
|
473
|
+
/**
|
|
474
|
+
* If a provider is supplied, wait up to this many milliseconds for a
|
|
475
|
+
* receipt before timing out. Default: 60_000 (one minute).
|
|
476
|
+
*/
|
|
477
|
+
confirmationTimeoutMs?: number;
|
|
478
|
+
/**
|
|
479
|
+
* Whether to run `simulateContract` before submitting. Catches most
|
|
480
|
+
* reverts locally without wasting gas. Default: true (when provider is
|
|
481
|
+
* supplied).
|
|
482
|
+
*/
|
|
483
|
+
simulateBeforeSubmit?: boolean;
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Submits `mintAndSwap` transactions to the Relay contract on behalf of
|
|
487
|
+
* the issuer. This is the single place the operator wallet is touched; the
|
|
488
|
+
* MintingGateway calls into `submitMintAndSwap()` after it has signed the
|
|
489
|
+
* MintRequest and verified the ReceiverConsent.
|
|
490
|
+
*
|
|
491
|
+
* The service is intentionally thin: calldata encoding stays in `@pafi/core`
|
|
492
|
+
* (`encodeMintAndSwap`), so on-chain ABI changes only ripple through one
|
|
493
|
+
* package.
|
|
494
|
+
*/
|
|
495
|
+
declare class RelayService {
|
|
496
|
+
private readonly relayAddress;
|
|
497
|
+
private readonly operatorWallet;
|
|
498
|
+
private readonly provider?;
|
|
499
|
+
private readonly confirmationTimeoutMs;
|
|
500
|
+
private readonly simulateBeforeSubmit;
|
|
501
|
+
constructor(config: RelayServiceConfig);
|
|
502
|
+
/** Address the operator wallet is broadcasting from (for logging). */
|
|
503
|
+
operatorAddress(): Address | undefined;
|
|
504
|
+
/**
|
|
505
|
+
* Build calldata for the Relay `mintAndSwap` function. Kept public so
|
|
506
|
+
* callers (e.g. the MintingGateway) can log or persist the encoded call
|
|
507
|
+
* for audit before broadcasting.
|
|
508
|
+
*/
|
|
509
|
+
encodeCall(params: SubmitMintAndSwapParams): Hex;
|
|
510
|
+
/**
|
|
511
|
+
* Submit a `mintAndSwap` transaction. Flow:
|
|
512
|
+
*
|
|
513
|
+
* 1. (optional) pre-flight simulate via provider
|
|
514
|
+
* 2. writeContract through the operator wallet
|
|
515
|
+
* 3. (optional) wait for the receipt and surface gasUsed / status
|
|
516
|
+
*
|
|
517
|
+
* Throws a typed `RelayError` on any failure so the MintingGateway can
|
|
518
|
+
* decide whether to release the ledger lock (`SUBMIT_FAILED` and
|
|
519
|
+
* `SIMULATION_FAILED` are safe to release; `TX_REVERTED` and `TIMEOUT`
|
|
520
|
+
* need manual review because the tx may still land).
|
|
521
|
+
*/
|
|
522
|
+
submitMintAndSwap(params: SubmitMintAndSwapParams): Promise<RelayResult>;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
interface FeeManagerConfig {
|
|
526
|
+
/** Provider used for gas price + balance reads. */
|
|
527
|
+
provider: PublicClient;
|
|
528
|
+
/** Operator wallet whose native balance the manager monitors. */
|
|
529
|
+
operatorWallet: OperatorWalletLike;
|
|
530
|
+
/** USDT token address on the target chain (used for rebalance swaps). */
|
|
531
|
+
usdtAddress: Address;
|
|
532
|
+
/** Wrapped-native token address (WETH on Base/Ethereum, WMATIC, etc). */
|
|
533
|
+
nativeWrappedAddress: Address;
|
|
534
|
+
/**
|
|
535
|
+
* Typical gas used by a `mintAndSwap` transaction. Default: 500_000. The
|
|
536
|
+
* manager multiplies this by current gas price to get the native cost,
|
|
537
|
+
* then converts to USDT via the injected `quoteNativeToUsdt`.
|
|
538
|
+
*/
|
|
539
|
+
mintAndSwapGasUnits?: bigint;
|
|
540
|
+
/**
|
|
541
|
+
* Safety margin applied to the gas estimate before charging the user.
|
|
542
|
+
* Expressed as a basis-point multiplier, e.g. 12_000 = 120%. Default 12_000.
|
|
543
|
+
*/
|
|
544
|
+
gasPremiumBps?: number;
|
|
545
|
+
/**
|
|
546
|
+
* Price conversion: given an amount of native token (wei), return the
|
|
547
|
+
* equivalent amount of USDT. Injected so the manager is chain-agnostic —
|
|
548
|
+
* production wires this to `@pafi/core` V4 quoting or an oracle feed.
|
|
549
|
+
*/
|
|
550
|
+
quoteNativeToUsdt: (amountNative: bigint) => Promise<bigint>;
|
|
551
|
+
/**
|
|
552
|
+
* Rebalance trigger: when the operator's native balance falls below
|
|
553
|
+
* `rebalanceThresholdWei`, `rebalanceIfNeeded()` swaps `rebalanceUsdtAmount`
|
|
554
|
+
* worth of USDT into native. Both optional — omit to disable rebalancing.
|
|
555
|
+
*/
|
|
556
|
+
rebalanceThresholdWei?: bigint;
|
|
557
|
+
rebalanceUsdtAmount?: bigint;
|
|
558
|
+
/**
|
|
559
|
+
* Actual swap executor — the manager calls this when a rebalance is
|
|
560
|
+
* triggered. Injected so the SDK does not hard-code a DEX choice; the
|
|
561
|
+
* issuer wires it to the UniversalRouter (via `@pafi/core swap/`) or
|
|
562
|
+
* whatever liquidity venue they trust. Required iff the rebalance
|
|
563
|
+
* fields above are set.
|
|
564
|
+
*/
|
|
565
|
+
swapUsdtToNative?: (amountUsdt: bigint) => Promise<void>;
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Manages the operator wallet's economics:
|
|
569
|
+
*
|
|
570
|
+
* 1. `estimateGasFee()` — how many USDT to deduct from the swap proceeds
|
|
571
|
+
* to cover the operator's gas cost for the upcoming `mintAndSwap`.
|
|
572
|
+
* 2. `rebalanceIfNeeded()` — when the operator's native balance gets
|
|
573
|
+
* low, swap some of the accumulated USDT fee back into native gas
|
|
574
|
+
* token so the operator never runs dry.
|
|
575
|
+
*
|
|
576
|
+
* Both calculations are intentionally injection-based: gas estimation and
|
|
577
|
+
* USDT→native swapping both depend on DEX state, which the SDK deliberately
|
|
578
|
+
* does not own. Issuers supply the conversion + swap functions.
|
|
579
|
+
*/
|
|
580
|
+
declare class FeeManager {
|
|
581
|
+
private readonly provider;
|
|
582
|
+
private readonly operatorWallet;
|
|
583
|
+
private readonly mintAndSwapGasUnits;
|
|
584
|
+
private readonly gasPremiumBps;
|
|
585
|
+
private readonly quoteNativeToUsdt;
|
|
586
|
+
private readonly rebalanceThresholdWei?;
|
|
587
|
+
private readonly rebalanceUsdtAmount?;
|
|
588
|
+
private readonly swapUsdtToNative?;
|
|
589
|
+
constructor(config: FeeManagerConfig);
|
|
590
|
+
/**
|
|
591
|
+
* Estimate the USDT fee the operator should charge for a single
|
|
592
|
+
* `mintAndSwap`. Computed as:
|
|
593
|
+
*
|
|
594
|
+
* nativeCost = gasUnits × gasPrice
|
|
595
|
+
* premiumNativeCost = nativeCost × premiumBps / 10_000
|
|
596
|
+
* usdtFee = quoteNativeToUsdt(premiumNativeCost)
|
|
597
|
+
*/
|
|
598
|
+
estimateGasFee(): Promise<bigint>;
|
|
599
|
+
/**
|
|
600
|
+
* Check the operator's native balance and, if it has dropped below the
|
|
601
|
+
* configured threshold, trigger a USDT→native rebalance via the injected
|
|
602
|
+
* `swapUsdtToNative` function.
|
|
603
|
+
*
|
|
604
|
+
* Returns `true` if a rebalance was performed, `false` otherwise.
|
|
605
|
+
* Silently no-ops when rebalance is not configured.
|
|
606
|
+
*/
|
|
607
|
+
rebalanceIfNeeded(): Promise<boolean>;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* End-user request for a full "burn points → receive USDT" flow. The
|
|
612
|
+
* receiver has already signed the `ReceiverConsent` EIP-712 message on
|
|
613
|
+
* the frontend; the issuer backend runs everything else.
|
|
614
|
+
*/
|
|
615
|
+
interface MintAndCashOutRequest {
|
|
616
|
+
/** Address owning the off-chain points (== receiver in the consent). */
|
|
617
|
+
userAddress: Address;
|
|
618
|
+
/** Point token contract to mint. */
|
|
619
|
+
pointTokenAddress: Address;
|
|
620
|
+
/** Chain id the relay will submit on. */
|
|
621
|
+
chainId: number;
|
|
622
|
+
/**
|
|
623
|
+
* EIP-712 domain for `pointTokenAddress`. The gateway passes this
|
|
624
|
+
* straight through to `@pafi/core` `verifyReceiverConsent` and the
|
|
625
|
+
* issuer signer. Callers typically fetch it once via
|
|
626
|
+
* `PafiSDK.getDomain()` and cache it.
|
|
627
|
+
*/
|
|
628
|
+
domain: PointTokenDomainConfig;
|
|
629
|
+
/**
|
|
630
|
+
* Receiver consent message + signature, pre-built by the frontend. The
|
|
631
|
+
* message specifies `onBehalfOf = relay contract` and
|
|
632
|
+
* `originalReceiver = userAddress`, plus the amount, nonce, deadline,
|
|
633
|
+
* and encoded extData.
|
|
634
|
+
*/
|
|
635
|
+
receiverConsent: ReceiverConsent;
|
|
636
|
+
receiverSignature: Hex;
|
|
637
|
+
/**
|
|
638
|
+
* Swap path for the USDT output. Empty array = "no swap" (rare — only
|
|
639
|
+
* useful for testing). Normally a single hop pointToken → USDT.
|
|
640
|
+
*/
|
|
641
|
+
swapPath: PathKey[];
|
|
642
|
+
/** Swap deadline (unix seconds). */
|
|
643
|
+
swapDeadline: bigint;
|
|
644
|
+
/**
|
|
645
|
+
* Lock TTL (ms) to apply to the off-chain balance. The gateway computes
|
|
646
|
+
* a safe default from `receiverConsent.deadline` if omitted.
|
|
647
|
+
*/
|
|
648
|
+
lockDurationMs?: number;
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Result returned to the caller after a successful `processMintAndCashOut`.
|
|
652
|
+
* The `lockId` is preserved so the indexer can correlate the on-chain
|
|
653
|
+
* Mint event back to the ledger row (though that correlation is typically
|
|
654
|
+
* done by `(userAddress, amount)` in the default `MemoryPointLedger`).
|
|
655
|
+
*/
|
|
656
|
+
interface MintAndCashOutResponse {
|
|
657
|
+
txHash: Hex;
|
|
658
|
+
lockId: string;
|
|
659
|
+
blockNumber?: bigint;
|
|
660
|
+
gasUsed?: bigint;
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Error codes a MintingGateway can surface. Callers (API handlers) map
|
|
664
|
+
* these to HTTP status codes. The `SAFE_TO_RETRY` field tells the caller
|
|
665
|
+
* whether the underlying lock was released — if not, the user should
|
|
666
|
+
* wait before retrying to avoid double-spend.
|
|
667
|
+
*/
|
|
668
|
+
type MintingGatewayErrorCode = "INVALID_REQUEST" | "INVALID_CONSENT_SIGNATURE" | "CONSENT_EXPIRED" | "POLICY_REJECTED" | "INSUFFICIENT_BALANCE" | "SIGNER_FAILED" | "RELAY_SIMULATION_FAILED" | "RELAY_SUBMIT_FAILED" | "RELAY_REVERTED" | "RELAY_TIMEOUT";
|
|
669
|
+
declare class MintingGatewayError extends Error {
|
|
670
|
+
readonly code: MintingGatewayErrorCode;
|
|
671
|
+
/**
|
|
672
|
+
* True if the ledger lock was released before this error was thrown,
|
|
673
|
+
* meaning the user can safely retry. False means the funds are still
|
|
674
|
+
* locked (e.g. tx may still land on-chain) and retry would double-spend.
|
|
675
|
+
*/
|
|
676
|
+
readonly safeToRetry: boolean;
|
|
677
|
+
readonly cause?: unknown;
|
|
678
|
+
constructor(code: MintingGatewayErrorCode, message: string, opts: {
|
|
679
|
+
safeToRetry: boolean;
|
|
680
|
+
cause?: unknown;
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
interface MintingGatewayConfig {
|
|
685
|
+
ledger: IPointLedger;
|
|
686
|
+
policy: IPolicyEngine;
|
|
687
|
+
signer: IIssuerSigner;
|
|
688
|
+
relayService: RelayService;
|
|
689
|
+
/**
|
|
690
|
+
* Clock override for tests. Defaults to `() => Date.now()`. Used to
|
|
691
|
+
* compute safe lock TTLs and to check consent deadlines.
|
|
692
|
+
*/
|
|
693
|
+
now?: () => number;
|
|
694
|
+
/**
|
|
695
|
+
* Extra buffer (ms) added on top of `(consent.deadline - now)` when the
|
|
696
|
+
* caller doesn't supply a `lockDurationMs`. Default: 60_000 (one minute
|
|
697
|
+
* — roughly 2× the Base L2 block confirmation time).
|
|
698
|
+
*/
|
|
699
|
+
defaultLockBufferMs?: number;
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* The MintingGateway is the central orchestrator that turns a user's
|
|
703
|
+
* signed ReceiverConsent into an on-chain `mintAndSwap` tx and a
|
|
704
|
+
* consistent off-chain ledger update.
|
|
705
|
+
*
|
|
706
|
+
* 11-step flow (per `PAFI_ISSUER_SDK_SPEC.md` §4.3):
|
|
707
|
+
*
|
|
708
|
+
* 1. Field validation (cheap rejects before any crypto)
|
|
709
|
+
* 2. Verify ReceiverConsent signature via `@pafi/core`
|
|
710
|
+
* 3. Check off-chain balance via ledger
|
|
711
|
+
* 4. Check locked requests via ledger
|
|
712
|
+
* 5. Run policy engine (balance + on-chain cap + issuer rules)
|
|
713
|
+
* 6. Lock the requested amount in the ledger
|
|
714
|
+
* 7. Sign MintRequest as issuer
|
|
715
|
+
* 8. Build MintParams + SwapParams
|
|
716
|
+
* 9. Submit via RelayService (encode + simulate + broadcast + wait)
|
|
717
|
+
* 10. Return { txHash, lockId, receipt fields }
|
|
718
|
+
* 11. On any failure, release the lock IF it's safe to retry — i.e. we
|
|
719
|
+
* know the tx cannot still land on-chain. If the tx may have made
|
|
720
|
+
* it (`TX_REVERTED` or `TIMEOUT`), we leave the lock alone and
|
|
721
|
+
* surface `safeToRetry: false` so the caller doesn't double-spend.
|
|
722
|
+
*
|
|
723
|
+
* The gateway deliberately does NOT deduct the balance here. Deduction
|
|
724
|
+
* happens in the `PointIndexer` when the on-chain Mint event is observed,
|
|
725
|
+
* which is what makes the system crash-safe: if the gateway dies between
|
|
726
|
+
* broadcast and receipt, the indexer will still finalize the ledger.
|
|
727
|
+
*/
|
|
728
|
+
declare class MintingGateway {
|
|
729
|
+
private readonly ledger;
|
|
730
|
+
private readonly policy;
|
|
731
|
+
private readonly signer;
|
|
732
|
+
private readonly relayService;
|
|
733
|
+
private readonly now;
|
|
734
|
+
private readonly defaultLockBufferMs;
|
|
735
|
+
constructor(config: MintingGatewayConfig);
|
|
736
|
+
processMintAndCashOut(request: MintAndCashOutRequest): Promise<MintAndCashOutResponse>;
|
|
737
|
+
private computeLockDurationMs;
|
|
738
|
+
/**
|
|
739
|
+
* Map a RelayError to a MintingGatewayError, releasing the lock only
|
|
740
|
+
* when the tx definitely did not land. `TX_REVERTED` and `TIMEOUT`
|
|
741
|
+
* leave the lock in place because the tx may still be in the mempool
|
|
742
|
+
* or already mined — releasing would enable a double-spend on retry.
|
|
743
|
+
* Always throws.
|
|
744
|
+
*/
|
|
745
|
+
private handleRelayFailure;
|
|
746
|
+
/**
|
|
747
|
+
* Release a lock, swallowing any secondary error. We never want a lock
|
|
748
|
+
* release failure to mask the original error — the lock will auto-expire
|
|
749
|
+
* via TTL anyway.
|
|
750
|
+
*/
|
|
751
|
+
private releaseLockSafely;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/** Decoded Transfer(from=0x0 → to) event used to finalize a mint. */
|
|
755
|
+
interface MintEvent {
|
|
756
|
+
/** Destination address (the user who received the minted points) */
|
|
757
|
+
to: Address;
|
|
758
|
+
/** Amount minted (in wei / base units) */
|
|
759
|
+
amount: bigint;
|
|
760
|
+
/** Block number the mint was included in */
|
|
761
|
+
blockNumber: bigint;
|
|
762
|
+
/** Transaction hash that emitted the event */
|
|
763
|
+
txHash: Hex;
|
|
764
|
+
/** Log index within the tx, for deterministic ordering */
|
|
765
|
+
logIndex: number;
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* Cursor persistence interface — the indexer reports the next block
|
|
769
|
+
* number it is about to process so the caller can write it to Redis /
|
|
770
|
+
* Postgres / a file. The SDK does not own persistence because every
|
|
771
|
+
* issuer has their own storage stack.
|
|
772
|
+
*/
|
|
773
|
+
interface IIndexerCursorStore {
|
|
774
|
+
/** Return the last persisted cursor (`undefined` on first run). */
|
|
775
|
+
load(): Promise<bigint | undefined>;
|
|
776
|
+
/** Persist a new cursor value. Called after each successful batch. */
|
|
777
|
+
save(blockNumber: bigint): Promise<void>;
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* No-op cursor store. Useful when the caller wants to drive the cursor
|
|
781
|
+
* entirely via `processBlockRange()` and doesn't need persistence.
|
|
782
|
+
*/
|
|
783
|
+
declare class InMemoryCursorStore implements IIndexerCursorStore {
|
|
784
|
+
private cursor;
|
|
785
|
+
load(): Promise<bigint | undefined>;
|
|
786
|
+
save(blockNumber: bigint): Promise<void>;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
interface PointIndexerConfig {
|
|
790
|
+
provider: PublicClient;
|
|
791
|
+
pointTokenAddress: Address;
|
|
792
|
+
ledger: IPointLedger;
|
|
793
|
+
/**
|
|
794
|
+
* Block to start from on first run. Ignored if the cursor store already
|
|
795
|
+
* has a value. Defaults to `0n`.
|
|
796
|
+
*/
|
|
797
|
+
fromBlock?: bigint;
|
|
798
|
+
/**
|
|
799
|
+
* Persistent cursor store. Defaults to an `InMemoryCursorStore` which
|
|
800
|
+
* only survives inside the current process.
|
|
801
|
+
*/
|
|
802
|
+
cursorStore?: IIndexerCursorStore;
|
|
803
|
+
/**
|
|
804
|
+
* Reorg safety: only treat events as final after this many confirmations.
|
|
805
|
+
* The indexer reads `latestBlock - confirmations` as its high-water mark.
|
|
806
|
+
* Default: 3 (a conservative number for Base L2).
|
|
807
|
+
*/
|
|
808
|
+
confirmations?: number;
|
|
809
|
+
/**
|
|
810
|
+
* How many blocks to scan per `getLogs` call. Keeps RPC pages bounded.
|
|
811
|
+
* Default: 2000 (comfortably under most provider page limits).
|
|
812
|
+
*/
|
|
813
|
+
batchSize?: number;
|
|
814
|
+
/**
|
|
815
|
+
* Polling interval (ms) used by `start()`. Default: 5000 (5s).
|
|
816
|
+
*/
|
|
817
|
+
pollIntervalMs?: number;
|
|
818
|
+
/** Clock override for tests. */
|
|
819
|
+
now?: () => number;
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* Watches `PointToken.Transfer(from=0x0 → to)` events and finalizes the
|
|
823
|
+
* issuer ledger on each confirmed mint.
|
|
824
|
+
*
|
|
825
|
+
* Finalization strategy (per event):
|
|
826
|
+
* 1. Find a PENDING locked mint request for `to` with matching amount.
|
|
827
|
+
* 2. Call `ledger.deductBalance(to, amount, txHash)` — this is idempotent
|
|
828
|
+
* in the default `MemoryPointLedger` because deducting also resolves
|
|
829
|
+
* the matching lock.
|
|
830
|
+
* 3. If no matching lock is found (e.g. a manual `PointToken.mint()` call
|
|
831
|
+
* or a race where the lock already expired), log and skip — this
|
|
832
|
+
* intentionally does NOT credit the ledger, because the gateway is
|
|
833
|
+
* the only sanctioned way to spawn a mint.
|
|
834
|
+
*
|
|
835
|
+
* Reorg safety: events are only processed when they are at least
|
|
836
|
+
* `confirmations` blocks deep. A new `getLogs` call on every poll picks
|
|
837
|
+
* up finalized blocks from the persisted cursor.
|
|
838
|
+
*
|
|
839
|
+
* Pure-polling design (rather than `watchContractEvent`) keeps the SDK
|
|
840
|
+
* compatible with HTTP-only providers and with RPC rotation.
|
|
841
|
+
*/
|
|
842
|
+
declare class PointIndexer {
|
|
843
|
+
private readonly provider;
|
|
844
|
+
private readonly pointTokenAddress;
|
|
845
|
+
private readonly ledger;
|
|
846
|
+
private readonly cursorStore;
|
|
847
|
+
private readonly startBlock;
|
|
848
|
+
private readonly confirmations;
|
|
849
|
+
private readonly batchSize;
|
|
850
|
+
private readonly pollIntervalMs;
|
|
851
|
+
private running;
|
|
852
|
+
private timer;
|
|
853
|
+
constructor(config: PointIndexerConfig);
|
|
854
|
+
/** Begin polling. Schedules `tick()` on a loop. */
|
|
855
|
+
start(): void;
|
|
856
|
+
/** Stop polling. Safe to call multiple times. */
|
|
857
|
+
stop(): void;
|
|
858
|
+
/**
|
|
859
|
+
* Run one poll cycle: load cursor → scan [cursor, safeHead] in
|
|
860
|
+
* `batchSize` chunks → persist new cursor. Swallows any error and
|
|
861
|
+
* schedules the next tick. Visible for test harnesses via a public name.
|
|
862
|
+
*/
|
|
863
|
+
tick(): Promise<void>;
|
|
864
|
+
private scheduleNext;
|
|
865
|
+
/**
|
|
866
|
+
* Scan `[from, to]` inclusive for mint events in `batchSize` chunks.
|
|
867
|
+
* Callers can use this directly to backfill a specific range without
|
|
868
|
+
* engaging `start()`. On completion, the cursor is advanced to `to + 1`.
|
|
869
|
+
*/
|
|
870
|
+
processBlockRange(from: bigint, to: bigint): Promise<void>;
|
|
871
|
+
private decodeMintEvents;
|
|
872
|
+
/**
|
|
873
|
+
* Finalize a single mint event: match it to a PENDING lock in the
|
|
874
|
+
* ledger, then call `deductBalance` (which also resolves the lock in
|
|
875
|
+
* the default `MemoryPointLedger`).
|
|
876
|
+
*
|
|
877
|
+
* No-matching-lock is a valid state: it means either the lock already
|
|
878
|
+
* expired, or the mint was authorized out-of-band (e.g. a direct
|
|
879
|
+
* `PointToken.mint()` from an EOA minter for testing). In that case we
|
|
880
|
+
* do NOT touch the ledger — crediting here would silently allow the
|
|
881
|
+
* issuer to mint without going through the gateway.
|
|
882
|
+
*/
|
|
883
|
+
private finalize;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
interface ApiConfigResponse {
|
|
887
|
+
chainId: number;
|
|
888
|
+
contracts: {
|
|
889
|
+
pointToken?: Address;
|
|
890
|
+
relay?: Address;
|
|
891
|
+
issuerRegistry?: Address;
|
|
892
|
+
pointTokenFactory?: Address;
|
|
893
|
+
mintingOracle?: Address;
|
|
894
|
+
poolManager?: Address;
|
|
895
|
+
usdt?: Address;
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
interface ApiNonceResponse {
|
|
899
|
+
nonce: string;
|
|
900
|
+
}
|
|
901
|
+
interface ApiLoginRequest {
|
|
902
|
+
message: string;
|
|
903
|
+
signature: Hex;
|
|
904
|
+
}
|
|
905
|
+
interface ApiLoginResponse {
|
|
906
|
+
token: string;
|
|
907
|
+
userAddress: Address;
|
|
908
|
+
/** Unix ms when the JWT (and backing session) expires. */
|
|
909
|
+
expiresAt: number;
|
|
910
|
+
}
|
|
911
|
+
interface ApiGasFeeResponse {
|
|
912
|
+
/** Gas fee quoted in USDT (6-decimal base units). */
|
|
913
|
+
gasFeeUsdt: bigint;
|
|
914
|
+
}
|
|
915
|
+
interface ApiPoolsRequest {
|
|
916
|
+
chainId: number;
|
|
917
|
+
pointTokenAddress: Address;
|
|
918
|
+
}
|
|
919
|
+
interface ApiPoolsResponse {
|
|
920
|
+
pools: PoolKey[];
|
|
921
|
+
}
|
|
922
|
+
interface ApiUserRequest {
|
|
923
|
+
chainId: number;
|
|
924
|
+
userAddress: Address;
|
|
925
|
+
pointTokenAddress: Address;
|
|
926
|
+
}
|
|
927
|
+
interface ApiUserResponse {
|
|
928
|
+
mintRequestNonce: bigint;
|
|
929
|
+
receiverConsentNonce: bigint;
|
|
930
|
+
balance: bigint;
|
|
931
|
+
isMinter: boolean;
|
|
932
|
+
}
|
|
933
|
+
interface ApiClaimAndSwapRequest {
|
|
934
|
+
chainId: number;
|
|
935
|
+
pointTokenAddress: Address;
|
|
936
|
+
/**
|
|
937
|
+
* EIP-712 domain for the point token. Frontends read this once (via
|
|
938
|
+
* `PafiSDK.getDomain()` or a direct contract call) and include it on
|
|
939
|
+
* every claim request so the backend doesn't need to re-read it.
|
|
940
|
+
*/
|
|
941
|
+
domain: PointTokenDomainConfig;
|
|
942
|
+
/** The full signed ReceiverConsent message. */
|
|
943
|
+
receiverConsent: ReceiverConsent;
|
|
944
|
+
/** Detached EIP-712 signature (`serialized` form from `@pafi/core`). */
|
|
945
|
+
receiverSignature: Hex;
|
|
946
|
+
/** Swap hop(s) from pointToken → USDT. */
|
|
947
|
+
swapPath: PathKey[];
|
|
948
|
+
/** Unix seconds. */
|
|
949
|
+
swapDeadline: bigint;
|
|
950
|
+
}
|
|
951
|
+
interface ApiClaimAndSwapResponse {
|
|
952
|
+
txHash: Hex;
|
|
953
|
+
lockId: string;
|
|
954
|
+
blockNumber?: bigint;
|
|
955
|
+
gasUsed?: bigint;
|
|
956
|
+
}
|
|
957
|
+
interface ApiBuildConsentTypedDataRequest {
|
|
958
|
+
chainId: number;
|
|
959
|
+
pointTokenAddress: Address;
|
|
960
|
+
receiverConsent: ReceiverConsent;
|
|
961
|
+
}
|
|
962
|
+
interface ApiBuildConsentTypedDataResponse {
|
|
963
|
+
typedData: {
|
|
964
|
+
domain: {
|
|
965
|
+
name: string;
|
|
966
|
+
version: string;
|
|
967
|
+
chainId: number;
|
|
968
|
+
verifyingContract: Address;
|
|
969
|
+
};
|
|
970
|
+
types: Record<string, {
|
|
971
|
+
name: string;
|
|
972
|
+
type: string;
|
|
973
|
+
}[]>;
|
|
974
|
+
primaryType: string;
|
|
975
|
+
message: Record<string, unknown>;
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
type PoolsProvider = (request: ApiPoolsRequest) => Promise<ApiPoolsResponse>;
|
|
979
|
+
|
|
980
|
+
interface IssuerApiHandlersConfig {
|
|
981
|
+
authService: AuthService;
|
|
982
|
+
gateway: MintingGateway;
|
|
983
|
+
ledger: IPointLedger;
|
|
984
|
+
/** Used by `handleUser` to read on-chain nonces and minter status. */
|
|
985
|
+
provider: PublicClient;
|
|
986
|
+
pointTokenAddress: Address;
|
|
987
|
+
chainId: number;
|
|
988
|
+
contracts: ApiConfigResponse["contracts"];
|
|
989
|
+
/** Required by `handleGasFee`; omit to disable the endpoint. */
|
|
990
|
+
feeManager?: FeeManager;
|
|
991
|
+
/** Required by `handlePools`; omit to disable the endpoint. */
|
|
992
|
+
poolsProvider?: PoolsProvider;
|
|
993
|
+
}
|
|
994
|
+
/**
|
|
995
|
+
* Framework-agnostic HTTP handlers that match the endpoints a `PafiSDK`
|
|
996
|
+
* frontend expects to call. Issuers wrap these in Express / Fastify /
|
|
997
|
+
* Hono / whatever — the handlers take plain inputs and return plain
|
|
998
|
+
* outputs, with `AuthError` / `MintingGatewayError` surfaced as typed
|
|
999
|
+
* exceptions.
|
|
1000
|
+
*
|
|
1001
|
+
* Every protected handler takes a pre-verified `userAddress` as its first
|
|
1002
|
+
* argument. The issuer's middleware wraps `authenticateRequest()` from
|
|
1003
|
+
* `@pafi/issuer` to extract that address from the Bearer token before
|
|
1004
|
+
* routing.
|
|
1005
|
+
*/
|
|
1006
|
+
declare class IssuerApiHandlers {
|
|
1007
|
+
private readonly authService;
|
|
1008
|
+
private readonly gateway;
|
|
1009
|
+
private readonly ledger;
|
|
1010
|
+
private readonly provider;
|
|
1011
|
+
private readonly pointTokenAddress;
|
|
1012
|
+
private readonly chainId;
|
|
1013
|
+
private readonly contracts;
|
|
1014
|
+
private readonly feeManager?;
|
|
1015
|
+
private readonly poolsProvider?;
|
|
1016
|
+
constructor(config: IssuerApiHandlersConfig);
|
|
1017
|
+
/** `GET /auth/nonce` */
|
|
1018
|
+
handleGetNonce(): Promise<ApiNonceResponse>;
|
|
1019
|
+
/** `POST /auth/login` */
|
|
1020
|
+
handleLogin(body: ApiLoginRequest): Promise<ApiLoginResponse>;
|
|
1021
|
+
/**
|
|
1022
|
+
* `GET /config?chainId=<id>`
|
|
1023
|
+
*
|
|
1024
|
+
* Returns the contract addresses and chain id that the frontend SDK
|
|
1025
|
+
* needs to build EIP-712 messages and interact with on-chain.
|
|
1026
|
+
*/
|
|
1027
|
+
handleConfig(chainId: number): Promise<ApiConfigResponse>;
|
|
1028
|
+
/** `GET /gas-fee` — quoted in USDT (6-decimal base units). */
|
|
1029
|
+
handleGasFee(): Promise<ApiGasFeeResponse>;
|
|
1030
|
+
/** `POST /auth/logout` */
|
|
1031
|
+
handleLogout(token: string): Promise<void>;
|
|
1032
|
+
/**
|
|
1033
|
+
* `GET /pools?chainId=<id>&pointToken=<addr>`
|
|
1034
|
+
*
|
|
1035
|
+
* Delegates to the injected `PoolsProvider`. The handler itself does
|
|
1036
|
+
* not know where pools come from — that's an issuer decision.
|
|
1037
|
+
*/
|
|
1038
|
+
handlePools(_userAddress: Address, request: ApiPoolsRequest): Promise<ApiPoolsResponse>;
|
|
1039
|
+
/**
|
|
1040
|
+
* `GET /user?chainId=<id>&user=<addr>&pointToken=<addr>`
|
|
1041
|
+
*
|
|
1042
|
+
* Returns per-user state the frontend needs to build a fresh
|
|
1043
|
+
* `ReceiverConsent`: on-chain nonces + minter status + off-chain
|
|
1044
|
+
* balance.
|
|
1045
|
+
*/
|
|
1046
|
+
handleUser(userAddress: Address, request: ApiUserRequest): Promise<ApiUserResponse>;
|
|
1047
|
+
/**
|
|
1048
|
+
* `POST /build-consent-typed-data`
|
|
1049
|
+
*
|
|
1050
|
+
* Backend builds the full EIP-712 typed data payload for a
|
|
1051
|
+
* ReceiverConsent message. The domain (name, version, chainId,
|
|
1052
|
+
* verifyingContract) is read from the PointToken contract — mobile
|
|
1053
|
+
* never needs to know or hardcode these values. Forward the
|
|
1054
|
+
* response directly to `wallet.signTypedData()`.
|
|
1055
|
+
*
|
|
1056
|
+
* This ensures a single source of truth for domain + types, and
|
|
1057
|
+
* makes contract upgrades (domain version bump) transparent to
|
|
1058
|
+
* mobile apps — no app store review needed.
|
|
1059
|
+
*/
|
|
1060
|
+
handleBuildConsentTypedData(userAddress: Address, request: ApiBuildConsentTypedDataRequest): Promise<ApiBuildConsentTypedDataResponse>;
|
|
1061
|
+
/**
|
|
1062
|
+
* `POST /claim-and-swap`
|
|
1063
|
+
*
|
|
1064
|
+
* The terminal handler: forwards the verified consent to the
|
|
1065
|
+
* MintingGateway, which runs the 11-step flow.
|
|
1066
|
+
*/
|
|
1067
|
+
handleClaimAndSwap(userAddress: Address, request: ApiClaimAndSwapRequest): Promise<ApiClaimAndSwapResponse>;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
/**
|
|
1071
|
+
* Config for `createSubgraphPoolsProvider`.
|
|
1072
|
+
*/
|
|
1073
|
+
interface SubgraphPoolsProviderConfig {
|
|
1074
|
+
/**
|
|
1075
|
+
* Fully qualified subgraph GraphQL endpoint. Example:
|
|
1076
|
+
* `https://graph.pacificfinance.org/subgraphs/name/pafi`
|
|
1077
|
+
*/
|
|
1078
|
+
subgraphUrl: string;
|
|
1079
|
+
/**
|
|
1080
|
+
* Cache TTL in milliseconds. Pool discovery is near-static — a 30s
|
|
1081
|
+
* cache removes subgraph load without meaningfully delaying UX.
|
|
1082
|
+
* Set to 0 to disable caching. Default: 30_000.
|
|
1083
|
+
*/
|
|
1084
|
+
cacheTtlMs?: number;
|
|
1085
|
+
/**
|
|
1086
|
+
* Optional fetch override for test harnesses. Defaults to global `fetch`.
|
|
1087
|
+
*/
|
|
1088
|
+
fetchImpl?: typeof fetch;
|
|
1089
|
+
/**
|
|
1090
|
+
* Optional clock override for tests.
|
|
1091
|
+
*/
|
|
1092
|
+
now?: () => number;
|
|
1093
|
+
}
|
|
1094
|
+
/**
|
|
1095
|
+
* Create a `PoolsProvider` backed by the PAFI subgraph.
|
|
1096
|
+
*
|
|
1097
|
+
* Queries the `pafiTokens` entity for the given `pointTokenAddress`,
|
|
1098
|
+
* reads its linked `Pool`, and returns a single-element `PoolKey[]`.
|
|
1099
|
+
* Multiple pools per token would require a subgraph schema change.
|
|
1100
|
+
*
|
|
1101
|
+
* The result is cached in-process with a short TTL (default 30s). Pool
|
|
1102
|
+
* discovery is near-static so this avoids hammering the subgraph without
|
|
1103
|
+
* blocking config changes for long.
|
|
1104
|
+
*
|
|
1105
|
+
* Returns `{ pools: [] }` if:
|
|
1106
|
+
* - the token is not registered on PAFI yet (no PafiToken entity)
|
|
1107
|
+
* - the token is registered but its pool has not been initialised
|
|
1108
|
+
* - the subgraph is unreachable or returns an error (logs to console,
|
|
1109
|
+
* does not throw — callers should handle empty pool list gracefully)
|
|
1110
|
+
*
|
|
1111
|
+
* Assumes the PAFI subgraph schema. Issuers with a custom subgraph must
|
|
1112
|
+
* implement `PoolsProvider` themselves instead of using this helper.
|
|
1113
|
+
*
|
|
1114
|
+
* @example
|
|
1115
|
+
* ```ts
|
|
1116
|
+
* import { createSubgraphPoolsProvider, createIssuerService } from "@pafi/issuer";
|
|
1117
|
+
*
|
|
1118
|
+
* const service = createIssuerService({
|
|
1119
|
+
* // ...other config
|
|
1120
|
+
* poolsProvider: createSubgraphPoolsProvider({
|
|
1121
|
+
* subgraphUrl: "https://graph.pacificfinance.org/subgraphs/name/pafi",
|
|
1122
|
+
* }),
|
|
1123
|
+
* });
|
|
1124
|
+
* ```
|
|
1125
|
+
*/
|
|
1126
|
+
declare function createSubgraphPoolsProvider(config: SubgraphPoolsProviderConfig): PoolsProvider;
|
|
1127
|
+
|
|
1128
|
+
/**
|
|
1129
|
+
* Config for `createSubgraphNativeUsdtQuoter`.
|
|
1130
|
+
*/
|
|
1131
|
+
interface SubgraphNativeUsdtQuoterConfig {
|
|
1132
|
+
/**
|
|
1133
|
+
* Fully qualified subgraph GraphQL endpoint. Same URL used by
|
|
1134
|
+
* `createSubgraphPoolsProvider`.
|
|
1135
|
+
*/
|
|
1136
|
+
subgraphUrl: string;
|
|
1137
|
+
/**
|
|
1138
|
+
* Decimals of the USDT token. Defaults to 6 (standard USDT/USDC on
|
|
1139
|
+
* Base, Ethereum, Polygon). Override for chains where USDT uses a
|
|
1140
|
+
* different decimals value.
|
|
1141
|
+
*/
|
|
1142
|
+
usdtDecimals?: number;
|
|
1143
|
+
/**
|
|
1144
|
+
* Decimals of the native token (ETH on Base/mainnet/Arbitrum/Optimism,
|
|
1145
|
+
* MATIC on Polygon). Default: 18.
|
|
1146
|
+
*/
|
|
1147
|
+
nativeDecimals?: number;
|
|
1148
|
+
/**
|
|
1149
|
+
* Cache TTL in milliseconds. ETH price drifts slowly relative to gas
|
|
1150
|
+
* estimation needs — a 30s cache keeps fees stable across bursts of
|
|
1151
|
+
* requests without letting them go stale during volatile markets.
|
|
1152
|
+
* Set to 0 to disable caching. Default: 30_000.
|
|
1153
|
+
*/
|
|
1154
|
+
cacheTtlMs?: number;
|
|
1155
|
+
/**
|
|
1156
|
+
* Fallback price (USDT per native token, human-readable float) used
|
|
1157
|
+
* when the subgraph is unreachable. This keeps the backend operational
|
|
1158
|
+
* during subgraph outages rather than bricking cashouts. The fee will
|
|
1159
|
+
* be slightly off but the operator still gets paid. Default: 3000.
|
|
1160
|
+
*/
|
|
1161
|
+
fallbackEthPriceUsd?: number;
|
|
1162
|
+
/** Optional fetch override for test harnesses. */
|
|
1163
|
+
fetchImpl?: typeof fetch;
|
|
1164
|
+
/** Optional clock override for tests. */
|
|
1165
|
+
now?: () => number;
|
|
1166
|
+
}
|
|
1167
|
+
/**
|
|
1168
|
+
* Create a `quoteNativeToUsdt` function backed by the PAFI subgraph's
|
|
1169
|
+
* `Bundle.ethPriceUSD`.
|
|
1170
|
+
*
|
|
1171
|
+
* Used by `FeeManager.estimateGasFee()` to convert the operator's native
|
|
1172
|
+
* gas cost into the USDT amount deducted from the user's cashout. Price
|
|
1173
|
+
* precision is not critical here — a 1-2% drift is acceptable since
|
|
1174
|
+
* the operator already takes a `gasPremiumBps` buffer.
|
|
1175
|
+
*
|
|
1176
|
+
* The result is cached in-process with a short TTL (default 30s). If the
|
|
1177
|
+
* subgraph is unreachable, falls back to `fallbackEthPriceUsd` so gas
|
|
1178
|
+
* estimation doesn't block cashouts during a subgraph outage.
|
|
1179
|
+
*
|
|
1180
|
+
* @example
|
|
1181
|
+
* ```ts
|
|
1182
|
+
* import { createSubgraphNativeUsdtQuoter, createIssuerService } from "@pafi/issuer";
|
|
1183
|
+
*
|
|
1184
|
+
* const service = createIssuerService({
|
|
1185
|
+
* // ...other config
|
|
1186
|
+
* fee: {
|
|
1187
|
+
* quoteNativeToUsdt: createSubgraphNativeUsdtQuoter({
|
|
1188
|
+
* subgraphUrl: "https://graph.pacificfinance.org/subgraphs/name/pafi",
|
|
1189
|
+
* }),
|
|
1190
|
+
* },
|
|
1191
|
+
* });
|
|
1192
|
+
* ```
|
|
1193
|
+
*/
|
|
1194
|
+
declare function createSubgraphNativeUsdtQuoter(config: SubgraphNativeUsdtQuoterConfig): (amountNative: bigint) => Promise<bigint>;
|
|
1195
|
+
|
|
1196
|
+
/**
|
|
1197
|
+
* Top-level configuration for `createIssuerService`. Everything except
|
|
1198
|
+
* the chain metadata, wallets, auth secret, and `signer` is optional and
|
|
1199
|
+
* falls back to the in-memory dev defaults — that makes the happy path
|
|
1200
|
+
* a single-call wire-up while still letting production issuers plug in
|
|
1201
|
+
* their own ledger, session store, policy engine, and KMS signer.
|
|
1202
|
+
*/
|
|
1203
|
+
interface IssuerServiceConfig {
|
|
1204
|
+
chainId: number;
|
|
1205
|
+
/** Address of the deployed PointToken (one per issuer instance). */
|
|
1206
|
+
pointTokenAddress: Address;
|
|
1207
|
+
/** Address of the deployed Relay contract. */
|
|
1208
|
+
relayAddress: Address;
|
|
1209
|
+
/**
|
|
1210
|
+
* Full contract address bundle returned verbatim by `handleConfig` so
|
|
1211
|
+
* the frontend SDK can pick them up.
|
|
1212
|
+
*/
|
|
1213
|
+
contracts: ApiConfigResponse["contracts"];
|
|
1214
|
+
/**
|
|
1215
|
+
* Shared `PublicClient` used for on-chain reads, simulation, indexer
|
|
1216
|
+
* polling, and gas-price lookups. Must be pointed at the target chain.
|
|
1217
|
+
*/
|
|
1218
|
+
provider: PublicClient;
|
|
1219
|
+
/** Operator wallet — pays gas, receives the operator fee. */
|
|
1220
|
+
operatorWallet: OperatorWalletLike;
|
|
1221
|
+
auth: {
|
|
1222
|
+
jwtSecret: string;
|
|
1223
|
+
/** SIWE-style login-message domain, e.g. `"app.example.com"`. */
|
|
1224
|
+
domain: string;
|
|
1225
|
+
/** Passed straight to `jose` (`"24h"`, `"7d"`, …). Default `"24h"`. */
|
|
1226
|
+
jwtExpiresIn?: string;
|
|
1227
|
+
};
|
|
1228
|
+
/**
|
|
1229
|
+
* Issuer signer. No default — production issuers MUST plug in an
|
|
1230
|
+
* HSM/KMS-backed implementation. For local development use
|
|
1231
|
+
* `PrivateKeySigner` directly.
|
|
1232
|
+
*/
|
|
1233
|
+
signer: IIssuerSigner;
|
|
1234
|
+
ledger?: IPointLedger;
|
|
1235
|
+
policy?: IPolicyEngine;
|
|
1236
|
+
sessionStore?: ISessionStore;
|
|
1237
|
+
/**
|
|
1238
|
+
* Fee management config. If omitted the `handleGasFee` endpoint will
|
|
1239
|
+
* throw "not configured" at request time. Pass any subset of fields
|
|
1240
|
+
* to opt in — provider + operatorWallet are inherited from the outer
|
|
1241
|
+
* config automatically.
|
|
1242
|
+
*/
|
|
1243
|
+
fee?: Omit<FeeManagerConfig, "provider" | "operatorWallet">;
|
|
1244
|
+
/**
|
|
1245
|
+
* Pool discovery function for `handlePools`. If omitted the endpoint
|
|
1246
|
+
* throws "not configured" at request time.
|
|
1247
|
+
*/
|
|
1248
|
+
poolsProvider?: PoolsProvider;
|
|
1249
|
+
indexer?: {
|
|
1250
|
+
fromBlock?: bigint;
|
|
1251
|
+
cursorStore?: IIndexerCursorStore;
|
|
1252
|
+
confirmations?: number;
|
|
1253
|
+
batchSize?: number;
|
|
1254
|
+
pollIntervalMs?: number;
|
|
1255
|
+
/**
|
|
1256
|
+
* If `true`, the factory calls `indexer.start()` before returning.
|
|
1257
|
+
* Default: `false` — the caller decides when to begin polling.
|
|
1258
|
+
*/
|
|
1259
|
+
autoStart?: boolean;
|
|
1260
|
+
};
|
|
1261
|
+
relay?: {
|
|
1262
|
+
simulateBeforeSubmit?: boolean;
|
|
1263
|
+
confirmationTimeoutMs?: number;
|
|
1264
|
+
};
|
|
1265
|
+
gateway?: {
|
|
1266
|
+
/** Extra lock TTL buffer beyond consent deadline. Default 60_000 ms. */
|
|
1267
|
+
defaultLockBufferMs?: number;
|
|
1268
|
+
};
|
|
1269
|
+
}
|
|
1270
|
+
interface IssuerService {
|
|
1271
|
+
authService: AuthService;
|
|
1272
|
+
sessionStore: ISessionStore;
|
|
1273
|
+
ledger: IPointLedger;
|
|
1274
|
+
policy: IPolicyEngine;
|
|
1275
|
+
signer: IIssuerSigner;
|
|
1276
|
+
relayService: RelayService;
|
|
1277
|
+
feeManager: FeeManager | undefined;
|
|
1278
|
+
gateway: MintingGateway;
|
|
1279
|
+
indexer: PointIndexer;
|
|
1280
|
+
handlers: IssuerApiHandlers;
|
|
1281
|
+
}
|
|
1282
|
+
/**
|
|
1283
|
+
* Wire a fully-functional issuer service from a single config object.
|
|
1284
|
+
* Returns every constructed collaborator so the caller can also use the
|
|
1285
|
+
* indexer or relay service directly outside the HTTP layer.
|
|
1286
|
+
*
|
|
1287
|
+
* Defaults:
|
|
1288
|
+
* - `ledger` → `MemoryPointLedger`
|
|
1289
|
+
* - `sessionStore` → `MemorySessionStore`
|
|
1290
|
+
* - `policy` → `DefaultPolicyEngine({ ledger })`
|
|
1291
|
+
* - `feeManager` → undefined (handleGasFee throws until configured)
|
|
1292
|
+
* - `poolsProvider` → undefined (handlePools throws until configured)
|
|
1293
|
+
* - `indexer.autoStart` → false
|
|
1294
|
+
*
|
|
1295
|
+
* Throws synchronously if any required field (`signer`, `provider`,
|
|
1296
|
+
* `operatorWallet`, `auth.jwtSecret`, `pointTokenAddress`, …) is missing.
|
|
1297
|
+
*/
|
|
1298
|
+
declare function createIssuerService(config: IssuerServiceConfig): IssuerService;
|
|
1299
|
+
|
|
1300
|
+
/** SDK package version — bumped on every release */
|
|
1301
|
+
declare const PAFI_ISSUER_SDK_VERSION = "0.1.0";
|
|
1302
|
+
|
|
1303
|
+
export { type ApiBuildConsentTypedDataRequest, type ApiBuildConsentTypedDataResponse, type ApiClaimAndSwapRequest, type ApiClaimAndSwapResponse, type ApiConfigResponse, type ApiGasFeeResponse, type ApiLoginRequest, type ApiLoginResponse, type ApiNonceResponse, type ApiPoolsRequest, type ApiPoolsResponse, type ApiUserRequest, type ApiUserResponse, type AuthContext, AuthError, type AuthErrorCode, AuthService, type AuthServiceConfig, DefaultPolicyEngine, type DefaultPolicyEngineOptions, FeeManager, type FeeManagerConfig, type IIndexerCursorStore, type IIssuerSigner, type IPointLedger, type IPolicyEngine, type ISessionStore, InMemoryCursorStore, IssuerApiHandlers, type IssuerApiHandlersConfig, type IssuerService, type IssuerServiceConfig, type LockedMintRequest, type LoginResult, MemoryPointLedger, MemorySessionStore, type MemorySessionStoreOptions, type MintAndCashOutRequest, type MintAndCashOutResponse, type MintEvent, MintingGateway, type MintingGatewayConfig, MintingGatewayError, type MintingGatewayErrorCode, type MintingStatus, NonceManager, type OperatorWalletLike, PAFI_ISSUER_SDK_VERSION, PointIndexer, type PointIndexerConfig, type PolicyDecision, type PolicyEvalRequest, type PoolsProvider, PrivateKeySigner, type PrivateKeySignerOptions, RelayError, type RelayErrorCode, type RelayResult, RelayService, type RelayServiceConfig, type Session, type SubgraphNativeUsdtQuoterConfig, type SubgraphPoolsProviderConfig, type SubmitMintAndSwapParams, authenticateRequest, createIssuerService, createSubgraphNativeUsdtQuoter, createSubgraphPoolsProvider };
|