@pafi-dev/issuer 0.39.2 → 0.40.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/dist/auth-client/index.cjs +65 -79
- package/dist/auth-client/index.cjs.map +1 -1
- package/dist/auth-client/index.js +2 -2
- package/dist/{chunk-7VEYSL2C.js → chunk-2Z3M2KQG.js} +69 -80
- package/dist/{chunk-7VEYSL2C.js.map → chunk-2Z3M2KQG.js.map} +1 -1
- package/dist/chunk-7QVYU63E.js +7 -0
- package/dist/{chunk-QLNGNH4A.js → chunk-RNQQYJIB.js} +23 -7
- package/dist/{chunk-QLNGNH4A.js.map → chunk-RNQQYJIB.js.map} +1 -1
- package/dist/direct-auth/index.cjs +363 -195
- package/dist/direct-auth/index.cjs.map +1 -1
- package/dist/direct-auth/index.js +304 -132
- package/dist/direct-auth/index.js.map +1 -1
- package/dist/http/index.cjs +14 -1
- package/dist/http/index.cjs.map +1 -1
- package/dist/http/index.js +2 -2
- package/dist/index.cjs +1093 -1482
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +33 -156
- package/dist/index.d.ts +33 -156
- package/dist/index.js +1069 -1533
- package/dist/index.js.map +1 -1
- package/dist/nestjs/index.cjs +114 -50
- package/dist/nestjs/index.cjs.map +1 -1
- package/dist/nestjs/index.js +106 -61
- package/dist/nestjs/index.js.map +1 -1
- package/dist/wallet-auth/index.cjs +11 -5
- package/dist/wallet-auth/index.cjs.map +1 -1
- package/dist/wallet-auth/index.js +13 -6
- package/dist/wallet-auth/index.js.map +1 -1
- package/package.json +5 -3
- package/dist/chunk-BRKEJJFQ.js +0 -17
- /package/dist/{chunk-BRKEJJFQ.js.map → chunk-7QVYU63E.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -11,11 +11,16 @@ import {
|
|
|
11
11
|
payloadFromGenericError,
|
|
12
12
|
payloadFromHttpException,
|
|
13
13
|
payloadFromPafiSdkError
|
|
14
|
-
} from "./chunk-
|
|
15
|
-
import
|
|
14
|
+
} from "./chunk-RNQQYJIB.js";
|
|
15
|
+
import {
|
|
16
|
+
__name
|
|
17
|
+
} from "./chunk-7QVYU63E.js";
|
|
16
18
|
|
|
17
19
|
// src/policy/defaultPolicy.ts
|
|
18
20
|
var DefaultPolicyEngine = class {
|
|
21
|
+
static {
|
|
22
|
+
__name(this, "DefaultPolicyEngine");
|
|
23
|
+
}
|
|
19
24
|
ledger;
|
|
20
25
|
provider;
|
|
21
26
|
mintingOracleAddress;
|
|
@@ -31,20 +36,18 @@ var DefaultPolicyEngine = class {
|
|
|
31
36
|
if (opts.resolveIssuer) this.resolveIssuer = opts.resolveIssuer;
|
|
32
37
|
if (!opts.mintingOracleAddress || !opts.provider || !opts.verifyMintCap || !opts.resolveIssuer) {
|
|
33
38
|
if (process.env.NODE_ENV === "production") {
|
|
34
|
-
throw new Error(
|
|
35
|
-
"[PAFI] DefaultPolicyEngine: on-chain MintingOracle cap check is required in production. Configure mintingOracleAddress, provider, verifyMintCap, and resolveIssuer."
|
|
36
|
-
);
|
|
39
|
+
throw new Error("[PAFI] DefaultPolicyEngine: on-chain MintingOracle cap check is required in production. Configure mintingOracleAddress, provider, verifyMintCap, and resolveIssuer.");
|
|
37
40
|
}
|
|
38
41
|
}
|
|
39
42
|
}
|
|
40
43
|
async evaluate(request) {
|
|
41
44
|
if (request.amount <= 0n) {
|
|
42
|
-
return {
|
|
45
|
+
return {
|
|
46
|
+
approved: false,
|
|
47
|
+
reason: "Amount must be positive"
|
|
48
|
+
};
|
|
43
49
|
}
|
|
44
|
-
const available = await this.ledger.getBalance(
|
|
45
|
-
request.userAddress,
|
|
46
|
-
request.pointTokenAddress
|
|
47
|
-
);
|
|
50
|
+
const available = await this.ledger.getBalance(request.userAddress, request.pointTokenAddress);
|
|
48
51
|
if (available < request.amount) {
|
|
49
52
|
return {
|
|
50
53
|
approved: false,
|
|
@@ -54,12 +57,7 @@ var DefaultPolicyEngine = class {
|
|
|
54
57
|
if (this.mintingOracleAddress && this.provider && this.verifyMintCap && this.resolveIssuer) {
|
|
55
58
|
try {
|
|
56
59
|
const issuer = await this.resolveIssuer(request.pointTokenAddress);
|
|
57
|
-
await this.verifyMintCap(
|
|
58
|
-
this.provider,
|
|
59
|
-
this.mintingOracleAddress,
|
|
60
|
-
issuer,
|
|
61
|
-
request.amount
|
|
62
|
-
);
|
|
60
|
+
await this.verifyMintCap(this.provider, this.mintingOracleAddress, issuer, request.amount);
|
|
63
61
|
} catch (err) {
|
|
64
62
|
const msg = err instanceof Error ? err.message : String(err);
|
|
65
63
|
return {
|
|
@@ -68,7 +66,9 @@ var DefaultPolicyEngine = class {
|
|
|
68
66
|
};
|
|
69
67
|
}
|
|
70
68
|
}
|
|
71
|
-
return {
|
|
69
|
+
return {
|
|
70
|
+
approved: true
|
|
71
|
+
};
|
|
72
72
|
}
|
|
73
73
|
};
|
|
74
74
|
|
|
@@ -77,17 +77,16 @@ import { randomBytes } from "crypto";
|
|
|
77
77
|
import { getAddress } from "viem";
|
|
78
78
|
var DEFAULT_NONCE_TTL_MS = 5 * 60 * 1e3;
|
|
79
79
|
var MemorySessionStore = class {
|
|
80
|
+
static {
|
|
81
|
+
__name(this, "MemorySessionStore");
|
|
82
|
+
}
|
|
80
83
|
nonces = /* @__PURE__ */ new Map();
|
|
81
|
-
// nonce → expiresAt
|
|
82
84
|
sessions = /* @__PURE__ */ new Map();
|
|
83
|
-
// tokenId → session
|
|
84
85
|
nonceTtlMs;
|
|
85
86
|
now;
|
|
86
87
|
constructor(opts = {}) {
|
|
87
88
|
if (process.env.NODE_ENV === "production" && !opts.dangerouslyAllowMemoryStoreInProduction) {
|
|
88
|
-
throw new Error(
|
|
89
|
-
"[PAFI] MemorySessionStore refuses to start in production (NODE_ENV=production). Multi-pod K8s deploys do not share Map state, so sessions are not revocable across replicas \u2014 `logout` on pod A leaves the token valid on pod B until expiry. Use a Redis-backed session store (see RedisSessionStoreService in gg56-backend or implement your own ISessionStore). To bypass for a single-pod deploy, pass `dangerouslyAllowMemoryStoreInProduction: true`."
|
|
90
|
-
);
|
|
89
|
+
throw new Error("[PAFI] MemorySessionStore refuses to start in production (NODE_ENV=production). Multi-pod K8s deploys do not share Map state, so sessions are not revocable across replicas \u2014 `logout` on pod A leaves the token valid on pod B until expiry. Use a Redis-backed session store (see RedisSessionStoreService in gg56-backend or implement your own ISessionStore). To bypass for a single-pod deploy, pass `dangerouslyAllowMemoryStoreInProduction: true`.");
|
|
91
90
|
}
|
|
92
91
|
this.nonceTtlMs = opts.nonceTtlMs ?? DEFAULT_NONCE_TTL_MS;
|
|
93
92
|
this.now = opts.now ?? (() => Date.now());
|
|
@@ -127,7 +126,9 @@ var MemorySessionStore = class {
|
|
|
127
126
|
this.sessions.delete(tokenId);
|
|
128
127
|
return null;
|
|
129
128
|
}
|
|
130
|
-
return {
|
|
129
|
+
return {
|
|
130
|
+
...session
|
|
131
|
+
};
|
|
131
132
|
}
|
|
132
133
|
async revokeSession(tokenId) {
|
|
133
134
|
this.sessions.delete(tokenId);
|
|
@@ -159,18 +160,21 @@ var MemorySessionStore = class {
|
|
|
159
160
|
|
|
160
161
|
// src/auth/nonceManager.ts
|
|
161
162
|
var NonceManager = class {
|
|
163
|
+
static {
|
|
164
|
+
__name(this, "NonceManager");
|
|
165
|
+
}
|
|
166
|
+
store;
|
|
162
167
|
constructor(store) {
|
|
163
168
|
this.store = store;
|
|
164
169
|
}
|
|
165
|
-
store;
|
|
166
170
|
/** Generate a fresh login nonce. The store is responsible for TTL. */
|
|
167
171
|
async generate() {
|
|
168
172
|
return this.store.createNonce();
|
|
169
173
|
}
|
|
170
174
|
/**
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
175
|
+
* Atomically validate + consume a nonce. Returns `true` iff the nonce
|
|
176
|
+
* was known and unexpired (and is now removed from the store).
|
|
177
|
+
*/
|
|
174
178
|
async consume(nonce) {
|
|
175
179
|
return this.store.consumeNonce(nonce);
|
|
176
180
|
}
|
|
@@ -202,7 +206,11 @@ function statusForCode(code) {
|
|
|
202
206
|
return "not_found";
|
|
203
207
|
}
|
|
204
208
|
}
|
|
209
|
+
__name(statusForCode, "statusForCode");
|
|
205
210
|
var AuthError = class extends PafiSdkError {
|
|
211
|
+
static {
|
|
212
|
+
__name(this, "AuthError");
|
|
213
|
+
}
|
|
206
214
|
code;
|
|
207
215
|
httpStatus;
|
|
208
216
|
constructor(code, message) {
|
|
@@ -215,37 +223,28 @@ var AuthError = class extends PafiSdkError {
|
|
|
215
223
|
// src/auth/loginVerifier.ts
|
|
216
224
|
function assertJwtSecretStrength(secret) {
|
|
217
225
|
if (!secret) {
|
|
218
|
-
throw new Error(
|
|
219
|
-
"AuthService: jwtSecret is required. Generate via `node -e \"console.log(require('crypto').randomBytes(32).toString('hex'))\"`"
|
|
220
|
-
);
|
|
226
|
+
throw new Error("AuthService: jwtSecret is required. Generate via `node -e \"console.log(require('crypto').randomBytes(32).toString('hex'))\"`");
|
|
221
227
|
}
|
|
222
228
|
if (secret.length < 32) {
|
|
223
|
-
throw new Error(
|
|
224
|
-
`AuthService: jwtSecret too short (${secret.length} chars; need \u2265 32). HS256 brute-force becomes feasible below this threshold.`
|
|
225
|
-
);
|
|
229
|
+
throw new Error(`AuthService: jwtSecret too short (${secret.length} chars; need \u2265 32). HS256 brute-force becomes feasible below this threshold.`);
|
|
226
230
|
}
|
|
227
231
|
const uniqueChars = new Set(secret).size;
|
|
228
232
|
if (uniqueChars < 16) {
|
|
229
|
-
throw new Error(
|
|
230
|
-
`AuthService: jwtSecret has only ${uniqueChars} unique characters; need \u2265 16. A trivially weak secret (e.g. "aaaaa...") will pass length but offer almost no entropy. Use \`crypto.randomBytes(32).toString('hex')\`.`
|
|
231
|
-
);
|
|
233
|
+
throw new Error(`AuthService: jwtSecret has only ${uniqueChars} unique characters; need \u2265 16. A trivially weak secret (e.g. "aaaaa...") will pass length but offer almost no entropy. Use \`crypto.randomBytes(32).toString('hex')\`.`);
|
|
232
234
|
}
|
|
233
235
|
const entropy = shannonEntropyBitsPerChar(secret);
|
|
234
236
|
if (entropy < 3.5) {
|
|
235
|
-
throw new Error(
|
|
236
|
-
`AuthService: jwtSecret entropy too low (${entropy.toFixed(2)} bits/char; need \u2265 3.5). Use \`crypto.randomBytes(32).toString('hex')\` (\u2248 4.0 bits/char).`
|
|
237
|
-
);
|
|
237
|
+
throw new Error(`AuthService: jwtSecret entropy too low (${entropy.toFixed(2)} bits/char; need \u2265 3.5). Use \`crypto.randomBytes(32).toString('hex')\` (\u2248 4.0 bits/char).`);
|
|
238
238
|
}
|
|
239
239
|
for (let period = 1; period <= secret.length / 4; period++) {
|
|
240
240
|
if (secret.length % period !== 0) continue;
|
|
241
241
|
const head = secret.slice(0, period);
|
|
242
242
|
if (secret === head.repeat(secret.length / period)) {
|
|
243
|
-
throw new Error(
|
|
244
|
-
`AuthService: jwtSecret is a repeating pattern of period ${period}. Use \`crypto.randomBytes(32).toString('hex')\`.`
|
|
245
|
-
);
|
|
243
|
+
throw new Error(`AuthService: jwtSecret is a repeating pattern of period ${period}. Use \`crypto.randomBytes(32).toString('hex')\`.`);
|
|
246
244
|
}
|
|
247
245
|
}
|
|
248
246
|
}
|
|
247
|
+
__name(assertJwtSecretStrength, "assertJwtSecretStrength");
|
|
249
248
|
function shannonEntropyBitsPerChar(s) {
|
|
250
249
|
const counts = /* @__PURE__ */ new Map();
|
|
251
250
|
for (const c of s) counts.set(c, (counts.get(c) ?? 0) + 1);
|
|
@@ -257,6 +256,7 @@ function shannonEntropyBitsPerChar(s) {
|
|
|
257
256
|
}
|
|
258
257
|
return h;
|
|
259
258
|
}
|
|
259
|
+
__name(shannonEntropyBitsPerChar, "shannonEntropyBitsPerChar");
|
|
260
260
|
function decodeExpiredJwtJti(token) {
|
|
261
261
|
try {
|
|
262
262
|
const parts = token.split(".");
|
|
@@ -266,13 +266,19 @@ function decodeExpiredJwtJti(token) {
|
|
|
266
266
|
const padded = payloadB64.replace(/-/g, "+").replace(/_/g, "/") + "===".slice((payloadB64.length + 3) % 4);
|
|
267
267
|
const json = Buffer.from(padded, "base64").toString("utf-8");
|
|
268
268
|
const claims = JSON.parse(json);
|
|
269
|
-
return typeof claims.jti === "string" ? {
|
|
269
|
+
return typeof claims.jti === "string" ? {
|
|
270
|
+
jti: claims.jti
|
|
271
|
+
} : {};
|
|
270
272
|
} catch {
|
|
271
273
|
return {};
|
|
272
274
|
}
|
|
273
275
|
}
|
|
276
|
+
__name(decodeExpiredJwtJti, "decodeExpiredJwtJti");
|
|
274
277
|
var DEFAULT_EXPIRES_IN = "24h";
|
|
275
278
|
var AuthService = class {
|
|
279
|
+
static {
|
|
280
|
+
__name(this, "AuthService");
|
|
281
|
+
}
|
|
276
282
|
sessionStore;
|
|
277
283
|
jwtSecret;
|
|
278
284
|
jwtExpiresIn;
|
|
@@ -302,9 +308,9 @@ var AuthService = class {
|
|
|
302
308
|
return this.nonceManager.generate();
|
|
303
309
|
}
|
|
304
310
|
/**
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
311
|
+
* Verify a signed login message and issue a JWT on success.
|
|
312
|
+
* Throws an `AuthError` on any validation failure.
|
|
313
|
+
*/
|
|
308
314
|
async login(message, signature) {
|
|
309
315
|
let parsed;
|
|
310
316
|
try {
|
|
@@ -314,46 +320,28 @@ var AuthService = class {
|
|
|
314
320
|
throw new AuthError("INVALID_MESSAGE", `Could not parse login message: ${msg}`);
|
|
315
321
|
}
|
|
316
322
|
if (parsed.expirationTime == null) {
|
|
317
|
-
throw new AuthError(
|
|
318
|
-
"INVALID_MESSAGE",
|
|
319
|
-
"login message must include expirationTime"
|
|
320
|
-
);
|
|
323
|
+
throw new AuthError("INVALID_MESSAGE", "login message must include expirationTime");
|
|
321
324
|
}
|
|
322
325
|
if (parsed.domain !== this.domain) {
|
|
323
|
-
throw new AuthError(
|
|
324
|
-
"DOMAIN_MISMATCH",
|
|
325
|
-
`Expected domain "${this.domain}", got "${parsed.domain}"`
|
|
326
|
-
);
|
|
326
|
+
throw new AuthError("DOMAIN_MISMATCH", `Expected domain "${this.domain}", got "${parsed.domain}"`);
|
|
327
327
|
}
|
|
328
328
|
if (parsed.chainId !== this.chainId) {
|
|
329
|
-
throw new AuthError(
|
|
330
|
-
"CHAIN_MISMATCH",
|
|
331
|
-
`Expected chainId ${this.chainId}, got ${parsed.chainId}`
|
|
332
|
-
);
|
|
329
|
+
throw new AuthError("CHAIN_MISMATCH", `Expected chainId ${this.chainId}, got ${parsed.chainId}`);
|
|
333
330
|
}
|
|
334
331
|
const now = this.now();
|
|
335
332
|
if (parsed.notBefore && parsed.notBefore.getTime() > now.getTime()) {
|
|
336
|
-
throw new AuthError(
|
|
337
|
-
"MESSAGE_NOT_YET_VALID",
|
|
338
|
-
"Login message is not yet valid"
|
|
339
|
-
);
|
|
333
|
+
throw new AuthError("MESSAGE_NOT_YET_VALID", "Login message is not yet valid");
|
|
340
334
|
}
|
|
341
335
|
if (parsed.expirationTime && parsed.expirationTime.getTime() <= now.getTime()) {
|
|
342
336
|
throw new AuthError("MESSAGE_EXPIRED", "Login message has expired");
|
|
343
337
|
}
|
|
344
338
|
const verifyResult = await verifyLoginMessage(message, signature);
|
|
345
339
|
if (!verifyResult.valid) {
|
|
346
|
-
throw new AuthError(
|
|
347
|
-
"SIGNATURE_INVALID",
|
|
348
|
-
"Signature does not match the address in the login message"
|
|
349
|
-
);
|
|
340
|
+
throw new AuthError("SIGNATURE_INVALID", "Signature does not match the address in the login message");
|
|
350
341
|
}
|
|
351
342
|
const nonceOk = await this.nonceManager.consume(parsed.nonce);
|
|
352
343
|
if (!nonceOk) {
|
|
353
|
-
throw new AuthError(
|
|
354
|
-
"NONCE_INVALID",
|
|
355
|
-
"Nonce is unknown, expired, or already used"
|
|
356
|
-
);
|
|
344
|
+
throw new AuthError("NONCE_INVALID", "Nonce is unknown, expired, or already used");
|
|
357
345
|
}
|
|
358
346
|
const userAddress = getAddress2(verifyResult.address);
|
|
359
347
|
const tokenId = randomBytes2(16).toString("hex");
|
|
@@ -370,11 +358,18 @@ var AuthService = class {
|
|
|
370
358
|
let signer = new SignJWT({
|
|
371
359
|
userAddress,
|
|
372
360
|
chainId: this.chainId
|
|
373
|
-
}).setProtectedHeader({
|
|
361
|
+
}).setProtectedHeader({
|
|
362
|
+
alg: "HS256"
|
|
363
|
+
}).setJti(tokenId).setIssuedAt(Math.floor(issuedAt.getTime() / 1e3)).setExpirationTime(Math.floor(expiresAt.getTime() / 1e3));
|
|
374
364
|
if (this.issuer) signer = signer.setIssuer(this.issuer);
|
|
375
365
|
if (this.audience) signer = signer.setAudience(this.audience);
|
|
376
366
|
const token = await signer.sign(this.jwtSecret);
|
|
377
|
-
return {
|
|
367
|
+
return {
|
|
368
|
+
token,
|
|
369
|
+
userAddress,
|
|
370
|
+
tokenId,
|
|
371
|
+
expiresAt
|
|
372
|
+
};
|
|
378
373
|
}
|
|
379
374
|
/** Revoke the session backing the given JWT (logout). */
|
|
380
375
|
async logout(token) {
|
|
@@ -382,9 +377,12 @@ var AuthService = class {
|
|
|
382
377
|
try {
|
|
383
378
|
const result = await jwtVerify(token, this.jwtSecret, {
|
|
384
379
|
clockTolerance: 60,
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
380
|
+
...this.issuer ? {
|
|
381
|
+
issuer: this.issuer
|
|
382
|
+
} : {},
|
|
383
|
+
...this.audience ? {
|
|
384
|
+
audience: this.audience
|
|
385
|
+
} : {}
|
|
388
386
|
});
|
|
389
387
|
payload = result.payload;
|
|
390
388
|
} catch (err) {
|
|
@@ -402,10 +400,7 @@ var AuthService = class {
|
|
|
402
400
|
if (err instanceof joseErrors.JWSSignatureVerificationFailed || err instanceof joseErrors.JWSInvalid || err instanceof joseErrors.JWTInvalid) {
|
|
403
401
|
throw new AuthError("TOKEN_INVALID", "JWT verification failed");
|
|
404
402
|
}
|
|
405
|
-
throw new AuthError(
|
|
406
|
-
"TOKEN_INVALID",
|
|
407
|
-
`JWT verification failed: ${err instanceof Error ? err.message : String(err)}`
|
|
408
|
-
);
|
|
403
|
+
throw new AuthError("TOKEN_INVALID", `JWT verification failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
409
404
|
}
|
|
410
405
|
if (payload.jti) {
|
|
411
406
|
try {
|
|
@@ -421,16 +416,20 @@ var AuthService = class {
|
|
|
421
416
|
console.error("[PAFI] AuthService logout: session store error", err);
|
|
422
417
|
}
|
|
423
418
|
/**
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
419
|
+
* Verify a JWT and return the authenticated user context. Throws an
|
|
420
|
+
* `AuthError` if the token is missing, malformed, expired, revoked, or
|
|
421
|
+
* signed by a different key.
|
|
422
|
+
*/
|
|
428
423
|
async verifyToken(token) {
|
|
429
424
|
let payload;
|
|
430
425
|
try {
|
|
431
426
|
const result = await jwtVerify(token, this.jwtSecret, {
|
|
432
|
-
...this.issuer ? {
|
|
433
|
-
|
|
427
|
+
...this.issuer ? {
|
|
428
|
+
issuer: this.issuer
|
|
429
|
+
} : {},
|
|
430
|
+
...this.audience ? {
|
|
431
|
+
audience: this.audience
|
|
432
|
+
} : {}
|
|
434
433
|
});
|
|
435
434
|
payload = result.payload;
|
|
436
435
|
} catch (err) {
|
|
@@ -448,10 +447,7 @@ var AuthService = class {
|
|
|
448
447
|
}
|
|
449
448
|
const session = await this.sessionStore.getSession(tokenId);
|
|
450
449
|
if (!session) {
|
|
451
|
-
throw new AuthError(
|
|
452
|
-
"SESSION_REVOKED",
|
|
453
|
-
"Session is no longer active (revoked or expired)"
|
|
454
|
-
);
|
|
450
|
+
throw new AuthError("SESSION_REVOKED", "Session is no longer active (revoked or expired)");
|
|
455
451
|
}
|
|
456
452
|
const userAddress = payload.userAddress;
|
|
457
453
|
const chainId = payload.chainId;
|
|
@@ -468,51 +464,50 @@ var AuthService = class {
|
|
|
468
464
|
function parseExpiry(from, expiresIn) {
|
|
469
465
|
const match = expiresIn.match(/^(\d+)\s*(s|m|h|d)$/i);
|
|
470
466
|
if (!match) {
|
|
471
|
-
throw new Error(
|
|
472
|
-
`AuthService: unsupported jwtExpiresIn "${expiresIn}" \u2014 use e.g. "24h"`
|
|
473
|
-
);
|
|
467
|
+
throw new Error(`AuthService: unsupported jwtExpiresIn "${expiresIn}" \u2014 use e.g. "24h"`);
|
|
474
468
|
}
|
|
475
469
|
const n = Number(match[1]);
|
|
476
470
|
const unit = match[2].toLowerCase();
|
|
477
471
|
const multiplier = unit === "s" ? 1e3 : unit === "m" ? 6e4 : unit === "h" ? 36e5 : 864e5;
|
|
478
472
|
return new Date(from.getTime() + n * multiplier);
|
|
479
473
|
}
|
|
474
|
+
__name(parseExpiry, "parseExpiry");
|
|
480
475
|
|
|
481
476
|
// src/auth/jwtMiddleware.ts
|
|
482
477
|
async function authenticateRequest(authHeader, authService) {
|
|
483
478
|
if (!authHeader) {
|
|
484
|
-
throw new AuthError(
|
|
485
|
-
"MISSING_TOKEN",
|
|
486
|
-
"Authorization header is required"
|
|
487
|
-
);
|
|
479
|
+
throw new AuthError("MISSING_TOKEN", "Authorization header is required");
|
|
488
480
|
}
|
|
489
481
|
const match = authHeader.match(/^Bearer\s+(\S+)\s*$/i);
|
|
490
482
|
if (!match) {
|
|
491
|
-
throw new AuthError(
|
|
492
|
-
"MALFORMED_TOKEN",
|
|
493
|
-
"Authorization header must be in the form 'Bearer <token>'"
|
|
494
|
-
);
|
|
483
|
+
throw new AuthError("MALFORMED_TOKEN", "Authorization header must be in the form 'Bearer <token>'");
|
|
495
484
|
}
|
|
496
485
|
const token = match[1];
|
|
497
486
|
return authService.verifyToken(token);
|
|
498
487
|
}
|
|
488
|
+
__name(authenticateRequest, "authenticateRequest");
|
|
499
489
|
|
|
500
490
|
// src/auth/rateLimiter.ts
|
|
501
491
|
var DEFAULT_LIMITS = {
|
|
502
|
-
auth_nonce: {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
492
|
+
auth_nonce: {
|
|
493
|
+
max: 30,
|
|
494
|
+
windowMs: 6e4
|
|
495
|
+
},
|
|
496
|
+
auth_login: {
|
|
497
|
+
max: 5,
|
|
498
|
+
windowMs: 6e4
|
|
499
|
+
}
|
|
506
500
|
};
|
|
507
501
|
var MemoryRateLimiter = class {
|
|
502
|
+
static {
|
|
503
|
+
__name(this, "MemoryRateLimiter");
|
|
504
|
+
}
|
|
508
505
|
buckets = /* @__PURE__ */ new Map();
|
|
509
506
|
limits;
|
|
510
507
|
now;
|
|
511
508
|
constructor(config = {}) {
|
|
512
509
|
if (process.env.NODE_ENV === "production" && !process.env.PAFI_ALLOW_MEMORY_RATE_LIMITER_IN_PROD) {
|
|
513
|
-
console.warn(
|
|
514
|
-
"[PAFI] MemoryRateLimiter not safe for multi-pod K8s deploys \u2014 rate counters are NOT shared across replicas, allowing round-robin bypass. Use a Redis-backed IRateLimiter in production."
|
|
515
|
-
);
|
|
510
|
+
console.warn("[PAFI] MemoryRateLimiter not safe for multi-pod K8s deploys \u2014 rate counters are NOT shared across replicas, allowing round-robin bypass. Use a Redis-backed IRateLimiter in production.");
|
|
516
511
|
}
|
|
517
512
|
this.limits = {
|
|
518
513
|
...DEFAULT_LIMITS,
|
|
@@ -522,47 +517,62 @@ var MemoryRateLimiter = class {
|
|
|
522
517
|
}
|
|
523
518
|
async consume(key, action) {
|
|
524
519
|
const limit = this.limits[action];
|
|
525
|
-
if (!limit) return {
|
|
520
|
+
if (!limit) return {
|
|
521
|
+
allowed: true
|
|
522
|
+
};
|
|
526
523
|
const bucketKey = `${action}:${key}`;
|
|
527
524
|
const now = this.now();
|
|
528
525
|
const bucket = this.buckets.get(bucketKey);
|
|
529
526
|
if (!bucket || now - bucket.windowStartedAt >= limit.windowMs) {
|
|
530
|
-
this.buckets.set(bucketKey, {
|
|
531
|
-
|
|
527
|
+
this.buckets.set(bucketKey, {
|
|
528
|
+
count: 1,
|
|
529
|
+
windowStartedAt: now
|
|
530
|
+
});
|
|
531
|
+
return {
|
|
532
|
+
allowed: true
|
|
533
|
+
};
|
|
532
534
|
}
|
|
533
535
|
if (bucket.count < limit.max) {
|
|
534
536
|
bucket.count += 1;
|
|
535
|
-
return {
|
|
537
|
+
return {
|
|
538
|
+
allowed: true
|
|
539
|
+
};
|
|
536
540
|
}
|
|
537
|
-
const retryAfterMs = Math.max(
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
541
|
+
const retryAfterMs = Math.max(0, bucket.windowStartedAt + limit.windowMs - now);
|
|
542
|
+
return {
|
|
543
|
+
allowed: false,
|
|
544
|
+
retryAfterMs
|
|
545
|
+
};
|
|
542
546
|
}
|
|
543
547
|
/**
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
548
|
+
* Test helper — clear all buckets. Not part of `IRateLimiter`; only
|
|
549
|
+
* exposed on the in-memory impl for unit tests.
|
|
550
|
+
*/
|
|
547
551
|
reset() {
|
|
548
552
|
this.buckets.clear();
|
|
549
553
|
}
|
|
550
554
|
};
|
|
551
555
|
var NoopRateLimiter = class {
|
|
556
|
+
static {
|
|
557
|
+
__name(this, "NoopRateLimiter");
|
|
558
|
+
}
|
|
552
559
|
warned = false;
|
|
553
560
|
async consume() {
|
|
554
561
|
if (!this.warned && process.env.NODE_ENV === "production") {
|
|
555
|
-
console.warn(
|
|
556
|
-
"[PAFI] NoopRateLimiter active \u2014 `/auth/nonce` and `/auth/login` are NOT throttled. Wire a `MemoryRateLimiter` (dev) or Redis-backed impl (prod) via `IssuerApiHandlersConfig.rateLimiter`."
|
|
557
|
-
);
|
|
562
|
+
console.warn("[PAFI] NoopRateLimiter active \u2014 `/auth/nonce` and `/auth/login` are NOT throttled. Wire a `MemoryRateLimiter` (dev) or Redis-backed impl (prod) via `IssuerApiHandlersConfig.rateLimiter`.");
|
|
558
563
|
this.warned = true;
|
|
559
564
|
}
|
|
560
|
-
return {
|
|
565
|
+
return {
|
|
566
|
+
allowed: true
|
|
567
|
+
};
|
|
561
568
|
}
|
|
562
569
|
};
|
|
563
570
|
|
|
564
571
|
// src/relay/types.ts
|
|
565
572
|
var RelayError = class extends PafiSdkError {
|
|
573
|
+
static {
|
|
574
|
+
__name(this, "RelayError");
|
|
575
|
+
}
|
|
566
576
|
httpStatus = "unprocessable";
|
|
567
577
|
code;
|
|
568
578
|
constructor(code, message, cause) {
|
|
@@ -575,21 +585,12 @@ var RelayError = class extends PafiSdkError {
|
|
|
575
585
|
};
|
|
576
586
|
|
|
577
587
|
// src/relay/relayService.ts
|
|
578
|
-
import {
|
|
579
|
-
|
|
580
|
-
erc20Abi
|
|
581
|
-
} from "viem";
|
|
582
|
-
import {
|
|
583
|
-
POINT_TOKEN_MINT_SIG_ABI,
|
|
584
|
-
POINT_TOKEN_BURN_SIG_ABI,
|
|
585
|
-
mintFeeWrapperAbi,
|
|
586
|
-
buildPartialUserOperation,
|
|
587
|
-
signMintRequest,
|
|
588
|
-
getContractAddresses,
|
|
589
|
-
quoteOperatorFeePt,
|
|
590
|
-
Source
|
|
591
|
-
} from "@pafi-dev/core";
|
|
588
|
+
import { encodeFunctionData, erc20Abi } from "viem";
|
|
589
|
+
import { POINT_TOKEN_MINT_SIG_ABI, POINT_TOKEN_BURN_SIG_ABI, mintFeeWrapperAbi, buildPartialUserOperation, signMintRequest, getContractAddresses, quoteOperatorFeePt, Source } from "@pafi-dev/core";
|
|
592
590
|
var RelayService = class {
|
|
591
|
+
static {
|
|
592
|
+
__name(this, "RelayService");
|
|
593
|
+
}
|
|
593
594
|
provider;
|
|
594
595
|
chainId;
|
|
595
596
|
constructor(config = {}) {
|
|
@@ -597,25 +598,28 @@ var RelayService = class {
|
|
|
597
598
|
this.chainId = config.chainId;
|
|
598
599
|
}
|
|
599
600
|
/**
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
601
|
+
* Resolve the fee recipient + amount applied to the next UserOp:
|
|
602
|
+
*
|
|
603
|
+
* - If caller passed an explicit `feeRecipient`, use it (testing
|
|
604
|
+
* only — sponsor-relayer's L1 will reject any non-canonical
|
|
605
|
+
* recipient with `INSUFFICIENT_FEE`). Otherwise, default to
|
|
606
|
+
* `getContractAddresses(chainId).pafiFeeRecipient` when the
|
|
607
|
+
* service has a `chainId` configured.
|
|
608
|
+
* - If caller passed `feeAmount`, use it. Otherwise, when the
|
|
609
|
+
* service has both `provider` + `chainId`, auto-quote via
|
|
610
|
+
* `quoteOperatorFeePt`.
|
|
611
|
+
* - When the service is unconfigured AND caller passed nothing,
|
|
612
|
+
* return `{ feeAmount: 0n, feeRecipient: undefined }` — legacy
|
|
613
|
+
* "no fee" behavior, caller must opt in for the gas-reimbursement
|
|
614
|
+
* transfer to be added to the batch.
|
|
615
|
+
*/
|
|
615
616
|
async resolveFee(params) {
|
|
616
617
|
const feeRecipient = params.feeRecipient ?? (this.chainId !== void 0 ? getContractAddresses(this.chainId).pafiFeeRecipient : void 0);
|
|
617
618
|
if (params.feeAmount !== void 0) {
|
|
618
|
-
return {
|
|
619
|
+
return {
|
|
620
|
+
feeAmount: params.feeAmount,
|
|
621
|
+
feeRecipient
|
|
622
|
+
};
|
|
619
623
|
}
|
|
620
624
|
if (this.provider && this.chainId !== void 0) {
|
|
621
625
|
const feeAmount = await quoteOperatorFeePt({
|
|
@@ -623,44 +627,39 @@ var RelayService = class {
|
|
|
623
627
|
chainId: this.chainId,
|
|
624
628
|
pointTokenAddress: params.pointTokenAddress,
|
|
625
629
|
allowStaleFallback: true,
|
|
626
|
-
onFallback: (info) => {
|
|
627
|
-
console.warn(
|
|
628
|
-
|
|
629
|
-
);
|
|
630
|
-
}
|
|
630
|
+
onFallback: /* @__PURE__ */ __name((info) => {
|
|
631
|
+
console.warn(`[RelayService] operatorFeeQuoter fallback (${info.source}): ${info.reason} \u2192 using ${info.fallbackValue}`);
|
|
632
|
+
}, "onFallback")
|
|
631
633
|
});
|
|
632
|
-
return {
|
|
634
|
+
return {
|
|
635
|
+
feeAmount,
|
|
636
|
+
feeRecipient
|
|
637
|
+
};
|
|
633
638
|
}
|
|
634
|
-
return {
|
|
639
|
+
return {
|
|
640
|
+
feeAmount: 0n,
|
|
641
|
+
feeRecipient
|
|
642
|
+
};
|
|
635
643
|
}
|
|
636
644
|
/**
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
645
|
+
* Build an unsigned UserOp for Scenario 1 (Mint) — sig-gated
|
|
646
|
+
* `PointToken.mint(to, amount, deadline, minterSig)`.
|
|
647
|
+
*/
|
|
640
648
|
async prepareMint(params) {
|
|
641
649
|
if (!params.batchExecutorAddress) {
|
|
642
|
-
throw new RelayError(
|
|
643
|
-
"ENCODE_FAILED",
|
|
644
|
-
"prepareMint: batchExecutorAddress required"
|
|
645
|
-
);
|
|
650
|
+
throw new RelayError("ENCODE_FAILED", "prepareMint: batchExecutorAddress required");
|
|
646
651
|
}
|
|
647
652
|
if (!params.userAddress) {
|
|
648
653
|
throw new RelayError("ENCODE_FAILED", "prepareMint: userAddress required");
|
|
649
654
|
}
|
|
650
655
|
if (!params.pointTokenAddress) {
|
|
651
|
-
throw new RelayError(
|
|
652
|
-
"ENCODE_FAILED",
|
|
653
|
-
"prepareMint: pointTokenAddress required"
|
|
654
|
-
);
|
|
656
|
+
throw new RelayError("ENCODE_FAILED", "prepareMint: pointTokenAddress required");
|
|
655
657
|
}
|
|
656
658
|
if (params.amount <= 0n) {
|
|
657
659
|
throw new RelayError("ENCODE_FAILED", "prepareMint: amount must be positive");
|
|
658
660
|
}
|
|
659
661
|
if (!params.issuerSignerWallet) {
|
|
660
|
-
throw new RelayError(
|
|
661
|
-
"ENCODE_FAILED",
|
|
662
|
-
"prepareMint: issuerSignerWallet required (for MintRequest EIP-712 signature)"
|
|
663
|
-
);
|
|
662
|
+
throw new RelayError("ENCODE_FAILED", "prepareMint: issuerSignerWallet required (for MintRequest EIP-712 signature)");
|
|
664
663
|
}
|
|
665
664
|
if (params.deadline <= 0n) {
|
|
666
665
|
throw new RelayError("ENCODE_FAILED", "prepareMint: deadline must be positive");
|
|
@@ -671,35 +670,24 @@ var RelayService = class {
|
|
|
671
670
|
}
|
|
672
671
|
const MAX_DEADLINE_WINDOW = 3600n;
|
|
673
672
|
if (params.deadline > nowSecs + MAX_DEADLINE_WINDOW) {
|
|
674
|
-
throw new RelayError(
|
|
675
|
-
"ENCODE_FAILED",
|
|
676
|
-
"prepareMint: deadline exceeds maximum allowed window (1 hour)"
|
|
677
|
-
);
|
|
673
|
+
throw new RelayError("ENCODE_FAILED", "prepareMint: deadline exceeds maximum allowed window (1 hour)");
|
|
678
674
|
}
|
|
679
675
|
const MINT_SOURCE = Source.EQUITY;
|
|
680
676
|
const useWrapper = params.mintFeeWrapperAddress !== void 0;
|
|
681
677
|
const receiverForSig = useWrapper ? params.mintFeeWrapperAddress : params.userAddress;
|
|
682
678
|
let minterSig;
|
|
683
679
|
try {
|
|
684
|
-
const sig = await signMintRequest(
|
|
685
|
-
params.
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
nonce: params.mintRequestNonce,
|
|
693
|
-
deadline: params.deadline
|
|
694
|
-
}
|
|
695
|
-
);
|
|
680
|
+
const sig = await signMintRequest(params.issuerSignerWallet, params.domain, {
|
|
681
|
+
user: params.userAddress,
|
|
682
|
+
receiver: receiverForSig,
|
|
683
|
+
amount: params.amount,
|
|
684
|
+
source: MINT_SOURCE,
|
|
685
|
+
nonce: params.mintRequestNonce,
|
|
686
|
+
deadline: params.deadline
|
|
687
|
+
});
|
|
696
688
|
minterSig = sig.serialized;
|
|
697
689
|
} catch (err) {
|
|
698
|
-
throw new RelayError(
|
|
699
|
-
"ENCODE_FAILED",
|
|
700
|
-
`prepareMint: failed to sign MintForRequest: ${errorMessage(err)}`,
|
|
701
|
-
err
|
|
702
|
-
);
|
|
690
|
+
throw new RelayError("ENCODE_FAILED", `prepareMint: failed to sign MintForRequest: ${errorMessage(err)}`, err);
|
|
703
691
|
}
|
|
704
692
|
let mintCallData;
|
|
705
693
|
let mintTarget;
|
|
@@ -732,11 +720,7 @@ var RelayService = class {
|
|
|
732
720
|
mintTarget = params.pointTokenAddress;
|
|
733
721
|
}
|
|
734
722
|
} catch (err) {
|
|
735
|
-
throw new RelayError(
|
|
736
|
-
"ENCODE_FAILED",
|
|
737
|
-
`prepareMint: failed to encode mint call: ${errorMessage(err)}`,
|
|
738
|
-
err
|
|
739
|
-
);
|
|
723
|
+
throw new RelayError("ENCODE_FAILED", `prepareMint: failed to encode mint call: ${errorMessage(err)}`, err);
|
|
740
724
|
}
|
|
741
725
|
const operations = [
|
|
742
726
|
{
|
|
@@ -752,16 +736,10 @@ var RelayService = class {
|
|
|
752
736
|
});
|
|
753
737
|
if (feeAmount > 0n) {
|
|
754
738
|
if (!feeRecipient) {
|
|
755
|
-
throw new RelayError(
|
|
756
|
-
"ENCODE_FAILED",
|
|
757
|
-
"prepareMint: feeRecipient could not be resolved \u2014 pass `feeRecipient` explicitly or construct RelayService with a `chainId`."
|
|
758
|
-
);
|
|
739
|
+
throw new RelayError("ENCODE_FAILED", "prepareMint: feeRecipient could not be resolved \u2014 pass `feeRecipient` explicitly or construct RelayService with a `chainId`.");
|
|
759
740
|
}
|
|
760
741
|
if (feeRecipient === "0x0000000000000000000000000000000000000000") {
|
|
761
|
-
throw new RelayError(
|
|
762
|
-
"ENCODE_FAILED",
|
|
763
|
-
"prepareMint: feeRecipient must not be zero address"
|
|
764
|
-
);
|
|
742
|
+
throw new RelayError("ENCODE_FAILED", "prepareMint: feeRecipient must not be zero address");
|
|
765
743
|
}
|
|
766
744
|
operations.push({
|
|
767
745
|
target: params.pointTokenAddress,
|
|
@@ -769,7 +747,10 @@ var RelayService = class {
|
|
|
769
747
|
data: encodeFunctionData({
|
|
770
748
|
abi: erc20Abi,
|
|
771
749
|
functionName: "transfer",
|
|
772
|
-
args: [
|
|
750
|
+
args: [
|
|
751
|
+
feeRecipient,
|
|
752
|
+
feeAmount
|
|
753
|
+
]
|
|
773
754
|
})
|
|
774
755
|
});
|
|
775
756
|
}
|
|
@@ -785,29 +766,23 @@ var RelayService = class {
|
|
|
785
766
|
});
|
|
786
767
|
}
|
|
787
768
|
/**
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
769
|
+
* Build an unsigned UserOp for Scenario 2 (Burn/Redeem) — sig-gated
|
|
770
|
+
* `PointToken.burn(from, amount, deadline, burnerSig)`. Caller
|
|
771
|
+
* provides a pre-signed `BurnRequest` + sig bytes (typically from
|
|
772
|
+
* `PTRedeemHandler`).
|
|
773
|
+
*
|
|
774
|
+
* Direct burn (no sig) is not used — every burn goes through the
|
|
775
|
+
* issuer-signed `BurnRequest` path.
|
|
776
|
+
*/
|
|
796
777
|
async prepareBurn(params) {
|
|
797
778
|
if (!params.pointTokenAddress) {
|
|
798
779
|
throw new RelayError("ENCODE_FAILED", "prepareBurn: pointTokenAddress required");
|
|
799
780
|
}
|
|
800
781
|
if (!params.batchExecutorAddress) {
|
|
801
|
-
throw new RelayError(
|
|
802
|
-
"ENCODE_FAILED",
|
|
803
|
-
"prepareBurn: batchExecutorAddress required"
|
|
804
|
-
);
|
|
782
|
+
throw new RelayError("ENCODE_FAILED", "prepareBurn: batchExecutorAddress required");
|
|
805
783
|
}
|
|
806
784
|
if (!params.burnRequest || !params.burnerSignature) {
|
|
807
|
-
throw new RelayError(
|
|
808
|
-
"ENCODE_FAILED",
|
|
809
|
-
"prepareBurn: burnRequest + burnerSignature required"
|
|
810
|
-
);
|
|
785
|
+
throw new RelayError("ENCODE_FAILED", "prepareBurn: burnRequest + burnerSignature required");
|
|
811
786
|
}
|
|
812
787
|
let burnCallData;
|
|
813
788
|
try {
|
|
@@ -823,11 +798,7 @@ var RelayService = class {
|
|
|
823
798
|
]
|
|
824
799
|
});
|
|
825
800
|
} catch (err) {
|
|
826
|
-
throw new RelayError(
|
|
827
|
-
"ENCODE_FAILED",
|
|
828
|
-
`prepareBurn: failed to encode burn call: ${errorMessage(err)}`,
|
|
829
|
-
err
|
|
830
|
-
);
|
|
801
|
+
throw new RelayError("ENCODE_FAILED", `prepareBurn: failed to encode burn call: ${errorMessage(err)}`, err);
|
|
831
802
|
}
|
|
832
803
|
const operations = [];
|
|
833
804
|
const { feeAmount, feeRecipient } = await this.resolveFee({
|
|
@@ -837,16 +808,10 @@ var RelayService = class {
|
|
|
837
808
|
});
|
|
838
809
|
if (feeAmount > 0n) {
|
|
839
810
|
if (!feeRecipient) {
|
|
840
|
-
throw new RelayError(
|
|
841
|
-
"ENCODE_FAILED",
|
|
842
|
-
"prepareBurn: feeRecipient could not be resolved \u2014 pass `feeRecipient` explicitly or construct RelayService with a `chainId`."
|
|
843
|
-
);
|
|
811
|
+
throw new RelayError("ENCODE_FAILED", "prepareBurn: feeRecipient could not be resolved \u2014 pass `feeRecipient` explicitly or construct RelayService with a `chainId`.");
|
|
844
812
|
}
|
|
845
813
|
if (feeRecipient === "0x0000000000000000000000000000000000000000") {
|
|
846
|
-
throw new RelayError(
|
|
847
|
-
"ENCODE_FAILED",
|
|
848
|
-
"prepareBurn: feeRecipient must not be zero address"
|
|
849
|
-
);
|
|
814
|
+
throw new RelayError("ENCODE_FAILED", "prepareBurn: feeRecipient must not be zero address");
|
|
850
815
|
}
|
|
851
816
|
operations.push({
|
|
852
817
|
target: params.pointTokenAddress,
|
|
@@ -854,7 +819,10 @@ var RelayService = class {
|
|
|
854
819
|
data: encodeFunctionData({
|
|
855
820
|
abi: erc20Abi,
|
|
856
821
|
functionName: "transfer",
|
|
857
|
-
args: [
|
|
822
|
+
args: [
|
|
823
|
+
feeRecipient,
|
|
824
|
+
feeAmount
|
|
825
|
+
]
|
|
858
826
|
})
|
|
859
827
|
});
|
|
860
828
|
}
|
|
@@ -889,10 +857,10 @@ var RelayService = class {
|
|
|
889
857
|
// call seeds the cache; subsequent ones hit it.
|
|
890
858
|
// =========================================================================
|
|
891
859
|
/**
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
860
|
+
* Build a dummy `PartialUserOperation` for the mint scenario, suitable
|
|
861
|
+
* for `feeManager.estimateGasFee({ partialUserOp, ... })`. NO signing —
|
|
862
|
+
* uses a 65-byte zero signature placeholder.
|
|
863
|
+
*/
|
|
896
864
|
previewMintUserOp(params) {
|
|
897
865
|
const useWrapper = params.mintFeeWrapperAddress !== void 0;
|
|
898
866
|
let mintCallData;
|
|
@@ -927,7 +895,13 @@ var RelayService = class {
|
|
|
927
895
|
return buildPartialUserOperation({
|
|
928
896
|
sender: params.userAddress,
|
|
929
897
|
nonce: params.aaNonce,
|
|
930
|
-
operations: [
|
|
898
|
+
operations: [
|
|
899
|
+
{
|
|
900
|
+
target: mintTarget,
|
|
901
|
+
value: 0n,
|
|
902
|
+
data: mintCallData
|
|
903
|
+
}
|
|
904
|
+
],
|
|
931
905
|
// Gas limits ignored by bundler estimate — it computes them.
|
|
932
906
|
gasLimits: {
|
|
933
907
|
callGasLimit: 1n,
|
|
@@ -953,7 +927,11 @@ var RelayService = class {
|
|
|
953
927
|
sender: params.userAddress,
|
|
954
928
|
nonce: params.aaNonce,
|
|
955
929
|
operations: [
|
|
956
|
-
{
|
|
930
|
+
{
|
|
931
|
+
target: params.pointTokenAddress,
|
|
932
|
+
value: 0n,
|
|
933
|
+
data: burnCallData
|
|
934
|
+
}
|
|
957
935
|
],
|
|
958
936
|
gasLimits: {
|
|
959
937
|
callGasLimit: 1n,
|
|
@@ -967,11 +945,15 @@ var PLACEHOLDER_SIG_65 = `0x${"00".repeat(65)}`;
|
|
|
967
945
|
function errorMessage(err) {
|
|
968
946
|
return err instanceof Error ? err.message : String(err);
|
|
969
947
|
}
|
|
948
|
+
__name(errorMessage, "errorMessage");
|
|
970
949
|
|
|
971
950
|
// src/relay/feeManager.ts
|
|
972
951
|
var DEFAULT_GAS_UNITS = 500000n;
|
|
973
952
|
var DEFAULT_PREMIUM_BPS = 1e4;
|
|
974
953
|
var FeeManager = class _FeeManager {
|
|
954
|
+
static {
|
|
955
|
+
__name(this, "FeeManager");
|
|
956
|
+
}
|
|
975
957
|
provider;
|
|
976
958
|
fallbackGasUnits;
|
|
977
959
|
gasPremiumBps;
|
|
@@ -988,8 +970,7 @@ var FeeManager = class _FeeManager {
|
|
|
988
970
|
static FEE_CACHE_TTL_MS = 1e4;
|
|
989
971
|
constructor(config) {
|
|
990
972
|
if (!config.provider) throw new Error("FeeManager: provider required");
|
|
991
|
-
if (!config.quoteNativeToFee)
|
|
992
|
-
throw new Error("FeeManager: quoteNativeToFee required");
|
|
973
|
+
if (!config.quoteNativeToFee) throw new Error("FeeManager: quoteNativeToFee required");
|
|
993
974
|
this.provider = config.provider;
|
|
994
975
|
this.fallbackGasUnits = config.gasUnits ?? DEFAULT_GAS_UNITS;
|
|
995
976
|
this.gasPremiumBps = config.gasPremiumBps ?? DEFAULT_PREMIUM_BPS;
|
|
@@ -998,16 +979,16 @@ var FeeManager = class _FeeManager {
|
|
|
998
979
|
this.metrics = config.metrics;
|
|
999
980
|
}
|
|
1000
981
|
/**
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
982
|
+
* Estimate the operator fee for the next sponsored UserOp.
|
|
983
|
+
*
|
|
984
|
+
* Without `opts` → legacy path: `gasUnits × gasPrice × premium →
|
|
985
|
+
* quoteNativeToFee`. Cached for 10 s to absorb bursts.
|
|
986
|
+
*
|
|
987
|
+
* With `opts` AND `bundlerClient` → estimator path. Each call may
|
|
988
|
+
* hit a different bundler-cached result; the SDK does NOT add its
|
|
989
|
+
* own value cache because the estimator's cache TTL is the source
|
|
990
|
+
* of truth for "how long is this estimate good for".
|
|
991
|
+
*/
|
|
1011
992
|
async estimateGasFee(opts = {}) {
|
|
1012
993
|
const isLegacyCall = !opts.partialUserOp && !opts.scenario && !opts.contractAddress;
|
|
1013
994
|
const now = Date.now();
|
|
@@ -1017,14 +998,12 @@ var FeeManager = class _FeeManager {
|
|
|
1017
998
|
const t0 = Date.now();
|
|
1018
999
|
const { gasUnits, source } = await this.resolveGasUnits(opts);
|
|
1019
1000
|
const latencyMs = Date.now() - t0;
|
|
1020
|
-
this.safeEmit(
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
})
|
|
1027
|
-
);
|
|
1001
|
+
this.safeEmit(() => this.metrics?.onEstimate?.({
|
|
1002
|
+
source,
|
|
1003
|
+
scenario: opts.scenario,
|
|
1004
|
+
gasUnits,
|
|
1005
|
+
latencyMs
|
|
1006
|
+
}));
|
|
1028
1007
|
const gasPrice = await this.provider.getGasPrice();
|
|
1029
1008
|
const nativeCost = gasPrice * gasUnits;
|
|
1030
1009
|
const withPremium = nativeCost * BigInt(this.gasPremiumBps) / 10000n;
|
|
@@ -1042,7 +1021,10 @@ var FeeManager = class _FeeManager {
|
|
|
1042
1021
|
}
|
|
1043
1022
|
async resolveGasUnits(opts) {
|
|
1044
1023
|
if (!this.bundlerClient || !opts.partialUserOp || !opts.scenario || !opts.contractAddress) {
|
|
1045
|
-
return {
|
|
1024
|
+
return {
|
|
1025
|
+
gasUnits: this.fallbackGasUnits,
|
|
1026
|
+
source: "fallback"
|
|
1027
|
+
};
|
|
1046
1028
|
}
|
|
1047
1029
|
try {
|
|
1048
1030
|
const result = await this.bundlerClient.getGasUnits({
|
|
@@ -1051,16 +1033,20 @@ var FeeManager = class _FeeManager {
|
|
|
1051
1033
|
paymasterAddress: opts.paymasterAddress,
|
|
1052
1034
|
partialUserOp: opts.partialUserOp
|
|
1053
1035
|
});
|
|
1054
|
-
return {
|
|
1036
|
+
return {
|
|
1037
|
+
gasUnits: result.gasUnits,
|
|
1038
|
+
source: "estimator"
|
|
1039
|
+
};
|
|
1055
1040
|
} catch (err) {
|
|
1056
1041
|
const reason = err instanceof Error ? err.message : String(err);
|
|
1057
|
-
this.safeEmit(
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1042
|
+
this.safeEmit(() => this.metrics?.onEstimatorError?.({
|
|
1043
|
+
scenario: opts.scenario,
|
|
1044
|
+
reason
|
|
1045
|
+
}));
|
|
1046
|
+
return {
|
|
1047
|
+
gasUnits: this.fallbackGasUnits,
|
|
1048
|
+
source: "fallback"
|
|
1049
|
+
};
|
|
1064
1050
|
}
|
|
1065
1051
|
}
|
|
1066
1052
|
safeEmit(fn) {
|
|
@@ -1073,6 +1059,9 @@ var FeeManager = class _FeeManager {
|
|
|
1073
1059
|
|
|
1074
1060
|
// src/relay/bundlerEstimator.ts
|
|
1075
1061
|
var PafiEstimatorHttpError = class extends Error {
|
|
1062
|
+
static {
|
|
1063
|
+
__name(this, "PafiEstimatorHttpError");
|
|
1064
|
+
}
|
|
1076
1065
|
status;
|
|
1077
1066
|
body;
|
|
1078
1067
|
constructor(status, body, message) {
|
|
@@ -1088,9 +1077,7 @@ function createPafiEstimatorClient(config) {
|
|
|
1088
1077
|
if (!issuerId) throw new Error("createPafiEstimatorClient: issuerId required");
|
|
1089
1078
|
const fetchImpl = config.fetchImpl ?? globalThis.fetch;
|
|
1090
1079
|
if (!fetchImpl) {
|
|
1091
|
-
throw new Error(
|
|
1092
|
-
"createPafiEstimatorClient: no fetch implementation available \u2014 pass `fetchImpl`"
|
|
1093
|
-
);
|
|
1080
|
+
throw new Error("createPafiEstimatorClient: no fetch implementation available \u2014 pass `fetchImpl`");
|
|
1094
1081
|
}
|
|
1095
1082
|
const url = `${baseUrl.replace(/\/$/, "")}/v1/estimate-gas-fee`;
|
|
1096
1083
|
return {
|
|
@@ -1134,15 +1121,19 @@ function createPafiEstimatorClient(config) {
|
|
|
1134
1121
|
}
|
|
1135
1122
|
};
|
|
1136
1123
|
}
|
|
1124
|
+
__name(createPafiEstimatorClient, "createPafiEstimatorClient");
|
|
1137
1125
|
|
|
1138
1126
|
// src/indexer/types.ts
|
|
1139
1127
|
var InMemoryCursorStore = class _InMemoryCursorStore {
|
|
1128
|
+
static {
|
|
1129
|
+
__name(this, "InMemoryCursorStore");
|
|
1130
|
+
}
|
|
1140
1131
|
cursor;
|
|
1141
1132
|
/**
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1133
|
+
* Child stores keyed by `forKey()`. Each child has its own cursor,
|
|
1134
|
+
* so a single InMemoryCursorStore can back N PointIndexers in tests
|
|
1135
|
+
* / single-process callers.
|
|
1136
|
+
*/
|
|
1146
1137
|
children = /* @__PURE__ */ new Map();
|
|
1147
1138
|
async load() {
|
|
1148
1139
|
return this.cursor;
|
|
@@ -1163,21 +1154,18 @@ var InMemoryCursorStore = class _InMemoryCursorStore {
|
|
|
1163
1154
|
// src/indexer/pointIndexer.ts
|
|
1164
1155
|
import { getAddress as getAddress3, parseAbiItem } from "viem";
|
|
1165
1156
|
var PointIndexerFinalizeError = class extends Error {
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
this.context = context;
|
|
1169
|
-
this.cause = cause;
|
|
1170
|
-
this.name = "PointIndexerFinalizeError";
|
|
1157
|
+
static {
|
|
1158
|
+
__name(this, "PointIndexerFinalizeError");
|
|
1171
1159
|
}
|
|
1172
1160
|
context;
|
|
1173
1161
|
cause;
|
|
1162
|
+
constructor(message, context, cause) {
|
|
1163
|
+
super(message), this.context = context, this.cause = cause;
|
|
1164
|
+
this.name = "PointIndexerFinalizeError";
|
|
1165
|
+
}
|
|
1174
1166
|
};
|
|
1175
|
-
var TRANSFER_EVENT = parseAbiItem(
|
|
1176
|
-
|
|
1177
|
-
);
|
|
1178
|
-
var MINT_WITH_FEE_EVENT = parseAbiItem(
|
|
1179
|
-
"event MintWithFee(address indexed pointToken, address indexed to, uint256 grossAmount, uint256 netAmount, uint256 feeAmount)"
|
|
1180
|
-
);
|
|
1167
|
+
var TRANSFER_EVENT = parseAbiItem("event Transfer(address indexed from, address indexed to, uint256 value)");
|
|
1168
|
+
var MINT_WITH_FEE_EVENT = parseAbiItem("event MintWithFee(address indexed pointToken, address indexed to, uint256 grossAmount, uint256 netAmount, uint256 feeAmount)");
|
|
1181
1169
|
var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
1182
1170
|
var DEAD_ADDRESS = "0x000000000000000000000000000000000000dEaD";
|
|
1183
1171
|
var DEFAULT_CONFIRMATIONS = 3;
|
|
@@ -1188,7 +1176,11 @@ function isNoWrapper(addr) {
|
|
|
1188
1176
|
const checksummed = getAddress3(addr);
|
|
1189
1177
|
return checksummed === ZERO_ADDRESS || checksummed === DEAD_ADDRESS;
|
|
1190
1178
|
}
|
|
1179
|
+
__name(isNoWrapper, "isNoWrapper");
|
|
1191
1180
|
var PointIndexer = class {
|
|
1181
|
+
static {
|
|
1182
|
+
__name(this, "PointIndexer");
|
|
1183
|
+
}
|
|
1192
1184
|
provider;
|
|
1193
1185
|
pointTokenAddress;
|
|
1194
1186
|
mintFeeWrapperAddress;
|
|
@@ -1203,8 +1195,7 @@ var PointIndexer = class {
|
|
|
1203
1195
|
timer;
|
|
1204
1196
|
constructor(config) {
|
|
1205
1197
|
if (!config.provider) throw new Error("PointIndexer: provider required");
|
|
1206
|
-
if (!config.pointTokenAddress)
|
|
1207
|
-
throw new Error("PointIndexer: pointTokenAddress required");
|
|
1198
|
+
if (!config.pointTokenAddress) throw new Error("PointIndexer: pointTokenAddress required");
|
|
1208
1199
|
if (!config.ledger) throw new Error("PointIndexer: ledger required");
|
|
1209
1200
|
this.provider = config.provider;
|
|
1210
1201
|
this.pointTokenAddress = getAddress3(config.pointTokenAddress);
|
|
@@ -1235,10 +1226,10 @@ var PointIndexer = class {
|
|
|
1235
1226
|
}
|
|
1236
1227
|
}
|
|
1237
1228
|
/**
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1229
|
+
* Run one poll cycle: load cursor → scan [cursor, safeHead] in
|
|
1230
|
+
* `batchSize` chunks → persist new cursor. Swallows any error and
|
|
1231
|
+
* schedules the next tick. Visible for test harnesses via a public name.
|
|
1232
|
+
*/
|
|
1242
1233
|
async tick() {
|
|
1243
1234
|
if (!this.running) return;
|
|
1244
1235
|
try {
|
|
@@ -1273,19 +1264,16 @@ var PointIndexer = class {
|
|
|
1273
1264
|
}
|
|
1274
1265
|
scheduleNext() {
|
|
1275
1266
|
if (!this.running) return;
|
|
1276
|
-
this.timer = setTimeout(
|
|
1277
|
-
() => this.tick().catch((err) => this.handleTickError(err)),
|
|
1278
|
-
this.pollIntervalMs
|
|
1279
|
-
);
|
|
1267
|
+
this.timer = setTimeout(() => this.tick().catch((err) => this.handleTickError(err)), this.pollIntervalMs);
|
|
1280
1268
|
}
|
|
1281
1269
|
// -------------------------------------------------------------------------
|
|
1282
1270
|
// Block scanning
|
|
1283
1271
|
// -------------------------------------------------------------------------
|
|
1284
1272
|
/**
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1273
|
+
* Scan `[from, to]` inclusive for mint events in `batchSize` chunks.
|
|
1274
|
+
* Callers can use this directly to backfill a specific range without
|
|
1275
|
+
* engaging `start()`. On completion, the cursor is advanced to `to + 1`.
|
|
1276
|
+
*/
|
|
1289
1277
|
async processBlockRange(from, to) {
|
|
1290
1278
|
if (from > to) return;
|
|
1291
1279
|
let cursor = from;
|
|
@@ -1309,15 +1297,17 @@ var PointIndexer = class {
|
|
|
1309
1297
|
// Event fetching — two modes (wrapper vs direct)
|
|
1310
1298
|
// -------------------------------------------------------------------------
|
|
1311
1299
|
/**
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1300
|
+
* Wrapper mode: listen for `MintWithFee` on the wrapper,
|
|
1301
|
+
* filtered to events for THIS pointToken only. The event's `to` field
|
|
1302
|
+
* is the actual end user, and `grossAmount` matches the lock amount.
|
|
1303
|
+
*/
|
|
1316
1304
|
async fetchWrapperMintEvents(fromBlock, toBlock) {
|
|
1317
1305
|
const logs = await this.provider.getLogs({
|
|
1318
1306
|
address: this.mintFeeWrapperAddress,
|
|
1319
1307
|
event: MINT_WITH_FEE_EVENT,
|
|
1320
|
-
args: {
|
|
1308
|
+
args: {
|
|
1309
|
+
pointToken: this.pointTokenAddress
|
|
1310
|
+
},
|
|
1321
1311
|
fromBlock,
|
|
1322
1312
|
toBlock
|
|
1323
1313
|
});
|
|
@@ -1339,14 +1329,16 @@ var PointIndexer = class {
|
|
|
1339
1329
|
return out;
|
|
1340
1330
|
}
|
|
1341
1331
|
/**
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1332
|
+
* Direct mode (legacy / chains without wrapper): listen for
|
|
1333
|
+
* `Transfer(from=0x0 → to)` on the PointToken itself.
|
|
1334
|
+
*/
|
|
1345
1335
|
async fetchTransferMintEvents(fromBlock, toBlock) {
|
|
1346
1336
|
const logs = await this.provider.getLogs({
|
|
1347
1337
|
address: this.pointTokenAddress,
|
|
1348
1338
|
event: TRANSFER_EVENT,
|
|
1349
|
-
args: {
|
|
1339
|
+
args: {
|
|
1340
|
+
from: ZERO_ADDRESS
|
|
1341
|
+
},
|
|
1350
1342
|
fromBlock,
|
|
1351
1343
|
toBlock
|
|
1352
1344
|
});
|
|
@@ -1373,42 +1365,30 @@ var PointIndexer = class {
|
|
|
1373
1365
|
// Finalization
|
|
1374
1366
|
// -------------------------------------------------------------------------
|
|
1375
1367
|
/**
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1368
|
+
* Finalize a single mint event: match it to a PENDING lock in the
|
|
1369
|
+
* ledger, then call `deductBalance` (which also resolves the lock in
|
|
1370
|
+
* the default `MemoryPointLedger`).
|
|
1371
|
+
*
|
|
1372
|
+
* No-matching-lock is a valid state: it means either the lock already
|
|
1373
|
+
* expired, or the mint was authorized out-of-band (e.g. a direct
|
|
1374
|
+
* `PointToken.mint()` from an EOA minter for testing). In that case we
|
|
1375
|
+
* do NOT touch the ledger — crediting here would silently allow the
|
|
1376
|
+
* issuer to mint without going through the gateway.
|
|
1377
|
+
*/
|
|
1386
1378
|
async finalize(evt) {
|
|
1387
|
-
const locks = await this.ledger.getLockedRequests(
|
|
1388
|
-
evt.to,
|
|
1389
|
-
this.pointTokenAddress
|
|
1390
|
-
);
|
|
1379
|
+
const locks = await this.ledger.getLockedRequests(evt.to, this.pointTokenAddress);
|
|
1391
1380
|
const match = pickMatchingLock(locks, evt.amount);
|
|
1392
1381
|
if (!match) return;
|
|
1393
1382
|
try {
|
|
1394
|
-
await this.ledger.deductBalance(
|
|
1395
|
-
evt.to,
|
|
1396
|
-
evt.amount,
|
|
1397
|
-
evt.txHash,
|
|
1398
|
-
this.pointTokenAddress
|
|
1399
|
-
);
|
|
1383
|
+
await this.ledger.deductBalance(evt.to, evt.amount, evt.txHash, this.pointTokenAddress);
|
|
1400
1384
|
} catch (err) {
|
|
1401
|
-
throw new PointIndexerFinalizeError(
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
blockNumber: evt.blockNumber
|
|
1409
|
-
},
|
|
1410
|
-
err
|
|
1411
|
-
);
|
|
1385
|
+
throw new PointIndexerFinalizeError(`PointIndexer.deductBalance failed for tx ${evt.txHash} (to=${evt.to}, amount=${evt.amount}, block=${evt.blockNumber}); cursor will NOT advance, next tick retries.`, {
|
|
1386
|
+
pointToken: this.pointTokenAddress,
|
|
1387
|
+
to: evt.to,
|
|
1388
|
+
amount: evt.amount,
|
|
1389
|
+
txHash: evt.txHash,
|
|
1390
|
+
blockNumber: evt.blockNumber
|
|
1391
|
+
}, err);
|
|
1412
1392
|
}
|
|
1413
1393
|
try {
|
|
1414
1394
|
await this.ledger.updateMintStatus(match.lockId, "MINTED", evt.txHash);
|
|
@@ -1427,32 +1407,35 @@ function pickMatchingLock(locks, amount) {
|
|
|
1427
1407
|
}
|
|
1428
1408
|
return best;
|
|
1429
1409
|
}
|
|
1410
|
+
__name(pickMatchingLock, "pickMatchingLock");
|
|
1430
1411
|
|
|
1431
1412
|
// src/indexer/burnIndexer.ts
|
|
1432
1413
|
import { getAddress as getAddress4, parseAbiItem as parseAbiItem2 } from "viem";
|
|
1433
1414
|
var BurnIndexerFinalizeError = class extends Error {
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
this.context = context;
|
|
1437
|
-
this.cause = cause;
|
|
1438
|
-
this.name = "BurnIndexerFinalizeError";
|
|
1415
|
+
static {
|
|
1416
|
+
__name(this, "BurnIndexerFinalizeError");
|
|
1439
1417
|
}
|
|
1440
1418
|
context;
|
|
1441
1419
|
cause;
|
|
1420
|
+
constructor(message, context, cause) {
|
|
1421
|
+
super(message), this.context = context, this.cause = cause;
|
|
1422
|
+
this.name = "BurnIndexerFinalizeError";
|
|
1423
|
+
}
|
|
1442
1424
|
};
|
|
1443
|
-
var TRANSFER_EVENT2 = parseAbiItem2(
|
|
1444
|
-
"event Transfer(address indexed from, address indexed to, uint256 value)"
|
|
1445
|
-
);
|
|
1425
|
+
var TRANSFER_EVENT2 = parseAbiItem2("event Transfer(address indexed from, address indexed to, uint256 value)");
|
|
1446
1426
|
var ZERO_ADDRESS2 = "0x0000000000000000000000000000000000000000";
|
|
1447
1427
|
var DEFAULT_CONFIRMATIONS2 = 3;
|
|
1448
1428
|
var DEFAULT_BATCH_SIZE2 = 2000n;
|
|
1449
1429
|
var DEFAULT_POLL_INTERVAL_MS2 = 5e3;
|
|
1450
1430
|
var BurnIndexer = class {
|
|
1431
|
+
static {
|
|
1432
|
+
__name(this, "BurnIndexer");
|
|
1433
|
+
}
|
|
1451
1434
|
provider;
|
|
1452
1435
|
/**
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1436
|
+
* The PointToken this indexer watches. Exposed so callers can key
|
|
1437
|
+
* leader-election locks / cursor stores by token
|
|
1438
|
+
*/
|
|
1456
1439
|
pointTokenAddress;
|
|
1457
1440
|
ledger;
|
|
1458
1441
|
cursorStore;
|
|
@@ -1466,24 +1449,19 @@ var BurnIndexer = class {
|
|
|
1466
1449
|
timer;
|
|
1467
1450
|
constructor(config) {
|
|
1468
1451
|
if (!config.provider) throw new Error("BurnIndexer: provider required");
|
|
1469
|
-
if (!config.pointTokenAddress)
|
|
1470
|
-
throw new Error("BurnIndexer: pointTokenAddress required");
|
|
1452
|
+
if (!config.pointTokenAddress) throw new Error("BurnIndexer: pointTokenAddress required");
|
|
1471
1453
|
if (!config.ledger) throw new Error("BurnIndexer: ledger required");
|
|
1472
1454
|
this.provider = config.provider;
|
|
1473
1455
|
this.pointTokenAddress = config.pointTokenAddress;
|
|
1474
1456
|
this.ledger = config.ledger;
|
|
1475
1457
|
this.cursorStore = config.cursorStore ?? new InMemoryCursorStore();
|
|
1476
1458
|
this.startBlock = config.fromBlock ?? 0n;
|
|
1477
|
-
this.confirmations = BigInt(
|
|
1478
|
-
config.confirmations ?? DEFAULT_CONFIRMATIONS2
|
|
1479
|
-
);
|
|
1459
|
+
this.confirmations = BigInt(config.confirmations ?? DEFAULT_CONFIRMATIONS2);
|
|
1480
1460
|
this.batchSize = BigInt(config.batchSize ?? Number(DEFAULT_BATCH_SIZE2));
|
|
1481
1461
|
this.pollIntervalMs = config.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS2;
|
|
1482
1462
|
if (config.onTickError) this.onTickError = config.onTickError;
|
|
1483
1463
|
if (!config.matchLockId) {
|
|
1484
|
-
throw new Error(
|
|
1485
|
-
"BurnIndexer: matchLockId is required. Provide a function that maps a burn event to its pending credit lockId. Without it, no on-chain burns will ever grant off-chain credits."
|
|
1486
|
-
);
|
|
1464
|
+
throw new Error("BurnIndexer: matchLockId is required. Provide a function that maps a burn event to its pending credit lockId. Without it, no on-chain burns will ever grant off-chain credits.");
|
|
1487
1465
|
}
|
|
1488
1466
|
this.matchLockId = config.matchLockId;
|
|
1489
1467
|
}
|
|
@@ -1533,16 +1511,13 @@ var BurnIndexer = class {
|
|
|
1533
1511
|
}
|
|
1534
1512
|
scheduleNext() {
|
|
1535
1513
|
if (!this.running) return;
|
|
1536
|
-
this.timer = setTimeout(
|
|
1537
|
-
() => this.tick().catch((err) => this.handleTickError(err)),
|
|
1538
|
-
this.pollIntervalMs
|
|
1539
|
-
);
|
|
1514
|
+
this.timer = setTimeout(() => this.tick().catch((err) => this.handleTickError(err)), this.pollIntervalMs);
|
|
1540
1515
|
}
|
|
1541
1516
|
/**
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1517
|
+
* Scan `[from, to]` inclusive for burn events. Callers can drive this
|
|
1518
|
+
* directly to backfill a specific range without `start()`. Cursor is
|
|
1519
|
+
* advanced to `to + 1` on completion.
|
|
1520
|
+
*/
|
|
1546
1521
|
async processBlockRange(from, to) {
|
|
1547
1522
|
if (from > to) return;
|
|
1548
1523
|
let cursor = from;
|
|
@@ -1551,8 +1526,9 @@ var BurnIndexer = class {
|
|
|
1551
1526
|
const logs = await this.provider.getLogs({
|
|
1552
1527
|
address: this.pointTokenAddress,
|
|
1553
1528
|
event: TRANSFER_EVENT2,
|
|
1554
|
-
args: {
|
|
1555
|
-
|
|
1529
|
+
args: {
|
|
1530
|
+
to: ZERO_ADDRESS2
|
|
1531
|
+
},
|
|
1556
1532
|
fromBlock: cursor,
|
|
1557
1533
|
toBlock: chunkEnd
|
|
1558
1534
|
});
|
|
@@ -1588,17 +1564,15 @@ var BurnIndexer = class {
|
|
|
1588
1564
|
return out;
|
|
1589
1565
|
}
|
|
1590
1566
|
/**
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1567
|
+
* Resolve a matching pending credit for this burn event and call
|
|
1568
|
+
* `ledger.resolveCreditByBurnTx(lockId, txHash)`. If no match found,
|
|
1569
|
+
* log + skip.
|
|
1570
|
+
*/
|
|
1595
1571
|
async finalize(evt) {
|
|
1596
1572
|
const txHash = evt.txHash;
|
|
1597
1573
|
const lockId = await this.matchLockId(evt);
|
|
1598
1574
|
if (lockId === void 0) {
|
|
1599
|
-
console.warn(
|
|
1600
|
-
"[PAFI] BurnIndexer: matchLockId returned undefined for burn tx " + txHash + ". This burn will NOT be credited. Implement matchLockId to map burn events to lock IDs."
|
|
1601
|
-
);
|
|
1575
|
+
console.warn("[PAFI] BurnIndexer: matchLockId returned undefined for burn tx " + txHash + ". This burn will NOT be credited. Implement matchLockId to map burn events to lock IDs.");
|
|
1602
1576
|
return;
|
|
1603
1577
|
}
|
|
1604
1578
|
if (!this.ledger.resolveCreditByBurnTx) {
|
|
@@ -1607,18 +1581,14 @@ var BurnIndexer = class {
|
|
|
1607
1581
|
try {
|
|
1608
1582
|
await this.ledger.resolveCreditByBurnTx(lockId, evt.txHash);
|
|
1609
1583
|
} catch (err) {
|
|
1610
|
-
throw new BurnIndexerFinalizeError(
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
lockId
|
|
1619
|
-
},
|
|
1620
|
-
err
|
|
1621
|
-
);
|
|
1584
|
+
throw new BurnIndexerFinalizeError(`BurnIndexer.resolveCreditByBurnTx failed for tx ${evt.txHash} (lockId=${lockId}, from=${evt.from}, amount=${evt.amount}, block=${evt.blockNumber}); cursor will NOT advance, next tick retries.`, {
|
|
1585
|
+
pointToken: this.pointTokenAddress,
|
|
1586
|
+
from: evt.from,
|
|
1587
|
+
amount: evt.amount,
|
|
1588
|
+
txHash: evt.txHash,
|
|
1589
|
+
blockNumber: evt.blockNumber,
|
|
1590
|
+
lockId
|
|
1591
|
+
}, err);
|
|
1622
1592
|
}
|
|
1623
1593
|
}
|
|
1624
1594
|
};
|
|
@@ -1628,10 +1598,9 @@ function makePostgresSingletonLock(runner) {
|
|
|
1628
1598
|
return {
|
|
1629
1599
|
async acquire(key) {
|
|
1630
1600
|
const lockId = hashKeyToInt64(key);
|
|
1631
|
-
const rows = await runner.query(
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
);
|
|
1601
|
+
const rows = await runner.query("SELECT pg_try_advisory_lock($1::bigint) AS got", [
|
|
1602
|
+
lockId
|
|
1603
|
+
]);
|
|
1635
1604
|
const got = rows[0]?.got === true;
|
|
1636
1605
|
if (!got) return null;
|
|
1637
1606
|
return {
|
|
@@ -1647,6 +1616,7 @@ function makePostgresSingletonLock(runner) {
|
|
|
1647
1616
|
}
|
|
1648
1617
|
};
|
|
1649
1618
|
}
|
|
1619
|
+
__name(makePostgresSingletonLock, "makePostgresSingletonLock");
|
|
1650
1620
|
function hashKeyToInt64(key) {
|
|
1651
1621
|
const FNV_OFFSET = 0xcbf29ce484222325n;
|
|
1652
1622
|
const FNV_PRIME = 0x100000001b3n;
|
|
@@ -1661,22 +1631,22 @@ function hashKeyToInt64(key) {
|
|
|
1661
1631
|
const signed = hash > SIGNED_MAX ? hash - TWO64 : hash;
|
|
1662
1632
|
return signed.toString();
|
|
1663
1633
|
}
|
|
1634
|
+
__name(hashKeyToInt64, "hashKeyToInt64");
|
|
1664
1635
|
|
|
1665
1636
|
// src/api/handlers.ts
|
|
1666
1637
|
import { getAddress as getAddress5 } from "viem";
|
|
1667
|
-
import {
|
|
1668
|
-
getMintFeeBps,
|
|
1669
|
-
getPointTokenBalance,
|
|
1670
|
-
isMinter
|
|
1671
|
-
} from "@pafi-dev/core";
|
|
1638
|
+
import { getMintFeeBps, getPointTokenBalance, isMinter } from "@pafi-dev/core";
|
|
1672
1639
|
var IssuerApiHandlers = class _IssuerApiHandlers {
|
|
1640
|
+
static {
|
|
1641
|
+
__name(this, "IssuerApiHandlers");
|
|
1642
|
+
}
|
|
1673
1643
|
authService;
|
|
1674
1644
|
ledger;
|
|
1675
1645
|
provider;
|
|
1676
1646
|
/**
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1647
|
+
* Set of supported PointToken addresses (checksum-normalized). Handlers
|
|
1648
|
+
* validate the request's `pointTokenAddress` against this set.
|
|
1649
|
+
*/
|
|
1680
1650
|
supportedTokens;
|
|
1681
1651
|
chainId;
|
|
1682
1652
|
contracts;
|
|
@@ -1687,10 +1657,10 @@ var IssuerApiHandlers = class _IssuerApiHandlers {
|
|
|
1687
1657
|
rateLimiter;
|
|
1688
1658
|
mintFeeWrapperAddress;
|
|
1689
1659
|
/**
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1660
|
+
* Per-token feeBps cache. Refreshed on /config when stale. feeBps
|
|
1661
|
+
* changes only when ops calls `wrapper.setRecipients`, so 5-min TTL
|
|
1662
|
+
* is more than safe for FE display purposes.
|
|
1663
|
+
*/
|
|
1694
1664
|
feeBpsCache = /* @__PURE__ */ new Map();
|
|
1695
1665
|
static FEE_BPS_TTL_MS = 5 * 60 * 1e3;
|
|
1696
1666
|
constructor(config) {
|
|
@@ -1698,11 +1668,11 @@ var IssuerApiHandlers = class _IssuerApiHandlers {
|
|
|
1698
1668
|
this.ledger = config.ledger;
|
|
1699
1669
|
this.provider = config.provider;
|
|
1700
1670
|
this.rateLimiter = config.rateLimiter ?? new NoopRateLimiter();
|
|
1701
|
-
const raw = config.pointTokenAddresses && config.pointTokenAddresses.length > 0 ? config.pointTokenAddresses : config.pointTokenAddress ? [
|
|
1671
|
+
const raw = config.pointTokenAddresses && config.pointTokenAddresses.length > 0 ? config.pointTokenAddresses : config.pointTokenAddress ? [
|
|
1672
|
+
config.pointTokenAddress
|
|
1673
|
+
] : [];
|
|
1702
1674
|
if (raw.length === 0) {
|
|
1703
|
-
throw new Error(
|
|
1704
|
-
"IssuerApiHandlers: pointTokenAddress or pointTokenAddresses required"
|
|
1705
|
-
);
|
|
1675
|
+
throw new Error("IssuerApiHandlers: pointTokenAddress or pointTokenAddresses required");
|
|
1706
1676
|
}
|
|
1707
1677
|
const normalized = raw.map((a) => getAddress5(a));
|
|
1708
1678
|
this.supportedTokens = new Set(normalized);
|
|
@@ -1720,63 +1690,48 @@ var IssuerApiHandlers = class _IssuerApiHandlers {
|
|
|
1720
1690
|
// Public handlers (no auth required)
|
|
1721
1691
|
// =========================================================================
|
|
1722
1692
|
/**
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1693
|
+
* `GET /auth/nonce`
|
|
1694
|
+
*
|
|
1695
|
+
* @param rateLimitKey Caller-side rate-limit key (typically client IP).
|
|
1696
|
+
* The HTTP layer (controller/middleware) extracts
|
|
1697
|
+
* this from the request and passes it through.
|
|
1698
|
+
* When omitted, no rate limit applies — production
|
|
1699
|
+
* callers SHOULD always pass a key.
|
|
1700
|
+
*/
|
|
1731
1701
|
async handleGetNonce(rateLimitKey) {
|
|
1732
1702
|
if (rateLimitKey) {
|
|
1733
|
-
const result = await this.rateLimiter.consume(
|
|
1734
|
-
rateLimitKey,
|
|
1735
|
-
"auth_nonce"
|
|
1736
|
-
);
|
|
1703
|
+
const result = await this.rateLimiter.consume(rateLimitKey, "auth_nonce");
|
|
1737
1704
|
if (!result.allowed) {
|
|
1738
|
-
throw new ValidationError(
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
retryAfterMs: result.retryAfterMs ?? 0,
|
|
1743
|
-
action: "auth_nonce"
|
|
1744
|
-
}
|
|
1745
|
-
);
|
|
1705
|
+
throw new ValidationError("RATE_LIMIT_EXCEEDED", "handleGetNonce: too many requests", {
|
|
1706
|
+
retryAfterMs: result.retryAfterMs ?? 0,
|
|
1707
|
+
action: "auth_nonce"
|
|
1708
|
+
});
|
|
1746
1709
|
}
|
|
1747
1710
|
}
|
|
1748
1711
|
const nonce = await this.authService.getNonce();
|
|
1749
|
-
return {
|
|
1712
|
+
return {
|
|
1713
|
+
nonce
|
|
1714
|
+
};
|
|
1750
1715
|
}
|
|
1751
1716
|
/**
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1717
|
+
* `POST /auth/login`
|
|
1718
|
+
*
|
|
1719
|
+
* @param body Login message + signature.
|
|
1720
|
+
* @param rateLimitKey Caller-side rate-limit key (typically client IP
|
|
1721
|
+
* or `body.userAddress` if known). See `handleGetNonce`.
|
|
1722
|
+
*/
|
|
1758
1723
|
async handleLogin(body, rateLimitKey) {
|
|
1759
1724
|
if (rateLimitKey) {
|
|
1760
|
-
const result2 = await this.rateLimiter.consume(
|
|
1761
|
-
rateLimitKey,
|
|
1762
|
-
"auth_login"
|
|
1763
|
-
);
|
|
1725
|
+
const result2 = await this.rateLimiter.consume(rateLimitKey, "auth_login");
|
|
1764
1726
|
if (!result2.allowed) {
|
|
1765
|
-
throw new ValidationError(
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
retryAfterMs: result2.retryAfterMs ?? 0,
|
|
1770
|
-
action: "auth_login"
|
|
1771
|
-
}
|
|
1772
|
-
);
|
|
1727
|
+
throw new ValidationError("RATE_LIMIT_EXCEEDED", "handleLogin: too many requests", {
|
|
1728
|
+
retryAfterMs: result2.retryAfterMs ?? 0,
|
|
1729
|
+
action: "auth_login"
|
|
1730
|
+
});
|
|
1773
1731
|
}
|
|
1774
1732
|
}
|
|
1775
1733
|
if (!body || typeof body.message !== "string" || body.message.length === 0 || typeof body.signature !== "string" || body.signature.length <= 2) {
|
|
1776
|
-
throw new ValidationError(
|
|
1777
|
-
"INVALID_LOGIN_BODY",
|
|
1778
|
-
"handleLogin: message and signature are required"
|
|
1779
|
-
);
|
|
1734
|
+
throw new ValidationError("INVALID_LOGIN_BODY", "handleLogin: message and signature are required");
|
|
1780
1735
|
}
|
|
1781
1736
|
if (body.message.length > 4096) {
|
|
1782
1737
|
throw new ValidationError("MESSAGE_TOO_LONG", "message too long");
|
|
@@ -1792,11 +1747,11 @@ var IssuerApiHandlers = class _IssuerApiHandlers {
|
|
|
1792
1747
|
};
|
|
1793
1748
|
}
|
|
1794
1749
|
/**
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1750
|
+
* `GET /config?chainId=<id>`
|
|
1751
|
+
*
|
|
1752
|
+
* Returns the contract addresses and chain id that the frontend SDK
|
|
1753
|
+
* needs to build EIP-712 messages and interact with on-chain.
|
|
1754
|
+
*/
|
|
1800
1755
|
async handleConfig(chainId) {
|
|
1801
1756
|
if (!Number.isInteger(chainId) || chainId <= 0) {
|
|
1802
1757
|
throw new ValidationError("INVALID_CHAIN_ID", "invalid chainId", {
|
|
@@ -1804,11 +1759,10 @@ var IssuerApiHandlers = class _IssuerApiHandlers {
|
|
|
1804
1759
|
});
|
|
1805
1760
|
}
|
|
1806
1761
|
if (chainId !== this.chainId) {
|
|
1807
|
-
throw new ValidationError(
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
);
|
|
1762
|
+
throw new ValidationError("UNSUPPORTED_CHAIN_ID", `handleConfig: unsupported chainId ${chainId}`, {
|
|
1763
|
+
requested: chainId,
|
|
1764
|
+
supported: this.chainId
|
|
1765
|
+
});
|
|
1812
1766
|
}
|
|
1813
1767
|
const contracts = {
|
|
1814
1768
|
...this.contracts,
|
|
@@ -1822,60 +1776,55 @@ var IssuerApiHandlers = class _IssuerApiHandlers {
|
|
|
1822
1776
|
contracts
|
|
1823
1777
|
};
|
|
1824
1778
|
if (this.mintFeeWrapperAddress) {
|
|
1825
|
-
const byToken = await this.resolveFeeBpsByToken(
|
|
1826
|
-
this.mintFeeWrapperAddress
|
|
1827
|
-
);
|
|
1779
|
+
const byToken = await this.resolveFeeBpsByToken(this.mintFeeWrapperAddress);
|
|
1828
1780
|
if (byToken) response.mintFeeBpsByToken = byToken;
|
|
1829
1781
|
}
|
|
1830
1782
|
if (this.pafiWebUrl) response.pafiWebUrl = this.pafiWebUrl;
|
|
1831
1783
|
return response;
|
|
1832
1784
|
}
|
|
1833
1785
|
/**
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1786
|
+
* Read `totalFeeBps(pointToken)` for every supported PointToken from
|
|
1787
|
+
* the wrapper. Cached per-token for 5 minutes. Returns `undefined`
|
|
1788
|
+
* (caller drops the field) if every read fails — FE must treat
|
|
1789
|
+
* "field missing" as "fee unknown, do not display".
|
|
1790
|
+
*/
|
|
1839
1791
|
async resolveFeeBpsByToken(wrapper) {
|
|
1840
1792
|
const now = Date.now();
|
|
1841
1793
|
const out = {};
|
|
1842
1794
|
let anyFresh = false;
|
|
1843
|
-
await Promise.all(
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1795
|
+
await Promise.all(Array.from(this.supportedTokens).map(async (token) => {
|
|
1796
|
+
const cached = this.feeBpsCache.get(token);
|
|
1797
|
+
if (cached && cached.expiresAt > now) {
|
|
1798
|
+
out[token.toLowerCase()] = cached.value;
|
|
1799
|
+
anyFresh = true;
|
|
1800
|
+
return;
|
|
1801
|
+
}
|
|
1802
|
+
try {
|
|
1803
|
+
const bps = await getMintFeeBps(this.provider, wrapper, token);
|
|
1804
|
+
this.feeBpsCache.set(token, {
|
|
1805
|
+
value: bps,
|
|
1806
|
+
expiresAt: now + _IssuerApiHandlers.FEE_BPS_TTL_MS
|
|
1807
|
+
});
|
|
1808
|
+
out[token.toLowerCase()] = bps;
|
|
1809
|
+
anyFresh = true;
|
|
1810
|
+
} catch {
|
|
1811
|
+
if (cached) {
|
|
1847
1812
|
out[token.toLowerCase()] = cached.value;
|
|
1848
1813
|
anyFresh = true;
|
|
1849
|
-
return;
|
|
1850
1814
|
}
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
this.feeBpsCache.set(token, {
|
|
1854
|
-
value: bps,
|
|
1855
|
-
expiresAt: now + _IssuerApiHandlers.FEE_BPS_TTL_MS
|
|
1856
|
-
});
|
|
1857
|
-
out[token.toLowerCase()] = bps;
|
|
1858
|
-
anyFresh = true;
|
|
1859
|
-
} catch {
|
|
1860
|
-
if (cached) {
|
|
1861
|
-
out[token.toLowerCase()] = cached.value;
|
|
1862
|
-
anyFresh = true;
|
|
1863
|
-
}
|
|
1864
|
-
}
|
|
1865
|
-
})
|
|
1866
|
-
);
|
|
1815
|
+
}
|
|
1816
|
+
}));
|
|
1867
1817
|
return anyFresh ? out : void 0;
|
|
1868
1818
|
}
|
|
1869
1819
|
/** `GET /gas-fee` — quoted in USDT (6-decimal base units). */
|
|
1870
1820
|
async handleGasFee() {
|
|
1871
1821
|
if (!this.feeManager) {
|
|
1872
|
-
throw new ConfigurationError(
|
|
1873
|
-
"FEE_MANAGER_NOT_CONFIGURED",
|
|
1874
|
-
"handleGasFee: feeManager is not configured on this issuer"
|
|
1875
|
-
);
|
|
1822
|
+
throw new ConfigurationError("FEE_MANAGER_NOT_CONFIGURED", "handleGasFee: feeManager is not configured on this issuer");
|
|
1876
1823
|
}
|
|
1877
1824
|
const gasFeeUsdt = await this.feeManager.estimateGasFee();
|
|
1878
|
-
return {
|
|
1825
|
+
return {
|
|
1826
|
+
gasFeeUsdt
|
|
1827
|
+
};
|
|
1879
1828
|
}
|
|
1880
1829
|
// =========================================================================
|
|
1881
1830
|
// Protected handlers (JWT required — userAddress extracted by middleware)
|
|
@@ -1885,57 +1834,49 @@ var IssuerApiHandlers = class _IssuerApiHandlers {
|
|
|
1885
1834
|
await this.authService.logout(token);
|
|
1886
1835
|
}
|
|
1887
1836
|
/**
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1837
|
+
* `GET /pools?chainId=<id>&pointToken=<addr>`
|
|
1838
|
+
*
|
|
1839
|
+
* Delegates to the injected `PoolsProvider`. The handler itself does
|
|
1840
|
+
* not know where pools come from — that's an issuer decision.
|
|
1841
|
+
*/
|
|
1893
1842
|
async handlePools(_userAddress, request) {
|
|
1894
1843
|
if (!this.poolsProvider) {
|
|
1895
|
-
throw new ConfigurationError(
|
|
1896
|
-
"POOLS_PROVIDER_NOT_CONFIGURED",
|
|
1897
|
-
"handlePools: poolsProvider is not configured on this issuer"
|
|
1898
|
-
);
|
|
1844
|
+
throw new ConfigurationError("POOLS_PROVIDER_NOT_CONFIGURED", "handlePools: poolsProvider is not configured on this issuer");
|
|
1899
1845
|
}
|
|
1900
1846
|
if (request.chainId !== this.chainId) {
|
|
1901
|
-
throw new ValidationError(
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
);
|
|
1847
|
+
throw new ValidationError("UNSUPPORTED_CHAIN_ID", `handlePools: unsupported chainId ${request.chainId}`, {
|
|
1848
|
+
requested: request.chainId,
|
|
1849
|
+
supported: this.chainId
|
|
1850
|
+
});
|
|
1906
1851
|
}
|
|
1907
1852
|
return this.poolsProvider(request);
|
|
1908
1853
|
}
|
|
1909
1854
|
/**
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1855
|
+
* `GET /user?chainId=<id>&user=<addr>&pointToken=<addr>`
|
|
1856
|
+
*
|
|
1857
|
+
* Returns per-user state the frontend needs to build a fresh mint:
|
|
1858
|
+
* on-chain nonces + minter status + off-chain balance.
|
|
1859
|
+
*/
|
|
1915
1860
|
async handleUser(userAddress, request) {
|
|
1916
1861
|
if (request.chainId !== this.chainId) {
|
|
1917
|
-
throw new ValidationError(
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
);
|
|
1862
|
+
throw new ValidationError("UNSUPPORTED_CHAIN_ID", `handleUser: unsupported chainId ${request.chainId}`, {
|
|
1863
|
+
requested: request.chainId,
|
|
1864
|
+
supported: this.chainId
|
|
1865
|
+
});
|
|
1922
1866
|
}
|
|
1923
1867
|
const normalizedAuthed = getAddress5(userAddress);
|
|
1924
1868
|
const normalizedRequest = getAddress5(request.userAddress);
|
|
1925
1869
|
if (normalizedAuthed !== normalizedRequest) {
|
|
1926
|
-
throw new ValidationError(
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
);
|
|
1870
|
+
throw new ValidationError("USER_ADDRESS_MISMATCH", "handleUser: request userAddress must match authenticated user", {
|
|
1871
|
+
authenticated: normalizedAuthed,
|
|
1872
|
+
requested: normalizedRequest
|
|
1873
|
+
});
|
|
1931
1874
|
}
|
|
1932
1875
|
const pointToken = getAddress5(request.pointTokenAddress);
|
|
1933
1876
|
if (!this.supportedTokens.has(pointToken)) {
|
|
1934
|
-
throw new ValidationError(
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
{ requested: pointToken }
|
|
1938
|
-
);
|
|
1877
|
+
throw new ValidationError("UNSUPPORTED_POINT_TOKEN", `handleUser: unsupported pointToken ${pointToken}`, {
|
|
1878
|
+
requested: pointToken
|
|
1879
|
+
});
|
|
1939
1880
|
}
|
|
1940
1881
|
const [offChainBalance, onChainBalance, minter] = await Promise.all([
|
|
1941
1882
|
this.ledger.getBalance(normalizedAuthed, pointToken),
|
|
@@ -1947,7 +1888,6 @@ var IssuerApiHandlers = class _IssuerApiHandlers {
|
|
|
1947
1888
|
onChainBalance,
|
|
1948
1889
|
totalBalance: offChainBalance + onChainBalance,
|
|
1949
1890
|
balance: offChainBalance,
|
|
1950
|
-
// deprecated alias
|
|
1951
1891
|
isMinter: minter
|
|
1952
1892
|
};
|
|
1953
1893
|
}
|
|
@@ -1955,56 +1895,41 @@ var IssuerApiHandlers = class _IssuerApiHandlers {
|
|
|
1955
1895
|
// removed in 0.5.43 — callers should use `PTClaimHandler` directly or
|
|
1956
1896
|
// wire `IssuerApiAdapter.claim()` which composes the full flow.
|
|
1957
1897
|
/**
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1898
|
+
* `GET /redemption/preview?pointToken=<addr>`
|
|
1899
|
+
*
|
|
1900
|
+
* Returns the headroom currently available to `userAddress` under the
|
|
1901
|
+
* configured RedemptionPolicy. Pure read — does not record anything.
|
|
1902
|
+
* Use this for UI to render "X PT redeemable now / next available at …".
|
|
1903
|
+
*/
|
|
1964
1904
|
async handleRedemptionPreview(userAddress, request) {
|
|
1965
1905
|
if (!this.redemption) {
|
|
1966
|
-
throw new ConfigurationError(
|
|
1967
|
-
"REDEMPTION_NOT_CONFIGURED",
|
|
1968
|
-
"handleRedemptionPreview: redemption is not configured on this issuer"
|
|
1969
|
-
);
|
|
1906
|
+
throw new ConfigurationError("REDEMPTION_NOT_CONFIGURED", "handleRedemptionPreview: redemption is not configured on this issuer");
|
|
1970
1907
|
}
|
|
1971
1908
|
const tokenAddress = request.pointTokenAddress ? this.requireSupportedToken(getAddress5(request.pointTokenAddress), "handleRedemptionPreview") : void 0;
|
|
1972
|
-
const preview = await this.redemption.preview(
|
|
1973
|
-
getAddress5(userAddress),
|
|
1974
|
-
tokenAddress
|
|
1975
|
-
);
|
|
1909
|
+
const preview = await this.redemption.preview(getAddress5(userAddress), tokenAddress);
|
|
1976
1910
|
return preview;
|
|
1977
1911
|
}
|
|
1978
1912
|
/**
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1913
|
+
* `POST /redemption/evaluate`
|
|
1914
|
+
*
|
|
1915
|
+
* Pre-flight check before the issuer signs a BurnRequest. Returns
|
|
1916
|
+
* { allowed, denial?, preview }. Caller (the burn-orchestrator) MUST
|
|
1917
|
+
* re-check on the actual initiate path — evaluate is read-only and a
|
|
1918
|
+
* caller could race two requests under the same headroom. The intended
|
|
1919
|
+
* write path is: evaluate → sign BurnRequest → reserve pending credit
|
|
1920
|
+
* → call `service.redemption.recordSuccessfulInitiate()`.
|
|
1921
|
+
*/
|
|
1988
1922
|
async handleRedemptionEvaluate(userAddress, request) {
|
|
1989
1923
|
if (!this.redemption) {
|
|
1990
|
-
throw new ConfigurationError(
|
|
1991
|
-
"REDEMPTION_NOT_CONFIGURED",
|
|
1992
|
-
"handleRedemptionEvaluate: redemption is not configured on this issuer"
|
|
1993
|
-
);
|
|
1924
|
+
throw new ConfigurationError("REDEMPTION_NOT_CONFIGURED", "handleRedemptionEvaluate: redemption is not configured on this issuer");
|
|
1994
1925
|
}
|
|
1995
1926
|
if (request.amountPt <= 0n) {
|
|
1996
|
-
throw new ValidationError(
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
{ amountPt: request.amountPt.toString() }
|
|
2000
|
-
);
|
|
1927
|
+
throw new ValidationError("INVALID_AMOUNT", "handleRedemptionEvaluate: amountPt must be positive", {
|
|
1928
|
+
amountPt: request.amountPt.toString()
|
|
1929
|
+
});
|
|
2001
1930
|
}
|
|
2002
1931
|
const tokenAddress = request.pointTokenAddress ? this.requireSupportedToken(getAddress5(request.pointTokenAddress), "handleRedemptionEvaluate") : void 0;
|
|
2003
|
-
const decision = await this.redemption.evaluate(
|
|
2004
|
-
getAddress5(userAddress),
|
|
2005
|
-
request.amountPt,
|
|
2006
|
-
tokenAddress
|
|
2007
|
-
);
|
|
1932
|
+
const decision = await this.redemption.evaluate(getAddress5(userAddress), request.amountPt, tokenAddress);
|
|
2008
1933
|
const response = {
|
|
2009
1934
|
allowed: decision.allowed,
|
|
2010
1935
|
preview: decision.preview
|
|
@@ -2014,11 +1939,9 @@ var IssuerApiHandlers = class _IssuerApiHandlers {
|
|
|
2014
1939
|
}
|
|
2015
1940
|
requireSupportedToken(pointToken, handler) {
|
|
2016
1941
|
if (!this.supportedTokens.has(pointToken)) {
|
|
2017
|
-
throw new ValidationError(
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
{ requested: pointToken }
|
|
2021
|
-
);
|
|
1942
|
+
throw new ValidationError("UNSUPPORTED_POINT_TOKEN", `${handler}: unsupported pointToken ${pointToken}`, {
|
|
1943
|
+
requested: pointToken
|
|
1944
|
+
});
|
|
2022
1945
|
}
|
|
2023
1946
|
return pointToken;
|
|
2024
1947
|
}
|
|
@@ -2032,10 +1955,17 @@ var NAME_ABI = [
|
|
|
2032
1955
|
name: "name",
|
|
2033
1956
|
stateMutability: "view",
|
|
2034
1957
|
inputs: [],
|
|
2035
|
-
outputs: [
|
|
1958
|
+
outputs: [
|
|
1959
|
+
{
|
|
1960
|
+
type: "string"
|
|
1961
|
+
}
|
|
1962
|
+
]
|
|
2036
1963
|
}
|
|
2037
1964
|
];
|
|
2038
1965
|
var PointTokenDomainResolver = class {
|
|
1966
|
+
static {
|
|
1967
|
+
__name(this, "PointTokenDomainResolver");
|
|
1968
|
+
}
|
|
2039
1969
|
provider;
|
|
2040
1970
|
overrides;
|
|
2041
1971
|
cache = /* @__PURE__ */ new Map();
|
|
@@ -2077,17 +2007,14 @@ var PointTokenDomainResolver = class {
|
|
|
2077
2007
|
|
|
2078
2008
|
// src/api/handlers/ptRedeemHandler.ts
|
|
2079
2009
|
import { getAddress as getAddress7 } from "viem";
|
|
2080
|
-
import {
|
|
2081
|
-
signBurnRequest,
|
|
2082
|
-
POINT_TOKEN_ABI,
|
|
2083
|
-
getPointTokenBalance as getPointTokenBalance2,
|
|
2084
|
-
getContractAddresses as getContractAddresses2,
|
|
2085
|
-
Source as Source2
|
|
2086
|
-
} from "@pafi-dev/core";
|
|
2010
|
+
import { signBurnRequest, POINT_TOKEN_ABI, getPointTokenBalance as getPointTokenBalance2, getContractAddresses as getContractAddresses2, Source as Source2 } from "@pafi-dev/core";
|
|
2087
2011
|
var DEFAULT_REDEEM_LOCK_MS = 15 * 60 * 1e3;
|
|
2088
2012
|
var M11_SAFETY_MARGIN_MS = 30 * 1e3;
|
|
2089
2013
|
var DEFAULT_SIG_DEADLINE_SEC = (DEFAULT_REDEEM_LOCK_MS - M11_SAFETY_MARGIN_MS) / 1e3;
|
|
2090
2014
|
var PTRedeemError = class extends PafiSdkError {
|
|
2015
|
+
static {
|
|
2016
|
+
__name(this, "PTRedeemError");
|
|
2017
|
+
}
|
|
2091
2018
|
httpStatus = "unprocessable";
|
|
2092
2019
|
code;
|
|
2093
2020
|
policyDenialCode;
|
|
@@ -2100,6 +2027,9 @@ var PTRedeemError = class extends PafiSdkError {
|
|
|
2100
2027
|
}
|
|
2101
2028
|
};
|
|
2102
2029
|
var PTRedeemHandler = class {
|
|
2030
|
+
static {
|
|
2031
|
+
__name(this, "PTRedeemHandler");
|
|
2032
|
+
}
|
|
2103
2033
|
ledger;
|
|
2104
2034
|
relayService;
|
|
2105
2035
|
provider;
|
|
@@ -2114,31 +2044,25 @@ var PTRedeemHandler = class {
|
|
|
2114
2044
|
now;
|
|
2115
2045
|
redemptionService;
|
|
2116
2046
|
/**
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2047
|
+
* Per-user in-flight nonce guard (single-process only).
|
|
2048
|
+
*
|
|
2049
|
+
* Prevents two concurrent requests from reading the same on-chain
|
|
2050
|
+
* burnRequestNonce before either has completed, which would produce two
|
|
2051
|
+
* signed UserOps with the same nonce — only one succeeds on-chain; the
|
|
2052
|
+
* other leaves an orphaned pending credit and a wasted signer call.
|
|
2053
|
+
*
|
|
2054
|
+
* NOTE: This guard is effective only within a single Node.js process. For
|
|
2055
|
+
* multi-instance deployments (k8s, PM2 cluster), enforce mutual exclusion
|
|
2056
|
+
* via a distributed lock (Redis SETNX / Postgres advisory lock) keyed on
|
|
2057
|
+
* `(userAddress, pointTokenAddress)` BEFORE calling `handle()`.
|
|
2058
|
+
*/
|
|
2129
2059
|
inFlightNonces = /* @__PURE__ */ new Map();
|
|
2130
2060
|
constructor(config) {
|
|
2131
2061
|
if (!config.ledger.reservePendingCredit) {
|
|
2132
|
-
throw new PTRedeemError(
|
|
2133
|
-
"LEDGER_NOT_SUPPORTED",
|
|
2134
|
-
"PTRedeemHandler requires a ledger that implements reservePendingCredit() (v0.3.0+)"
|
|
2135
|
-
);
|
|
2062
|
+
throw new PTRedeemError("LEDGER_NOT_SUPPORTED", "PTRedeemHandler requires a ledger that implements reservePendingCredit() (v0.3.0+)");
|
|
2136
2063
|
}
|
|
2137
2064
|
if (!config.burnerSignerWallet) {
|
|
2138
|
-
throw new PTRedeemError(
|
|
2139
|
-
"SIGNING_FAILED",
|
|
2140
|
-
"PTRedeemHandler requires burnerSignerWallet (issuer burner signer)"
|
|
2141
|
-
);
|
|
2065
|
+
throw new PTRedeemError("SIGNING_FAILED", "PTRedeemHandler requires burnerSignerWallet (issuer burner signer)");
|
|
2142
2066
|
}
|
|
2143
2067
|
this.ledger = config.ledger;
|
|
2144
2068
|
this.relayService = config.relayService;
|
|
@@ -2149,10 +2073,7 @@ var PTRedeemHandler = class {
|
|
|
2149
2073
|
this.domainResolver = config.domainResolver;
|
|
2150
2074
|
this.burnerSignerWallet = config.burnerSignerWallet;
|
|
2151
2075
|
if (!config.supportedTokens) {
|
|
2152
|
-
throw new PTRedeemError(
|
|
2153
|
-
"UNSUPPORTED_POINT_TOKEN",
|
|
2154
|
-
"PTRedeemHandler requires `supportedTokens` (issuer's allow-listed PointToken contracts)."
|
|
2155
|
-
);
|
|
2076
|
+
throw new PTRedeemError("UNSUPPORTED_POINT_TOKEN", "PTRedeemHandler requires `supportedTokens` (issuer's allow-listed PointToken contracts).");
|
|
2156
2077
|
}
|
|
2157
2078
|
this.supportedTokens = config.supportedTokens;
|
|
2158
2079
|
if (this.burnerSignerWallet?.account?.type === "local") {
|
|
@@ -2163,10 +2084,7 @@ var PTRedeemHandler = class {
|
|
|
2163
2084
|
this.now = config.now ?? (() => Date.now());
|
|
2164
2085
|
const maxAllowedSignatureMs = this.redeemLockDurationMs - M11_SAFETY_MARGIN_MS;
|
|
2165
2086
|
if (this.signatureDeadlineSeconds * 1e3 > maxAllowedSignatureMs) {
|
|
2166
|
-
throw new PTRedeemError(
|
|
2167
|
-
"INVALID_AMOUNT",
|
|
2168
|
-
`PTRedeemHandler config: signatureDeadlineSeconds (${this.signatureDeadlineSeconds}s) must be at most redeemLockDurationMs - safety margin = ${maxAllowedSignatureMs / 1e3}s (redeemLockDurationMs=${this.redeemLockDurationMs / 1e3}s, safety=${M11_SAFETY_MARGIN_MS / 1e3}s).`
|
|
2169
|
-
);
|
|
2087
|
+
throw new PTRedeemError("INVALID_AMOUNT", `PTRedeemHandler config: signatureDeadlineSeconds (${this.signatureDeadlineSeconds}s) must be at most redeemLockDurationMs - safety margin = ${maxAllowedSignatureMs / 1e3}s (redeemLockDurationMs=${this.redeemLockDurationMs / 1e3}s, safety=${M11_SAFETY_MARGIN_MS / 1e3}s).`);
|
|
2170
2088
|
}
|
|
2171
2089
|
if (config.redemptionService) {
|
|
2172
2090
|
this.redemptionService = config.redemptionService;
|
|
@@ -2174,46 +2092,31 @@ var PTRedeemHandler = class {
|
|
|
2174
2092
|
}
|
|
2175
2093
|
async handle(request) {
|
|
2176
2094
|
if (getAddress7(request.authenticatedAddress) !== getAddress7(request.userAddress)) {
|
|
2177
|
-
throw new PTRedeemError(
|
|
2178
|
-
"UNAUTHORIZED",
|
|
2179
|
-
`userAddress (${request.userAddress}) does not match authenticated session (${request.authenticatedAddress})`
|
|
2180
|
-
);
|
|
2095
|
+
throw new PTRedeemError("UNAUTHORIZED", `userAddress (${request.userAddress}) does not match authenticated session (${request.authenticatedAddress})`);
|
|
2181
2096
|
}
|
|
2182
2097
|
if (request.amount <= 0n) {
|
|
2183
2098
|
throw new PTRedeemError("INVALID_AMOUNT", "redeem amount must be positive");
|
|
2184
2099
|
}
|
|
2185
2100
|
const pointTokenAddress = getAddress7(request.pointTokenAddress);
|
|
2186
2101
|
if (!this.supportedTokens.has(pointTokenAddress)) {
|
|
2187
|
-
throw new PTRedeemError(
|
|
2188
|
-
"UNSUPPORTED_POINT_TOKEN",
|
|
2189
|
-
`redeem: pointTokenAddress ${pointTokenAddress} is not in the issuer's supported-token allowlist. Check IssuerApiHandlers.supportedTokens and PTRedeemHandler.config.supportedTokens point at the same set.`
|
|
2190
|
-
);
|
|
2102
|
+
throw new PTRedeemError("UNSUPPORTED_POINT_TOKEN", `redeem: pointTokenAddress ${pointTokenAddress} is not in the issuer's supported-token allowlist. Check IssuerApiHandlers.supportedTokens and PTRedeemHandler.config.supportedTokens point at the same set.`);
|
|
2191
2103
|
}
|
|
2192
2104
|
if (this.redemptionService) {
|
|
2193
2105
|
let decision;
|
|
2194
2106
|
try {
|
|
2195
|
-
decision = await this.redemptionService.evaluate(
|
|
2196
|
-
request.userAddress,
|
|
2197
|
-
request.amount,
|
|
2198
|
-
pointTokenAddress
|
|
2199
|
-
);
|
|
2107
|
+
decision = await this.redemptionService.evaluate(request.userAddress, request.amount, pointTokenAddress);
|
|
2200
2108
|
} catch (err) {
|
|
2201
2109
|
const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
|
|
2202
2110
|
if (code === "POLICY_PROVIDER_UNAVAILABLE") {
|
|
2203
|
-
throw new PTRedeemError(
|
|
2204
|
-
"REDEMPTION_POLICY_UNAVAILABLE",
|
|
2205
|
-
"Redemption policy temporarily unavailable \u2014 please try again shortly."
|
|
2206
|
-
);
|
|
2111
|
+
throw new PTRedeemError("REDEMPTION_POLICY_UNAVAILABLE", "Redemption policy temporarily unavailable \u2014 please try again shortly.");
|
|
2207
2112
|
}
|
|
2208
2113
|
throw err;
|
|
2209
2114
|
}
|
|
2210
2115
|
if (!decision.allowed) {
|
|
2211
2116
|
const denial = decision.denial;
|
|
2212
|
-
throw new PTRedeemError(
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
{ policyDenialCode: denial.code }
|
|
2216
|
-
);
|
|
2117
|
+
throw new PTRedeemError("REDEMPTION_POLICY_DENIED", `redemption denied: ${denial.message}`, {
|
|
2118
|
+
policyDenialCode: denial.code
|
|
2119
|
+
});
|
|
2217
2120
|
}
|
|
2218
2121
|
}
|
|
2219
2122
|
let burnNonce;
|
|
@@ -2222,13 +2125,12 @@ var PTRedeemHandler = class {
|
|
|
2222
2125
|
address: pointTokenAddress,
|
|
2223
2126
|
abi: POINT_TOKEN_ABI,
|
|
2224
2127
|
functionName: "burnRequestNonces",
|
|
2225
|
-
args: [
|
|
2128
|
+
args: [
|
|
2129
|
+
request.userAddress
|
|
2130
|
+
]
|
|
2226
2131
|
});
|
|
2227
2132
|
} catch (err) {
|
|
2228
|
-
throw new PTRedeemError(
|
|
2229
|
-
"NONCE_READ_FAILED",
|
|
2230
|
-
`failed to read burnRequestNonces(${request.userAddress}): ${err instanceof Error ? err.message : String(err)}`
|
|
2231
|
-
);
|
|
2133
|
+
throw new PTRedeemError("NONCE_READ_FAILED", `failed to read burnRequestNonces(${request.userAddress}): ${err instanceof Error ? err.message : String(err)}`);
|
|
2232
2134
|
}
|
|
2233
2135
|
const nonceKey = `${getAddress7(request.userAddress).toLowerCase()}:${pointTokenAddress.toLowerCase()}`;
|
|
2234
2136
|
let userNonces = this.inFlightNonces.get(nonceKey);
|
|
@@ -2237,10 +2139,7 @@ var PTRedeemHandler = class {
|
|
|
2237
2139
|
this.inFlightNonces.set(nonceKey, userNonces);
|
|
2238
2140
|
}
|
|
2239
2141
|
if (userNonces.has(burnNonce)) {
|
|
2240
|
-
throw new PTRedeemError(
|
|
2241
|
-
"NONCE_IN_FLIGHT",
|
|
2242
|
-
`A burn request for nonce ${burnNonce} is already in progress for ${request.userAddress} on ${pointTokenAddress}. Retry after the current request completes.`
|
|
2243
|
-
);
|
|
2142
|
+
throw new PTRedeemError("NONCE_IN_FLIGHT", `A burn request for nonce ${burnNonce} is already in progress for ${request.userAddress} on ${pointTokenAddress}. Retry after the current request completes.`);
|
|
2244
2143
|
}
|
|
2245
2144
|
userNonces.add(burnNonce);
|
|
2246
2145
|
try {
|
|
@@ -2254,12 +2153,8 @@ var PTRedeemHandler = class {
|
|
|
2254
2153
|
const referenceMs = this.now();
|
|
2255
2154
|
const projectedLockExpiresAtMs = referenceMs + this.redeemLockDurationMs;
|
|
2256
2155
|
const requestedDeadlineSec = Math.floor(referenceMs / 1e3) + this.signatureDeadlineSeconds;
|
|
2257
|
-
const lockBoundedDeadlineSec = Math.floor(
|
|
2258
|
-
|
|
2259
|
-
);
|
|
2260
|
-
const previewDeadline = BigInt(
|
|
2261
|
-
Math.min(requestedDeadlineSec, lockBoundedDeadlineSec)
|
|
2262
|
-
);
|
|
2156
|
+
const lockBoundedDeadlineSec = Math.floor((projectedLockExpiresAtMs - M11_SAFETY_MARGIN_MS) / 1e3);
|
|
2157
|
+
const previewDeadline = BigInt(Math.min(requestedDeadlineSec, lockBoundedDeadlineSec));
|
|
2263
2158
|
let fee;
|
|
2264
2159
|
if (request.feeAmount !== void 0) {
|
|
2265
2160
|
fee = request.feeAmount > 0n ? request.feeAmount : 0n;
|
|
@@ -2285,21 +2180,11 @@ var PTRedeemHandler = class {
|
|
|
2285
2180
|
}
|
|
2286
2181
|
const feeRecipient = request.feeRecipient ?? (request.chainId !== void 0 ? getContractAddresses2(request.chainId).pafiFeeRecipient : getContractAddresses2(this.chainId).pafiFeeRecipient);
|
|
2287
2182
|
if (fee > 0n && fee >= request.amount) {
|
|
2288
|
-
throw new PTRedeemError(
|
|
2289
|
-
"INVALID_AMOUNT",
|
|
2290
|
-
`fee (${fee}) must be strictly less than redeem amount (${request.amount})`
|
|
2291
|
-
);
|
|
2183
|
+
throw new PTRedeemError("INVALID_AMOUNT", `fee (${fee}) must be strictly less than redeem amount (${request.amount})`);
|
|
2292
2184
|
}
|
|
2293
|
-
const onChainBalance = await getPointTokenBalance2(
|
|
2294
|
-
this.provider,
|
|
2295
|
-
pointTokenAddress,
|
|
2296
|
-
request.userAddress
|
|
2297
|
-
);
|
|
2185
|
+
const onChainBalance = await getPointTokenBalance2(this.provider, pointTokenAddress, request.userAddress);
|
|
2298
2186
|
if (onChainBalance < request.amount) {
|
|
2299
|
-
throw new PTRedeemError(
|
|
2300
|
-
"INVALID_AMOUNT",
|
|
2301
|
-
`insufficient on-chain PT balance: have ${onChainBalance}, need ${request.amount}`
|
|
2302
|
-
);
|
|
2187
|
+
throw new PTRedeemError("INVALID_AMOUNT", `insufficient on-chain PT balance: have ${onChainBalance}, need ${request.amount}`);
|
|
2303
2188
|
}
|
|
2304
2189
|
const deadline = previewDeadline;
|
|
2305
2190
|
const domainName = await this.domainResolver.resolve(pointTokenAddress);
|
|
@@ -2321,17 +2206,9 @@ var PTRedeemHandler = class {
|
|
|
2321
2206
|
try {
|
|
2322
2207
|
sponsoredSig = (await signBurnRequest(this.burnerSignerWallet, domain, sponsoredBurnRequest)).serialized;
|
|
2323
2208
|
} catch (err) {
|
|
2324
|
-
throw new PTRedeemError(
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
);
|
|
2328
|
-
}
|
|
2329
|
-
const sponsoredLockId = await this.ledger.reservePendingCredit(
|
|
2330
|
-
request.userAddress,
|
|
2331
|
-
sponsoredBurnAmount,
|
|
2332
|
-
this.redeemLockDurationMs,
|
|
2333
|
-
pointTokenAddress
|
|
2334
|
-
);
|
|
2209
|
+
throw new PTRedeemError("SIGNING_FAILED", `failed to sign sponsored BurnRequest: ${err instanceof Error ? err.message : String(err)}`);
|
|
2210
|
+
}
|
|
2211
|
+
const sponsoredLockId = await this.ledger.reservePendingCredit(request.userAddress, sponsoredBurnAmount, this.redeemLockDurationMs, pointTokenAddress);
|
|
2335
2212
|
try {
|
|
2336
2213
|
const sponsoredUserOp = await this.relayService.prepareBurn({
|
|
2337
2214
|
mode: "burnWithSig",
|
|
@@ -2357,17 +2234,9 @@ var PTRedeemHandler = class {
|
|
|
2357
2234
|
try {
|
|
2358
2235
|
fallbackSig = (await signBurnRequest(this.burnerSignerWallet, domain, fallbackBurnRequest)).serialized;
|
|
2359
2236
|
} catch (err) {
|
|
2360
|
-
throw new PTRedeemError(
|
|
2361
|
-
"SIGNING_FAILED",
|
|
2362
|
-
`failed to sign fallback BurnRequest: ${err instanceof Error ? err.message : String(err)}`
|
|
2363
|
-
);
|
|
2237
|
+
throw new PTRedeemError("SIGNING_FAILED", `failed to sign fallback BurnRequest: ${err instanceof Error ? err.message : String(err)}`);
|
|
2364
2238
|
}
|
|
2365
|
-
const fallbackLockId = await this.ledger.reservePendingCredit(
|
|
2366
|
-
request.userAddress,
|
|
2367
|
-
request.amount,
|
|
2368
|
-
this.redeemLockDurationMs,
|
|
2369
|
-
pointTokenAddress
|
|
2370
|
-
);
|
|
2239
|
+
const fallbackLockId = await this.ledger.reservePendingCredit(request.userAddress, request.amount, this.redeemLockDurationMs, pointTokenAddress);
|
|
2371
2240
|
let fallbackUserOp;
|
|
2372
2241
|
try {
|
|
2373
2242
|
fallbackUserOp = await this.relayService.prepareBurn({
|
|
@@ -2425,15 +2294,11 @@ var PTRedeemHandler = class {
|
|
|
2425
2294
|
var DEFAULT_STATUS_CONFIRMATIONS = 3;
|
|
2426
2295
|
async function isReceiptPastConfirmations(receipt, provider, confirmations, onWarning, handlerName) {
|
|
2427
2296
|
if (!provider) {
|
|
2428
|
-
onWarning?.(
|
|
2429
|
-
`${handlerName}: provider missing \u2014 cannot enforce confirmation depth; deferring receipt fallback to on-chain indexer.`
|
|
2430
|
-
);
|
|
2297
|
+
onWarning?.(`${handlerName}: provider missing \u2014 cannot enforce confirmation depth; deferring receipt fallback to on-chain indexer.`);
|
|
2431
2298
|
return false;
|
|
2432
2299
|
}
|
|
2433
2300
|
if (!receipt.blockNumber) {
|
|
2434
|
-
onWarning?.(
|
|
2435
|
-
`${handlerName}: receipt has no blockNumber \u2014 cannot enforce confirmation depth; deferring to indexer.`
|
|
2436
|
-
);
|
|
2301
|
+
onWarning?.(`${handlerName}: receipt has no blockNumber \u2014 cannot enforce confirmation depth; deferring to indexer.`);
|
|
2437
2302
|
return false;
|
|
2438
2303
|
}
|
|
2439
2304
|
const requiredConfs = BigInt(confirmations ?? DEFAULT_STATUS_CONFIRMATIONS);
|
|
@@ -2441,18 +2306,14 @@ async function isReceiptPastConfirmations(receipt, provider, confirmations, onWa
|
|
|
2441
2306
|
try {
|
|
2442
2307
|
receiptBlock = BigInt(receipt.blockNumber);
|
|
2443
2308
|
} catch {
|
|
2444
|
-
onWarning?.(
|
|
2445
|
-
`${handlerName}: malformed receipt blockNumber (${receipt.blockNumber}) \u2014 deferring to indexer.`
|
|
2446
|
-
);
|
|
2309
|
+
onWarning?.(`${handlerName}: malformed receipt blockNumber (${receipt.blockNumber}) \u2014 deferring to indexer.`);
|
|
2447
2310
|
return false;
|
|
2448
2311
|
}
|
|
2449
2312
|
let head;
|
|
2450
2313
|
try {
|
|
2451
2314
|
head = await provider.getBlockNumber();
|
|
2452
2315
|
} catch (err) {
|
|
2453
|
-
onWarning?.(
|
|
2454
|
-
`${handlerName}: getBlockNumber failed (${err instanceof Error ? err.message : String(err)}) \u2014 deferring to indexer.`
|
|
2455
|
-
);
|
|
2316
|
+
onWarning?.(`${handlerName}: getBlockNumber failed (${err instanceof Error ? err.message : String(err)}) \u2014 deferring to indexer.`);
|
|
2456
2317
|
return false;
|
|
2457
2318
|
}
|
|
2458
2319
|
const depth = head - receiptBlock;
|
|
@@ -2461,7 +2322,11 @@ async function isReceiptPastConfirmations(receipt, provider, confirmations, onWa
|
|
|
2461
2322
|
}
|
|
2462
2323
|
return true;
|
|
2463
2324
|
}
|
|
2464
|
-
|
|
2325
|
+
__name(isReceiptPastConfirmations, "isReceiptPastConfirmations");
|
|
2326
|
+
var LockNotFoundError = class LockNotFoundError2 extends PafiSdkError {
|
|
2327
|
+
static {
|
|
2328
|
+
__name(this, "LockNotFoundError");
|
|
2329
|
+
}
|
|
2465
2330
|
code = "LOCK_NOT_FOUND";
|
|
2466
2331
|
httpStatus = "not_found";
|
|
2467
2332
|
constructor() {
|
|
@@ -2470,14 +2335,9 @@ var LockNotFoundError = class extends PafiSdkError {
|
|
|
2470
2335
|
};
|
|
2471
2336
|
async function handleClaimStatus(params) {
|
|
2472
2337
|
if (!params.ledger.getMintLock) {
|
|
2473
|
-
throw new Error(
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
}
|
|
2477
|
-
const lock = await params.ledger.getMintLock(
|
|
2478
|
-
params.lockId,
|
|
2479
|
-
params.userAddress
|
|
2480
|
-
);
|
|
2338
|
+
throw new Error("handleClaimStatus: ledger does not implement `getMintLock` \u2014 implement the optional method on `IPointLedger` or write a custom status handler.");
|
|
2339
|
+
}
|
|
2340
|
+
const lock = await params.ledger.getMintLock(params.lockId, params.userAddress);
|
|
2481
2341
|
if (!lock || lock.userAddress.toLowerCase() !== params.userAddress.toLowerCase()) {
|
|
2482
2342
|
throw new LockNotFoundError();
|
|
2483
2343
|
}
|
|
@@ -2485,17 +2345,9 @@ async function handleClaimStatus(params) {
|
|
|
2485
2345
|
let txHash = lock.txHash ?? null;
|
|
2486
2346
|
if (status === "PENDING" && lock.userOpHash && params.pafiBackendClient) {
|
|
2487
2347
|
try {
|
|
2488
|
-
const receipt = await params.pafiBackendClient.getUserOpReceipt(
|
|
2489
|
-
lock.userOpHash
|
|
2490
|
-
);
|
|
2348
|
+
const receipt = await params.pafiBackendClient.getUserOpReceipt(lock.userOpHash);
|
|
2491
2349
|
if (receipt) {
|
|
2492
|
-
const passesConfirmationDepth = await isReceiptPastConfirmations(
|
|
2493
|
-
receipt,
|
|
2494
|
-
params.provider,
|
|
2495
|
-
params.confirmations,
|
|
2496
|
-
params.onWarning,
|
|
2497
|
-
"handleClaimStatus"
|
|
2498
|
-
);
|
|
2350
|
+
const passesConfirmationDepth = await isReceiptPastConfirmations(receipt, params.provider, params.confirmations, params.onWarning, "handleClaimStatus");
|
|
2499
2351
|
if (!passesConfirmationDepth) {
|
|
2500
2352
|
return {
|
|
2501
2353
|
lockId: lock.lockId,
|
|
@@ -2508,46 +2360,31 @@ async function handleClaimStatus(params) {
|
|
|
2508
2360
|
}
|
|
2509
2361
|
if (receipt.success && receipt.txHash) {
|
|
2510
2362
|
if (!lock.tokenAddress) {
|
|
2511
|
-
params.onWarning?.(
|
|
2512
|
-
`handleClaimStatus: lock ${lock.lockId} has no tokenAddress; falling back to status-only flip \u2014 atomic debit+flip cannot run on a legacy single-token row. Migrate the ledger to the multi-token schema.`
|
|
2513
|
-
);
|
|
2363
|
+
params.onWarning?.(`handleClaimStatus: lock ${lock.lockId} has no tokenAddress; falling back to status-only flip \u2014 atomic debit+flip cannot run on a legacy single-token row. Migrate the ledger to the multi-token schema.`);
|
|
2514
2364
|
await params.ledger.updateMintStatus(lock.lockId, "MINTED", receipt.txHash).catch((err) => {
|
|
2515
|
-
params.onWarning?.(
|
|
2516
|
-
`handleClaimStatus: ledger updateMintStatus failed for lock ${lock.lockId}: ${err}`
|
|
2517
|
-
);
|
|
2365
|
+
params.onWarning?.(`handleClaimStatus: ledger updateMintStatus failed for lock ${lock.lockId}: ${err}`);
|
|
2518
2366
|
});
|
|
2519
2367
|
status = "MINTED";
|
|
2520
2368
|
txHash = receipt.txHash;
|
|
2521
2369
|
} else {
|
|
2522
2370
|
try {
|
|
2523
|
-
await params.ledger.deductBalance(
|
|
2524
|
-
lock.userAddress,
|
|
2525
|
-
lock.amount,
|
|
2526
|
-
receipt.txHash,
|
|
2527
|
-
lock.tokenAddress
|
|
2528
|
-
);
|
|
2371
|
+
await params.ledger.deductBalance(lock.userAddress, lock.amount, receipt.txHash, lock.tokenAddress);
|
|
2529
2372
|
status = "MINTED";
|
|
2530
2373
|
txHash = receipt.txHash;
|
|
2531
2374
|
} catch (deductErr) {
|
|
2532
|
-
params.onWarning?.(
|
|
2533
|
-
`handleClaimStatus: deductBalance failed for lock ${lock.lockId}: ${deductErr}`
|
|
2534
|
-
);
|
|
2375
|
+
params.onWarning?.(`handleClaimStatus: deductBalance failed for lock ${lock.lockId}: ${deductErr}`);
|
|
2535
2376
|
}
|
|
2536
2377
|
}
|
|
2537
2378
|
} else {
|
|
2538
2379
|
await params.ledger.updateMintStatus(lock.lockId, "FAILED", receipt.txHash).catch((err) => {
|
|
2539
|
-
params.onWarning?.(
|
|
2540
|
-
`handleClaimStatus: ledger updateMintStatus failed for lock ${lock.lockId}: ${err}`
|
|
2541
|
-
);
|
|
2380
|
+
params.onWarning?.(`handleClaimStatus: ledger updateMintStatus failed for lock ${lock.lockId}: ${err}`);
|
|
2542
2381
|
});
|
|
2543
2382
|
status = "FAILED";
|
|
2544
2383
|
txHash = receipt.txHash;
|
|
2545
2384
|
}
|
|
2546
2385
|
}
|
|
2547
2386
|
} catch (err) {
|
|
2548
|
-
params.onWarning?.(
|
|
2549
|
-
`handleClaimStatus: bundler-receipt fallback failed for lock ${lock.lockId}: ${err}`
|
|
2550
|
-
);
|
|
2387
|
+
params.onWarning?.(`handleClaimStatus: bundler-receipt fallback failed for lock ${lock.lockId}: ${err}`);
|
|
2551
2388
|
}
|
|
2552
2389
|
}
|
|
2553
2390
|
return {
|
|
@@ -2559,16 +2396,12 @@ async function handleClaimStatus(params) {
|
|
|
2559
2396
|
expiresAt: new Date(lock.expiresAt).toISOString()
|
|
2560
2397
|
};
|
|
2561
2398
|
}
|
|
2399
|
+
__name(handleClaimStatus, "handleClaimStatus");
|
|
2562
2400
|
async function handleRedeemStatus(params) {
|
|
2563
2401
|
if (!params.ledger.getPendingCredit) {
|
|
2564
|
-
throw new Error(
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
}
|
|
2568
|
-
const credit = await params.ledger.getPendingCredit(
|
|
2569
|
-
params.lockId,
|
|
2570
|
-
params.userAddress
|
|
2571
|
-
);
|
|
2402
|
+
throw new Error("handleRedeemStatus: ledger does not implement `getPendingCredit` \u2014 implement the optional method on `IPointLedger` or write a custom status handler.");
|
|
2403
|
+
}
|
|
2404
|
+
const credit = await params.ledger.getPendingCredit(params.lockId, params.userAddress);
|
|
2572
2405
|
if (!credit || credit.userAddress.toLowerCase() !== params.userAddress.toLowerCase()) {
|
|
2573
2406
|
throw new LockNotFoundError();
|
|
2574
2407
|
}
|
|
@@ -2576,33 +2409,21 @@ async function handleRedeemStatus(params) {
|
|
|
2576
2409
|
let txHash = credit.txHash ?? null;
|
|
2577
2410
|
if (status === "PENDING" && credit.userOpHash && params.pafiBackendClient) {
|
|
2578
2411
|
try {
|
|
2579
|
-
const receipt = await params.pafiBackendClient.getUserOpReceipt(
|
|
2580
|
-
credit.userOpHash
|
|
2581
|
-
);
|
|
2412
|
+
const receipt = await params.pafiBackendClient.getUserOpReceipt(credit.userOpHash);
|
|
2582
2413
|
if (receipt && receipt.success) {
|
|
2583
|
-
const passesConfirmationDepth = await isReceiptPastConfirmations(
|
|
2584
|
-
receipt,
|
|
2585
|
-
params.provider,
|
|
2586
|
-
params.confirmations,
|
|
2587
|
-
params.onWarning,
|
|
2588
|
-
"handleRedeemStatus"
|
|
2589
|
-
);
|
|
2414
|
+
const passesConfirmationDepth = await isReceiptPastConfirmations(receipt, params.provider, params.confirmations, params.onWarning, "handleRedeemStatus");
|
|
2590
2415
|
if (passesConfirmationDepth) {
|
|
2591
2416
|
status = "RESOLVED";
|
|
2592
2417
|
txHash = receipt.txHash;
|
|
2593
2418
|
if (params.ledger.resolveCreditByBurnTx) {
|
|
2594
2419
|
await params.ledger.resolveCreditByBurnTx(credit.lockId, receipt.txHash).catch((err) => {
|
|
2595
|
-
params.onWarning?.(
|
|
2596
|
-
`handleRedeemStatus: resolveCreditByBurnTx failed for lock ${credit.lockId}: ${err}`
|
|
2597
|
-
);
|
|
2420
|
+
params.onWarning?.(`handleRedeemStatus: resolveCreditByBurnTx failed for lock ${credit.lockId}: ${err}`);
|
|
2598
2421
|
});
|
|
2599
2422
|
}
|
|
2600
2423
|
}
|
|
2601
2424
|
}
|
|
2602
2425
|
} catch (err) {
|
|
2603
|
-
params.onWarning?.(
|
|
2604
|
-
`handleRedeemStatus: bundler-receipt fallback failed for lock ${credit.lockId}: ${err}`
|
|
2605
|
-
);
|
|
2426
|
+
params.onWarning?.(`handleRedeemStatus: bundler-receipt fallback failed for lock ${credit.lockId}: ${err}`);
|
|
2606
2427
|
}
|
|
2607
2428
|
}
|
|
2608
2429
|
return {
|
|
@@ -2615,59 +2436,52 @@ async function handleRedeemStatus(params) {
|
|
|
2615
2436
|
resolvedAt: credit.resolvedAt ? new Date(credit.resolvedAt).toISOString() : null
|
|
2616
2437
|
};
|
|
2617
2438
|
}
|
|
2439
|
+
__name(handleRedeemStatus, "handleRedeemStatus");
|
|
2618
2440
|
|
|
2619
2441
|
// src/api/mobileHandlers.ts
|
|
2620
2442
|
import { getAddress as getAddress8 } from "viem";
|
|
2621
|
-
import {
|
|
2622
|
-
ENTRY_POINT_V08,
|
|
2623
|
-
parseEip7702DelegatedAddress
|
|
2624
|
-
} from "@pafi-dev/core";
|
|
2443
|
+
import { ENTRY_POINT_V08, parseEip7702DelegatedAddress } from "@pafi-dev/core";
|
|
2625
2444
|
|
|
2626
2445
|
// src/userop-store/serialize.ts
|
|
2627
2446
|
import { serializeUserOpToJsonRpc } from "@pafi-dev/core";
|
|
2628
2447
|
function serializeEntryToJsonRpc(entry, signature, variant = "sponsored") {
|
|
2629
2448
|
if (variant === "fallback") {
|
|
2630
2449
|
if (!entry.fallback) {
|
|
2631
|
-
throw new Error(
|
|
2632
|
-
"serializeEntryToJsonRpc: variant=fallback requested but the stored entry has no `fallback` branch \u2014 caller should resubmit with variant='sponsored' or re-prepare with a fee configured."
|
|
2633
|
-
);
|
|
2450
|
+
throw new Error("serializeEntryToJsonRpc: variant=fallback requested but the stored entry has no `fallback` branch \u2014 caller should resubmit with variant='sponsored' or re-prepare with a fee configured.");
|
|
2634
2451
|
}
|
|
2635
|
-
return serializeUserOpToJsonRpc(
|
|
2636
|
-
{
|
|
2637
|
-
sender: entry.sender,
|
|
2638
|
-
nonce: BigInt(entry.nonce),
|
|
2639
|
-
callData: entry.fallback.callData,
|
|
2640
|
-
callGasLimit: BigInt(entry.fallback.callGasLimit),
|
|
2641
|
-
verificationGasLimit: BigInt(entry.fallback.verificationGasLimit),
|
|
2642
|
-
preVerificationGas: BigInt(entry.fallback.preVerificationGas),
|
|
2643
|
-
maxFeePerGas: BigInt(entry.maxFeePerGas),
|
|
2644
|
-
maxPriorityFeePerGas: BigInt(entry.maxPriorityFeePerGas)
|
|
2645
|
-
// intentionally no paymaster — user pays ETH gas
|
|
2646
|
-
},
|
|
2647
|
-
signature
|
|
2648
|
-
);
|
|
2649
|
-
}
|
|
2650
|
-
return serializeUserOpToJsonRpc(
|
|
2651
|
-
{
|
|
2452
|
+
return serializeUserOpToJsonRpc({
|
|
2652
2453
|
sender: entry.sender,
|
|
2653
2454
|
nonce: BigInt(entry.nonce),
|
|
2654
|
-
callData: entry.callData,
|
|
2655
|
-
callGasLimit: BigInt(entry.callGasLimit),
|
|
2656
|
-
verificationGasLimit: BigInt(entry.verificationGasLimit),
|
|
2657
|
-
preVerificationGas: BigInt(entry.preVerificationGas),
|
|
2455
|
+
callData: entry.fallback.callData,
|
|
2456
|
+
callGasLimit: BigInt(entry.fallback.callGasLimit),
|
|
2457
|
+
verificationGasLimit: BigInt(entry.fallback.verificationGasLimit),
|
|
2458
|
+
preVerificationGas: BigInt(entry.fallback.preVerificationGas),
|
|
2658
2459
|
maxFeePerGas: BigInt(entry.maxFeePerGas),
|
|
2659
|
-
maxPriorityFeePerGas: BigInt(entry.maxPriorityFeePerGas)
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2460
|
+
maxPriorityFeePerGas: BigInt(entry.maxPriorityFeePerGas)
|
|
2461
|
+
}, signature);
|
|
2462
|
+
}
|
|
2463
|
+
return serializeUserOpToJsonRpc({
|
|
2464
|
+
sender: entry.sender,
|
|
2465
|
+
nonce: BigInt(entry.nonce),
|
|
2466
|
+
callData: entry.callData,
|
|
2467
|
+
callGasLimit: BigInt(entry.callGasLimit),
|
|
2468
|
+
verificationGasLimit: BigInt(entry.verificationGasLimit),
|
|
2469
|
+
preVerificationGas: BigInt(entry.preVerificationGas),
|
|
2470
|
+
maxFeePerGas: BigInt(entry.maxFeePerGas),
|
|
2471
|
+
maxPriorityFeePerGas: BigInt(entry.maxPriorityFeePerGas),
|
|
2472
|
+
paymaster: entry.paymaster,
|
|
2473
|
+
paymasterVerificationGasLimit: entry.paymasterVerificationGasLimit != null ? BigInt(entry.paymasterVerificationGasLimit) : void 0,
|
|
2474
|
+
paymasterPostOpGasLimit: entry.paymasterPostOpGasLimit != null ? BigInt(entry.paymasterPostOpGasLimit) : void 0,
|
|
2475
|
+
paymasterData: entry.paymasterData
|
|
2476
|
+
}, signature);
|
|
2667
2477
|
}
|
|
2478
|
+
__name(serializeEntryToJsonRpc, "serializeEntryToJsonRpc");
|
|
2668
2479
|
|
|
2669
2480
|
// src/userop-store/memoryStore.ts
|
|
2670
2481
|
var MemoryPendingUserOpStore = class {
|
|
2482
|
+
static {
|
|
2483
|
+
__name(this, "MemoryPendingUserOpStore");
|
|
2484
|
+
}
|
|
2671
2485
|
entries = /* @__PURE__ */ new Map();
|
|
2672
2486
|
now;
|
|
2673
2487
|
constructor(now = () => Date.now()) {
|
|
@@ -2694,29 +2508,7 @@ var MemoryPendingUserOpStore = class {
|
|
|
2694
2508
|
};
|
|
2695
2509
|
|
|
2696
2510
|
// src/userop-store/prepareUserOp.ts
|
|
2697
|
-
import {
|
|
2698
|
-
buildUserOpTypedData,
|
|
2699
|
-
computeUserOpHash
|
|
2700
|
-
} from "@pafi-dev/core";
|
|
2701
|
-
function serializeUserOpTypedData(td) {
|
|
2702
|
-
return {
|
|
2703
|
-
domain: td.domain,
|
|
2704
|
-
types: td.types,
|
|
2705
|
-
primaryType: td.primaryType,
|
|
2706
|
-
message: {
|
|
2707
|
-
sender: td.message.sender,
|
|
2708
|
-
nonce: `0x${td.message.nonce.toString(16)}`,
|
|
2709
|
-
initCode: td.message.initCode,
|
|
2710
|
-
callData: td.message.callData,
|
|
2711
|
-
accountGasLimits: td.message.accountGasLimits,
|
|
2712
|
-
preVerificationGas: `0x${td.message.preVerificationGas.toString(
|
|
2713
|
-
16
|
|
2714
|
-
)}`,
|
|
2715
|
-
gasFees: td.message.gasFees,
|
|
2716
|
-
paymasterAndData: td.message.paymasterAndData
|
|
2717
|
-
}
|
|
2718
|
-
};
|
|
2719
|
-
}
|
|
2511
|
+
import { computeUserOpHash } from "@pafi-dev/core";
|
|
2720
2512
|
function mergePaymasterFields(userOp, paymasterFields) {
|
|
2721
2513
|
if (!paymasterFields) return userOp;
|
|
2722
2514
|
const merged = {
|
|
@@ -2727,36 +2519,26 @@ function mergePaymasterFields(userOp, paymasterFields) {
|
|
|
2727
2519
|
}
|
|
2728
2520
|
return merged;
|
|
2729
2521
|
}
|
|
2522
|
+
__name(mergePaymasterFields, "mergePaymasterFields");
|
|
2730
2523
|
function applyPaymasterGasEstimates(partialUserOp, paymasterFields, chainId) {
|
|
2731
|
-
const userOp = mergePaymasterFields(
|
|
2732
|
-
partialUserOp,
|
|
2733
|
-
paymasterFields
|
|
2734
|
-
);
|
|
2524
|
+
const userOp = mergePaymasterFields(partialUserOp, paymasterFields);
|
|
2735
2525
|
return {
|
|
2736
2526
|
userOp,
|
|
2737
|
-
userOpHash: computeUserOpHash(userOp, chainId)
|
|
2738
|
-
typedData: serializeUserOpTypedData(buildUserOpTypedData(userOp, chainId))
|
|
2527
|
+
userOpHash: computeUserOpHash(userOp, chainId)
|
|
2739
2528
|
};
|
|
2740
2529
|
}
|
|
2530
|
+
__name(applyPaymasterGasEstimates, "applyPaymasterGasEstimates");
|
|
2741
2531
|
async function prepareMobileUserOp(params) {
|
|
2742
|
-
const sponsored = applyPaymasterGasEstimates(
|
|
2743
|
-
params.partialUserOp,
|
|
2744
|
-
params.paymasterFields,
|
|
2745
|
-
params.chainId
|
|
2746
|
-
);
|
|
2532
|
+
const sponsored = applyPaymasterGasEstimates(params.partialUserOp, params.paymasterFields, params.chainId);
|
|
2747
2533
|
const { userOp, userOpHash } = sponsored;
|
|
2748
2534
|
let fallback;
|
|
2749
2535
|
let fallbackEntry;
|
|
2750
2536
|
if (params.partialUserOpFallback) {
|
|
2751
|
-
fallback = applyPaymasterGasEstimates(
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
},
|
|
2757
|
-
void 0,
|
|
2758
|
-
params.chainId
|
|
2759
|
-
);
|
|
2537
|
+
fallback = applyPaymasterGasEstimates({
|
|
2538
|
+
...params.partialUserOpFallback,
|
|
2539
|
+
maxFeePerGas: userOp.maxFeePerGas,
|
|
2540
|
+
maxPriorityFeePerGas: userOp.maxPriorityFeePerGas
|
|
2541
|
+
}, void 0, params.chainId);
|
|
2760
2542
|
fallbackEntry = {
|
|
2761
2543
|
callData: fallback.userOp.callData,
|
|
2762
2544
|
callGasLimit: fallback.userOp.callGasLimit.toString(),
|
|
@@ -2793,23 +2575,24 @@ async function prepareMobileUserOp(params) {
|
|
|
2793
2575
|
entry
|
|
2794
2576
|
};
|
|
2795
2577
|
}
|
|
2578
|
+
__name(prepareMobileUserOp, "prepareMobileUserOp");
|
|
2796
2579
|
|
|
2797
2580
|
// src/pafi-backend/types.ts
|
|
2798
2581
|
var PafiBackendError = class extends Error {
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
this.code = code;
|
|
2802
|
-
this.httpStatus = httpStatus;
|
|
2803
|
-
this.details = details;
|
|
2804
|
-
this.name = "PafiBackendError";
|
|
2805
|
-
if (opts?.retryAfter !== void 0) this.retryAfter = opts.retryAfter;
|
|
2806
|
-
if (opts?.safeToRetry !== void 0) this.serverSafeToRetry = opts.safeToRetry;
|
|
2582
|
+
static {
|
|
2583
|
+
__name(this, "PafiBackendError");
|
|
2807
2584
|
}
|
|
2808
2585
|
code;
|
|
2809
2586
|
httpStatus;
|
|
2810
2587
|
details;
|
|
2811
2588
|
retryAfter;
|
|
2812
2589
|
serverSafeToRetry;
|
|
2590
|
+
constructor(code, message, httpStatus, details, opts) {
|
|
2591
|
+
super(message), this.code = code, this.httpStatus = httpStatus, this.details = details;
|
|
2592
|
+
this.name = "PafiBackendError";
|
|
2593
|
+
if (opts?.retryAfter !== void 0) this.retryAfter = opts.retryAfter;
|
|
2594
|
+
if (opts?.safeToRetry !== void 0) this.serverSafeToRetry = opts.safeToRetry;
|
|
2595
|
+
}
|
|
2813
2596
|
get safeToRetry() {
|
|
2814
2597
|
if (this.serverSafeToRetry !== void 0) return this.serverSafeToRetry;
|
|
2815
2598
|
switch (this.code) {
|
|
@@ -2831,15 +2614,19 @@ var PafiBackendError = class extends Error {
|
|
|
2831
2614
|
|
|
2832
2615
|
// src/pafi-backend/helpers.ts
|
|
2833
2616
|
var BundlerNotConfiguredError = class extends PafiSdkError {
|
|
2617
|
+
static {
|
|
2618
|
+
__name(this, "BundlerNotConfiguredError");
|
|
2619
|
+
}
|
|
2834
2620
|
code = "BUNDLER_NOT_CONFIGURED";
|
|
2835
2621
|
httpStatus = "service_unavailable";
|
|
2836
2622
|
constructor() {
|
|
2837
|
-
super(
|
|
2838
|
-
"PAFI backend client not configured \u2014 set PAFI_BACKEND_URL, PAFI_ISSUER_ID, PAFI_API_KEY to enable mobile submit."
|
|
2839
|
-
);
|
|
2623
|
+
super("PAFI backend client not configured \u2014 set PAFI_BACKEND_URL, PAFI_ISSUER_ID, PAFI_API_KEY to enable mobile submit.");
|
|
2840
2624
|
}
|
|
2841
2625
|
};
|
|
2842
2626
|
var BundlerRejectedError = class extends PafiSdkError {
|
|
2627
|
+
static {
|
|
2628
|
+
__name(this, "BundlerRejectedError");
|
|
2629
|
+
}
|
|
2843
2630
|
code = "BUNDLER_REJECTED";
|
|
2844
2631
|
httpStatus = "unprocessable";
|
|
2845
2632
|
cause;
|
|
@@ -2861,7 +2648,9 @@ async function requestPaymaster(params) {
|
|
|
2861
2648
|
function: fn,
|
|
2862
2649
|
pointToken: params.pointTokenAddress
|
|
2863
2650
|
},
|
|
2864
|
-
...params.eip7702Auth ? {
|
|
2651
|
+
...params.eip7702Auth ? {
|
|
2652
|
+
eip7702Auth: params.eip7702Auth
|
|
2653
|
+
} : {}
|
|
2865
2654
|
});
|
|
2866
2655
|
} catch (err) {
|
|
2867
2656
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -2872,6 +2661,7 @@ async function requestPaymaster(params) {
|
|
|
2872
2661
|
throw err;
|
|
2873
2662
|
}
|
|
2874
2663
|
}
|
|
2664
|
+
__name(requestPaymaster, "requestPaymaster");
|
|
2875
2665
|
function isTransientPaymasterError(code) {
|
|
2876
2666
|
switch (code) {
|
|
2877
2667
|
case "NETWORK_ERROR":
|
|
@@ -2884,6 +2674,7 @@ function isTransientPaymasterError(code) {
|
|
|
2884
2674
|
return false;
|
|
2885
2675
|
}
|
|
2886
2676
|
}
|
|
2677
|
+
__name(isTransientPaymasterError, "isTransientPaymasterError");
|
|
2887
2678
|
function defaultFunctionForScenario(scenario) {
|
|
2888
2679
|
switch (scenario) {
|
|
2889
2680
|
case "mint":
|
|
@@ -2898,6 +2689,7 @@ function defaultFunctionForScenario(scenario) {
|
|
|
2898
2689
|
return scenario;
|
|
2899
2690
|
}
|
|
2900
2691
|
}
|
|
2692
|
+
__name(defaultFunctionForScenario, "defaultFunctionForScenario");
|
|
2901
2693
|
async function relayUserOp(params) {
|
|
2902
2694
|
if (!params.client) {
|
|
2903
2695
|
throw new BundlerNotConfiguredError();
|
|
@@ -2908,36 +2700,43 @@ async function relayUserOp(params) {
|
|
|
2908
2700
|
entryPoint: params.entryPoint,
|
|
2909
2701
|
eip7702Auth: params.eip7702Auth
|
|
2910
2702
|
});
|
|
2911
|
-
return {
|
|
2703
|
+
return {
|
|
2704
|
+
userOpHash: result.userOpHash
|
|
2705
|
+
};
|
|
2912
2706
|
} catch (err) {
|
|
2913
2707
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2914
2708
|
throw new BundlerRejectedError(msg, err);
|
|
2915
2709
|
}
|
|
2916
2710
|
}
|
|
2711
|
+
__name(relayUserOp, "relayUserOp");
|
|
2917
2712
|
|
|
2918
2713
|
// src/api/mobileHandlers.ts
|
|
2919
2714
|
var PendingUserOpNotFoundError = class extends PafiSdkError {
|
|
2715
|
+
static {
|
|
2716
|
+
__name(this, "PendingUserOpNotFoundError");
|
|
2717
|
+
}
|
|
2920
2718
|
code = "PENDING_USEROP_NOT_FOUND";
|
|
2921
2719
|
httpStatus = "not_found";
|
|
2922
2720
|
constructor(lockId) {
|
|
2923
|
-
super(
|
|
2924
|
-
`No pending UserOp found for lockId ${lockId} \u2014 it may have expired or already been submitted.`
|
|
2925
|
-
);
|
|
2721
|
+
super(`No pending UserOp found for lockId ${lockId} \u2014 it may have expired or already been submitted.`);
|
|
2926
2722
|
}
|
|
2927
2723
|
};
|
|
2928
2724
|
var PendingUserOpForbiddenError = class extends PafiSdkError {
|
|
2725
|
+
static {
|
|
2726
|
+
__name(this, "PendingUserOpForbiddenError");
|
|
2727
|
+
}
|
|
2929
2728
|
code = "PENDING_USEROP_FORBIDDEN";
|
|
2930
2729
|
httpStatus = "forbidden";
|
|
2931
2730
|
constructor(lockId) {
|
|
2932
|
-
super(
|
|
2933
|
-
`Pending UserOp ${lockId} does not belong to the authenticated user.`
|
|
2934
|
-
);
|
|
2731
|
+
super(`Pending UserOp ${lockId} does not belong to the authenticated user.`);
|
|
2935
2732
|
}
|
|
2936
2733
|
};
|
|
2937
2734
|
async function handleMobilePrepare(params) {
|
|
2938
2735
|
const [fees, userCode] = await Promise.all([
|
|
2939
2736
|
params.provider.estimateFeesPerGas(),
|
|
2940
|
-
params.provider.getCode({
|
|
2737
|
+
params.provider.getCode({
|
|
2738
|
+
address: params.userAddress
|
|
2739
|
+
})
|
|
2941
2740
|
]);
|
|
2942
2741
|
const needsDelegation = !params.eip7702Auth && parseEip7702DelegatedAddress(userCode) === null;
|
|
2943
2742
|
const sponsoredOp = {
|
|
@@ -2971,6 +2770,7 @@ async function handleMobilePrepare(params) {
|
|
|
2971
2770
|
needsDelegation
|
|
2972
2771
|
};
|
|
2973
2772
|
}
|
|
2773
|
+
__name(handleMobilePrepare, "handleMobilePrepare");
|
|
2974
2774
|
async function handleMobileSubmit(params) {
|
|
2975
2775
|
const entry = await params.store.get(params.lockId);
|
|
2976
2776
|
if (!entry) {
|
|
@@ -2990,19 +2790,21 @@ async function handleMobileSubmit(params) {
|
|
|
2990
2790
|
const targetLockId = variant === "fallback" && entry.fallback?.lockId ? entry.fallback.lockId : params.lockId;
|
|
2991
2791
|
await params.bindUserOpHash(targetLockId, result.userOpHash);
|
|
2992
2792
|
await params.store.delete(params.lockId);
|
|
2993
|
-
return {
|
|
2793
|
+
return {
|
|
2794
|
+
userOpHash: result.userOpHash
|
|
2795
|
+
};
|
|
2994
2796
|
}
|
|
2797
|
+
__name(handleMobileSubmit, "handleMobileSubmit");
|
|
2995
2798
|
|
|
2996
2799
|
// src/api/handlers/ptClaimHandler.ts
|
|
2997
2800
|
import { getAddress as getAddress9 } from "viem";
|
|
2998
|
-
import {
|
|
2999
|
-
POINT_TOKEN_ABI as POINT_TOKEN_ABI2,
|
|
3000
|
-
decodeBatchExecuteCalls,
|
|
3001
|
-
getContractAddresses as getContractAddresses3
|
|
3002
|
-
} from "@pafi-dev/core";
|
|
2801
|
+
import { POINT_TOKEN_ABI as POINT_TOKEN_ABI2, decodeKernelExecuteCalls, getContractAddresses as getContractAddresses3 } from "@pafi-dev/core";
|
|
3003
2802
|
|
|
3004
2803
|
// src/issuer-state/types.ts
|
|
3005
2804
|
var IssuerStateError = class extends PafiSdkError {
|
|
2805
|
+
static {
|
|
2806
|
+
__name(this, "IssuerStateError");
|
|
2807
|
+
}
|
|
3006
2808
|
httpStatus = "unprocessable";
|
|
3007
2809
|
code;
|
|
3008
2810
|
details;
|
|
@@ -3017,6 +2819,9 @@ var IssuerStateError = class extends PafiSdkError {
|
|
|
3017
2819
|
|
|
3018
2820
|
// src/api/handlers/ptClaimHandler.ts
|
|
3019
2821
|
var PTClaimError = class extends PafiSdkError {
|
|
2822
|
+
static {
|
|
2823
|
+
__name(this, "PTClaimError");
|
|
2824
|
+
}
|
|
3020
2825
|
httpStatus = "unprocessable";
|
|
3021
2826
|
code;
|
|
3022
2827
|
details;
|
|
@@ -3031,32 +2836,29 @@ function isNoWrapper2(address) {
|
|
|
3031
2836
|
const lower = address.toLowerCase();
|
|
3032
2837
|
return lower === "0x0000000000000000000000000000000000000000" || lower === "0x000000000000000000000000000000000000dead";
|
|
3033
2838
|
}
|
|
2839
|
+
__name(isNoWrapper2, "isNoWrapper");
|
|
3034
2840
|
var DEFAULT_LOCK_MS = 15 * 60 * 1e3;
|
|
3035
2841
|
var M11_SAFETY_MARGIN_MS2 = 30 * 1e3;
|
|
3036
2842
|
var DEFAULT_SIG_DEADLINE_SEC2 = (DEFAULT_LOCK_MS - M11_SAFETY_MARGIN_MS2) / 1e3;
|
|
3037
2843
|
var PTClaimHandler = class {
|
|
2844
|
+
static {
|
|
2845
|
+
__name(this, "PTClaimHandler");
|
|
2846
|
+
}
|
|
3038
2847
|
cfg;
|
|
3039
2848
|
inFlightNonces = /* @__PURE__ */ new Map();
|
|
3040
2849
|
constructor(config) {
|
|
3041
2850
|
if (!config.supportedTokens) {
|
|
3042
|
-
throw new PTClaimError(
|
|
3043
|
-
"UNSUPPORTED_POINT_TOKEN",
|
|
3044
|
-
"PTClaimHandler requires `supportedTokens` (issuer's allow-listed PointToken contracts)."
|
|
3045
|
-
);
|
|
2851
|
+
throw new PTClaimError("UNSUPPORTED_POINT_TOKEN", "PTClaimHandler requires `supportedTokens` (issuer's allow-listed PointToken contracts).");
|
|
3046
2852
|
}
|
|
3047
2853
|
const lockDurationMs = config.lockDurationMs ?? DEFAULT_LOCK_MS;
|
|
3048
2854
|
const signatureDeadlineSeconds = config.signatureDeadlineSeconds ?? DEFAULT_SIG_DEADLINE_SEC2;
|
|
3049
2855
|
const maxAllowedSignatureMs = lockDurationMs - M11_SAFETY_MARGIN_MS2;
|
|
3050
2856
|
if (signatureDeadlineSeconds * 1e3 > maxAllowedSignatureMs) {
|
|
3051
|
-
throw new PTClaimError(
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
signatureDeadlineSeconds,
|
|
3057
|
-
maxAllowedSignatureSec: maxAllowedSignatureMs / 1e3
|
|
3058
|
-
}
|
|
3059
|
-
);
|
|
2857
|
+
throw new PTClaimError("VALIDATION_FAILED", `PTClaimHandler config: signatureDeadlineSeconds (${signatureDeadlineSeconds}s) must be at most lockDurationMs - safety margin = ${maxAllowedSignatureMs / 1e3}s (lockDurationMs=${lockDurationMs / 1e3}s, safety=${M11_SAFETY_MARGIN_MS2 / 1e3}s).`, {
|
|
2858
|
+
lockDurationMs,
|
|
2859
|
+
signatureDeadlineSeconds,
|
|
2860
|
+
maxAllowedSignatureSec: maxAllowedSignatureMs / 1e3
|
|
2861
|
+
});
|
|
3060
2862
|
}
|
|
3061
2863
|
this.cfg = {
|
|
3062
2864
|
...config,
|
|
@@ -3067,51 +2869,39 @@ var PTClaimHandler = class {
|
|
|
3067
2869
|
}
|
|
3068
2870
|
async handle(request) {
|
|
3069
2871
|
if (getAddress9(request.authenticatedAddress) !== getAddress9(request.userAddress)) {
|
|
3070
|
-
throw new PTClaimError(
|
|
3071
|
-
"VALIDATION_FAILED",
|
|
3072
|
-
`userAddress (${request.userAddress}) does not match authenticated session (${request.authenticatedAddress})`
|
|
3073
|
-
);
|
|
2872
|
+
throw new PTClaimError("VALIDATION_FAILED", `userAddress (${request.userAddress}) does not match authenticated session (${request.authenticatedAddress})`);
|
|
3074
2873
|
}
|
|
3075
2874
|
if (request.amount <= 0n) {
|
|
3076
2875
|
throw new PTClaimError("INVALID_AMOUNT", "claim amount must be positive");
|
|
3077
2876
|
}
|
|
3078
2877
|
const pointTokenAddress = getAddress9(request.pointTokenAddress);
|
|
3079
2878
|
if (!this.cfg.supportedTokens.has(pointTokenAddress)) {
|
|
3080
|
-
throw new PTClaimError(
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
{ requested: pointTokenAddress }
|
|
3084
|
-
);
|
|
2879
|
+
throw new PTClaimError("UNSUPPORTED_POINT_TOKEN", `claim: pointTokenAddress ${pointTokenAddress} is not in the issuer's supported-token allowlist. Check IssuerApiHandlers.supportedTokens and PTClaimHandler.config.supportedTokens point at the same set.`, {
|
|
2880
|
+
requested: pointTokenAddress
|
|
2881
|
+
});
|
|
3085
2882
|
}
|
|
3086
2883
|
if (this.cfg.issuerStateValidator) {
|
|
3087
2884
|
try {
|
|
3088
|
-
await this.cfg.issuerStateValidator.preValidateMint(
|
|
3089
|
-
request.pointTokenAddress,
|
|
3090
|
-
request.amount
|
|
3091
|
-
);
|
|
2885
|
+
await this.cfg.issuerStateValidator.preValidateMint(request.pointTokenAddress, request.amount);
|
|
3092
2886
|
} catch (err) {
|
|
3093
2887
|
if (err instanceof IssuerStateError) throw err;
|
|
3094
|
-
throw new PTClaimError(
|
|
3095
|
-
"VALIDATION_FAILED",
|
|
3096
|
-
`issuer-state pre-validate failed: ${err instanceof Error ? err.message : String(err)}`
|
|
3097
|
-
);
|
|
2888
|
+
throw new PTClaimError("VALIDATION_FAILED", `issuer-state pre-validate failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
3098
2889
|
}
|
|
3099
2890
|
}
|
|
3100
2891
|
const chainAddresses = getContractAddresses3(request.chainId);
|
|
3101
|
-
const {
|
|
2892
|
+
const { kernel: batchExecutorAddress } = chainAddresses;
|
|
3102
2893
|
let mintRequestNonce;
|
|
3103
2894
|
try {
|
|
3104
2895
|
mintRequestNonce = await this.cfg.provider.readContract({
|
|
3105
2896
|
address: request.pointTokenAddress,
|
|
3106
2897
|
abi: POINT_TOKEN_ABI2,
|
|
3107
2898
|
functionName: "mintRequestNonces",
|
|
3108
|
-
args: [
|
|
2899
|
+
args: [
|
|
2900
|
+
request.userAddress
|
|
2901
|
+
]
|
|
3109
2902
|
});
|
|
3110
2903
|
} catch (err) {
|
|
3111
|
-
throw new PTClaimError(
|
|
3112
|
-
"NONCE_READ_FAILED",
|
|
3113
|
-
`failed to read mintRequestNonces(${request.userAddress}): ${err instanceof Error ? err.message : String(err)}`
|
|
3114
|
-
);
|
|
2904
|
+
throw new PTClaimError("NONCE_READ_FAILED", `failed to read mintRequestNonces(${request.userAddress}): ${err instanceof Error ? err.message : String(err)}`);
|
|
3115
2905
|
}
|
|
3116
2906
|
const nonceKey = `${getAddress9(request.userAddress).toLowerCase()}:${request.pointTokenAddress.toLowerCase()}`;
|
|
3117
2907
|
let userNonces = this.inFlightNonces.get(nonceKey);
|
|
@@ -3120,11 +2910,11 @@ var PTClaimHandler = class {
|
|
|
3120
2910
|
this.inFlightNonces.set(nonceKey, userNonces);
|
|
3121
2911
|
}
|
|
3122
2912
|
if (userNonces.has(mintRequestNonce)) {
|
|
3123
|
-
throw new PTClaimError(
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
);
|
|
2913
|
+
throw new PTClaimError("NONCE_IN_FLIGHT", `concurrent claim for nonce ${mintRequestNonce} in progress; retry after the prior request completes`, {
|
|
2914
|
+
userAddress: request.userAddress,
|
|
2915
|
+
pointToken: request.pointTokenAddress,
|
|
2916
|
+
nonce: mintRequestNonce.toString()
|
|
2917
|
+
});
|
|
3128
2918
|
}
|
|
3129
2919
|
userNonces.add(mintRequestNonce);
|
|
3130
2920
|
const wrapperOverride = this.cfg.mintFeeWrapperAddress;
|
|
@@ -3133,20 +2923,11 @@ var PTClaimHandler = class {
|
|
|
3133
2923
|
try {
|
|
3134
2924
|
const lockCreatedAtMs = this.cfg.now();
|
|
3135
2925
|
const lockExpiresAtMs = lockCreatedAtMs + this.cfg.lockDurationMs;
|
|
3136
|
-
const lockId = await this.cfg.ledger.lockForMinting(
|
|
3137
|
-
request.userAddress,
|
|
3138
|
-
request.amount,
|
|
3139
|
-
this.cfg.lockDurationMs,
|
|
3140
|
-
request.pointTokenAddress
|
|
3141
|
-
);
|
|
2926
|
+
const lockId = await this.cfg.ledger.lockForMinting(request.userAddress, request.amount, this.cfg.lockDurationMs, request.pointTokenAddress);
|
|
3142
2927
|
try {
|
|
3143
2928
|
const requestedDeadlineSec = Math.floor(lockCreatedAtMs / 1e3) + this.cfg.signatureDeadlineSeconds;
|
|
3144
|
-
const lockBoundedDeadlineSec = Math.floor(
|
|
3145
|
-
|
|
3146
|
-
);
|
|
3147
|
-
const signatureDeadline = BigInt(
|
|
3148
|
-
Math.min(requestedDeadlineSec, lockBoundedDeadlineSec)
|
|
3149
|
-
);
|
|
2929
|
+
const lockBoundedDeadlineSec = Math.floor((lockExpiresAtMs - M11_SAFETY_MARGIN_MS2) / 1e3);
|
|
2930
|
+
const signatureDeadline = BigInt(Math.min(requestedDeadlineSec, lockBoundedDeadlineSec));
|
|
3150
2931
|
const previewUserOp = this.cfg.relayService.previewMintUserOp({
|
|
3151
2932
|
userAddress: request.userAddress,
|
|
3152
2933
|
aaNonce: request.aaNonce,
|
|
@@ -3165,15 +2946,12 @@ var PTClaimHandler = class {
|
|
|
3165
2946
|
}
|
|
3166
2947
|
}) : 0n;
|
|
3167
2948
|
if (feeAmount > 0n && feeAmount >= request.amount) {
|
|
3168
|
-
throw new PTClaimError(
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
);
|
|
2949
|
+
throw new PTClaimError("INVALID_AMOUNT", `fee (${feeAmount}) must be strictly less than claim amount (${request.amount})`, {
|
|
2950
|
+
feeAmount: feeAmount.toString(),
|
|
2951
|
+
amount: request.amount.toString()
|
|
2952
|
+
});
|
|
3173
2953
|
}
|
|
3174
|
-
const domainName = await this.cfg.domainResolver.resolve(
|
|
3175
|
-
request.pointTokenAddress
|
|
3176
|
-
);
|
|
2954
|
+
const domainName = await this.cfg.domainResolver.resolve(request.pointTokenAddress);
|
|
3177
2955
|
const domain = {
|
|
3178
2956
|
name: domainName,
|
|
3179
2957
|
chainId: request.chainId,
|
|
@@ -3203,10 +2981,7 @@ var PTClaimHandler = class {
|
|
|
3203
2981
|
feeAmount
|
|
3204
2982
|
});
|
|
3205
2983
|
} catch (err) {
|
|
3206
|
-
throw new PTClaimError(
|
|
3207
|
-
"BUILD_FAILED",
|
|
3208
|
-
`prepareMint failed: ${err instanceof Error ? err.message : String(err)}`
|
|
3209
|
-
);
|
|
2984
|
+
throw new PTClaimError("BUILD_FAILED", `prepareMint failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
3210
2985
|
}
|
|
3211
2986
|
let fallback;
|
|
3212
2987
|
if (feeAmount > 0n) {
|
|
@@ -3225,14 +3000,11 @@ var PTClaimHandler = class {
|
|
|
3225
3000
|
mintFeeWrapperAddress: resolvedWrapper
|
|
3226
3001
|
});
|
|
3227
3002
|
} catch (err) {
|
|
3228
|
-
throw new PTClaimError(
|
|
3229
|
-
"BUILD_FAILED",
|
|
3230
|
-
`prepareMint (fallback) failed: ${err instanceof Error ? err.message : String(err)}`
|
|
3231
|
-
);
|
|
3003
|
+
throw new PTClaimError("BUILD_FAILED", `prepareMint (fallback) failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
3232
3004
|
}
|
|
3233
3005
|
}
|
|
3234
|
-
const calls =
|
|
3235
|
-
const callsFallback = fallback ?
|
|
3006
|
+
const calls = decodeKernelExecuteCalls(userOp.callData);
|
|
3007
|
+
const callsFallback = fallback ? decodeKernelExecuteCalls(fallback.callData) : void 0;
|
|
3236
3008
|
return {
|
|
3237
3009
|
userOp,
|
|
3238
3010
|
fallback,
|
|
@@ -3256,19 +3028,11 @@ var PTClaimHandler = class {
|
|
|
3256
3028
|
};
|
|
3257
3029
|
|
|
3258
3030
|
// src/api/handlers/perpDepositHandler.ts
|
|
3259
|
-
import {
|
|
3260
|
-
BROKER_HASHES,
|
|
3261
|
-
ORDERLY_RELAY_ABI,
|
|
3262
|
-
ORDERLY_VAULT_ABI,
|
|
3263
|
-
ORDERLY_VAULT_ADDRESSES,
|
|
3264
|
-
TOKEN_HASHES,
|
|
3265
|
-
buildPerpDepositViaRelay,
|
|
3266
|
-
computeAccountId,
|
|
3267
|
-
decodeBatchExecuteCalls as decodeBatchExecuteCalls2,
|
|
3268
|
-
getContractAddresses as getContractAddresses4,
|
|
3269
|
-
quoteOperatorFeeUsdt
|
|
3270
|
-
} from "@pafi-dev/core";
|
|
3031
|
+
import { BROKER_HASHES, ORDERLY_RELAY_ABI, ORDERLY_VAULT_ABI, ORDERLY_VAULT_ADDRESSES, TOKEN_HASHES, buildPerpDepositViaRelay, computeAccountId, decodeKernelExecuteCalls as decodeKernelExecuteCalls2, getContractAddresses as getContractAddresses4, quoteOperatorFeeUsdt } from "@pafi-dev/core";
|
|
3271
3032
|
var PerpDepositError = class extends PafiSdkError {
|
|
3033
|
+
static {
|
|
3034
|
+
__name(this, "PerpDepositError");
|
|
3035
|
+
}
|
|
3272
3036
|
httpStatus = "unprocessable";
|
|
3273
3037
|
code;
|
|
3274
3038
|
safeToRetry;
|
|
@@ -3280,6 +3044,9 @@ var PerpDepositError = class extends PafiSdkError {
|
|
|
3280
3044
|
};
|
|
3281
3045
|
var DEFAULT_MAX_FEE_PREMIUM_BPS = 5e3;
|
|
3282
3046
|
var PerpDepositHandler = class {
|
|
3047
|
+
static {
|
|
3048
|
+
__name(this, "PerpDepositHandler");
|
|
3049
|
+
}
|
|
3283
3050
|
cfg;
|
|
3284
3051
|
constructor(config) {
|
|
3285
3052
|
this.cfg = {
|
|
@@ -3295,10 +3062,7 @@ var PerpDepositHandler = class {
|
|
|
3295
3062
|
const tokenHash = TOKEN_HASHES.USDC;
|
|
3296
3063
|
const vault = ORDERLY_VAULT_ADDRESSES[request.chainId];
|
|
3297
3064
|
if (!vault) {
|
|
3298
|
-
throw new PerpDepositError(
|
|
3299
|
-
"PERP_DEPOSIT_UNAVAILABLE",
|
|
3300
|
-
`no Orderly Vault for chainId ${request.chainId}`
|
|
3301
|
-
);
|
|
3065
|
+
throw new PerpDepositError("PERP_DEPOSIT_UNAVAILABLE", `no Orderly Vault for chainId ${request.chainId}`);
|
|
3302
3066
|
}
|
|
3303
3067
|
const { orderlyRelay: relayAddress, pafiFeeRecipient } = getContractAddresses4(request.chainId);
|
|
3304
3068
|
const [usdcAddress, brokerAllowed] = await Promise.all([
|
|
@@ -3306,20 +3070,21 @@ var PerpDepositHandler = class {
|
|
|
3306
3070
|
address: vault,
|
|
3307
3071
|
abi: ORDERLY_VAULT_ABI,
|
|
3308
3072
|
functionName: "getAllowedToken",
|
|
3309
|
-
args: [
|
|
3073
|
+
args: [
|
|
3074
|
+
tokenHash
|
|
3075
|
+
]
|
|
3310
3076
|
}),
|
|
3311
3077
|
this.cfg.provider.readContract({
|
|
3312
3078
|
address: vault,
|
|
3313
3079
|
abi: ORDERLY_VAULT_ABI,
|
|
3314
3080
|
functionName: "getAllowedBroker",
|
|
3315
|
-
args: [
|
|
3081
|
+
args: [
|
|
3082
|
+
brokerHash
|
|
3083
|
+
]
|
|
3316
3084
|
})
|
|
3317
3085
|
]);
|
|
3318
3086
|
if (!brokerAllowed) {
|
|
3319
|
-
throw new PerpDepositError(
|
|
3320
|
-
"BROKER_NOT_WHITELISTED",
|
|
3321
|
-
`broker "${request.brokerId}" is not whitelisted on Orderly Vault`
|
|
3322
|
-
);
|
|
3087
|
+
throw new PerpDepositError("BROKER_NOT_WHITELISTED", `broker "${request.brokerId}" is not whitelisted on Orderly Vault`);
|
|
3323
3088
|
}
|
|
3324
3089
|
const accountId = computeAccountId(request.userAddress, brokerHash);
|
|
3325
3090
|
const requestForQuote = {
|
|
@@ -3334,7 +3099,9 @@ var PerpDepositHandler = class {
|
|
|
3334
3099
|
address: relayAddress,
|
|
3335
3100
|
abi: ORDERLY_RELAY_ABI,
|
|
3336
3101
|
functionName: "quoteTokenFee",
|
|
3337
|
-
args: [
|
|
3102
|
+
args: [
|
|
3103
|
+
requestForQuote
|
|
3104
|
+
]
|
|
3338
3105
|
}),
|
|
3339
3106
|
quoteOperatorFeeUsdt({
|
|
3340
3107
|
provider: this.cfg.provider,
|
|
@@ -3344,16 +3111,10 @@ var PerpDepositHandler = class {
|
|
|
3344
3111
|
})
|
|
3345
3112
|
]);
|
|
3346
3113
|
if (relayTokenFee >= request.amount) {
|
|
3347
|
-
throw new PerpDepositError(
|
|
3348
|
-
"RELAY_FEE_EXCEEDS_AMOUNT",
|
|
3349
|
-
`Relay quoted fee ${relayTokenFee} >= deposit amount ${request.amount}`
|
|
3350
|
-
);
|
|
3114
|
+
throw new PerpDepositError("RELAY_FEE_EXCEEDS_AMOUNT", `Relay quoted fee ${relayTokenFee} >= deposit amount ${request.amount}`);
|
|
3351
3115
|
}
|
|
3352
3116
|
if (usdcGasFee > 0n && usdcGasFee >= request.amount) {
|
|
3353
|
-
throw new PerpDepositError(
|
|
3354
|
-
"FEE_EXCEEDS_AMOUNT",
|
|
3355
|
-
`USDC gas fee ${usdcGasFee} >= deposit amount ${request.amount}`
|
|
3356
|
-
);
|
|
3117
|
+
throw new PerpDepositError("FEE_EXCEEDS_AMOUNT", `USDC gas fee ${usdcGasFee} >= deposit amount ${request.amount}`);
|
|
3357
3118
|
}
|
|
3358
3119
|
const maxFee = relayTokenFee * BigInt(1e4 + this.cfg.maxFeePremiumBps) / 10000n;
|
|
3359
3120
|
const depositReq = {
|
|
@@ -3388,138 +3149,19 @@ var PerpDepositHandler = class {
|
|
|
3388
3149
|
brokerHash,
|
|
3389
3150
|
usdcAddress,
|
|
3390
3151
|
relayAddress,
|
|
3391
|
-
calls:
|
|
3392
|
-
callsFallback: fallbackOp ?
|
|
3152
|
+
calls: decodeKernelExecuteCalls2(sponsoredOp.callData),
|
|
3153
|
+
callsFallback: fallbackOp ? decodeKernelExecuteCalls2(fallbackOp.callData) : void 0
|
|
3393
3154
|
};
|
|
3394
3155
|
}
|
|
3395
3156
|
};
|
|
3396
3157
|
|
|
3397
|
-
// src/api/delegateHandler.ts
|
|
3398
|
-
import {
|
|
3399
|
-
ENTRY_POINT_V08 as ENTRY_POINT_V082,
|
|
3400
|
-
buildDelegationUserOp,
|
|
3401
|
-
buildEip7702Authorization,
|
|
3402
|
-
getContractAddresses as getContractAddresses5,
|
|
3403
|
-
serializeUserOpToJsonRpc as serializeUserOpToJsonRpc2
|
|
3404
|
-
} from "@pafi-dev/core";
|
|
3405
|
-
import { getAddress as getAddress10 } from "viem";
|
|
3406
|
-
var DEFAULT_DELEGATE_GAS = {
|
|
3407
|
-
callGasLimit: 100000n,
|
|
3408
|
-
verificationGasLimit: 150000n,
|
|
3409
|
-
preVerificationGas: 50000n
|
|
3410
|
-
};
|
|
3411
|
-
async function handleDelegatePrepare(params) {
|
|
3412
|
-
const { batchExecutor } = getContractAddresses5(params.chainId);
|
|
3413
|
-
const partial = buildDelegationUserOp({
|
|
3414
|
-
userAddress: params.userAddress,
|
|
3415
|
-
aaNonce: params.aaNonce,
|
|
3416
|
-
gasLimits: {
|
|
3417
|
-
callGasLimit: params.gasLimits?.callGasLimit ?? DEFAULT_DELEGATE_GAS.callGasLimit,
|
|
3418
|
-
verificationGasLimit: params.gasLimits?.verificationGasLimit ?? DEFAULT_DELEGATE_GAS.verificationGasLimit,
|
|
3419
|
-
preVerificationGas: params.gasLimits?.preVerificationGas ?? DEFAULT_DELEGATE_GAS.preVerificationGas
|
|
3420
|
-
}
|
|
3421
|
-
});
|
|
3422
|
-
const userOp = {
|
|
3423
|
-
sender: partial.sender,
|
|
3424
|
-
nonce: partial.nonce,
|
|
3425
|
-
callData: partial.callData,
|
|
3426
|
-
callGasLimit: partial.callGasLimit,
|
|
3427
|
-
verificationGasLimit: partial.verificationGasLimit,
|
|
3428
|
-
preVerificationGas: partial.preVerificationGas,
|
|
3429
|
-
maxFeePerGas: params.fees.maxFeePerGas ?? 0n,
|
|
3430
|
-
maxPriorityFeePerGas: params.fees.maxPriorityFeePerGas ?? 0n
|
|
3431
|
-
};
|
|
3432
|
-
const authorization = buildEip7702Authorization({
|
|
3433
|
-
chainId: params.chainId,
|
|
3434
|
-
address: batchExecutor,
|
|
3435
|
-
nonce: params.delegationNonce,
|
|
3436
|
-
authSig: params.authSig
|
|
3437
|
-
});
|
|
3438
|
-
const paymasterFields = await requestPaymaster({
|
|
3439
|
-
client: params.pafiBackendClient,
|
|
3440
|
-
chainId: params.chainId,
|
|
3441
|
-
scenario: "delegate",
|
|
3442
|
-
userOp,
|
|
3443
|
-
pointTokenAddress: batchExecutor,
|
|
3444
|
-
eip7702Auth: authorization,
|
|
3445
|
-
onWarning: params.onWarning
|
|
3446
|
-
});
|
|
3447
|
-
const prepared = applyPaymasterGasEstimates(
|
|
3448
|
-
userOp,
|
|
3449
|
-
paymasterFields,
|
|
3450
|
-
params.chainId
|
|
3451
|
-
);
|
|
3452
|
-
const merged = prepared.userOp;
|
|
3453
|
-
const userOpHash = prepared.userOpHash;
|
|
3454
|
-
await params.store.save(
|
|
3455
|
-
params.lockId,
|
|
3456
|
-
{
|
|
3457
|
-
sender: merged.sender,
|
|
3458
|
-
nonce: merged.nonce.toString(10),
|
|
3459
|
-
callData: merged.callData,
|
|
3460
|
-
callGasLimit: merged.callGasLimit.toString(10),
|
|
3461
|
-
verificationGasLimit: merged.verificationGasLimit.toString(10),
|
|
3462
|
-
preVerificationGas: merged.preVerificationGas.toString(10),
|
|
3463
|
-
maxFeePerGas: merged.maxFeePerGas.toString(10),
|
|
3464
|
-
maxPriorityFeePerGas: merged.maxPriorityFeePerGas.toString(10),
|
|
3465
|
-
...merged.paymaster ? { paymaster: merged.paymaster } : {},
|
|
3466
|
-
...merged.paymasterVerificationGasLimit ? {
|
|
3467
|
-
paymasterVerificationGasLimit: merged.paymasterVerificationGasLimit.toString(10)
|
|
3468
|
-
} : {},
|
|
3469
|
-
...merged.paymasterPostOpGasLimit ? {
|
|
3470
|
-
paymasterPostOpGasLimit: merged.paymasterPostOpGasLimit.toString(10)
|
|
3471
|
-
} : {},
|
|
3472
|
-
...merged.paymasterData ? { paymasterData: merged.paymasterData } : {},
|
|
3473
|
-
chainId: params.chainId,
|
|
3474
|
-
userOpHash,
|
|
3475
|
-
eip7702Auth: authorization
|
|
3476
|
-
},
|
|
3477
|
-
params.ttlSeconds
|
|
3478
|
-
);
|
|
3479
|
-
return {
|
|
3480
|
-
lockId: params.lockId,
|
|
3481
|
-
userOpHash,
|
|
3482
|
-
typedData: prepared.typedData,
|
|
3483
|
-
expiresInSeconds: params.ttlSeconds,
|
|
3484
|
-
isSponsored: !!paymasterFields
|
|
3485
|
-
};
|
|
3486
|
-
}
|
|
3487
|
-
async function handleDelegateSubmit(params) {
|
|
3488
|
-
const entry = await params.store.get(params.lockId);
|
|
3489
|
-
if (!entry) {
|
|
3490
|
-
throw new PendingUserOpNotFoundError(params.lockId);
|
|
3491
|
-
}
|
|
3492
|
-
if (getAddress10(entry.sender) !== getAddress10(params.authenticatedAddress)) {
|
|
3493
|
-
throw new PendingUserOpForbiddenError(params.lockId);
|
|
3494
|
-
}
|
|
3495
|
-
if (!entry.eip7702Auth) {
|
|
3496
|
-
throw new Error(
|
|
3497
|
-
`delegate entry ${params.lockId} missing eip7702Auth \u2014 prepare step did not run correctly`
|
|
3498
|
-
);
|
|
3499
|
-
}
|
|
3500
|
-
const userOpJson = serializeEntryToJsonRpc(entry, params.userOpSig, "sponsored");
|
|
3501
|
-
const result = await relayUserOp({
|
|
3502
|
-
client: params.pafiBackendClient,
|
|
3503
|
-
userOp: userOpJson,
|
|
3504
|
-
entryPoint: params.entryPoint ?? ENTRY_POINT_V082,
|
|
3505
|
-
eip7702Auth: entry.eip7702Auth
|
|
3506
|
-
});
|
|
3507
|
-
await params.store.delete(params.lockId);
|
|
3508
|
-
return { userOpHash: result.userOpHash };
|
|
3509
|
-
}
|
|
3510
|
-
|
|
3511
3158
|
// src/api/issuerApiAdapter.ts
|
|
3512
|
-
import {
|
|
3513
|
-
import {
|
|
3514
|
-
import {
|
|
3515
|
-
buildAndSignSponsorAuth,
|
|
3516
|
-
decodeBatchExecuteCalls as decodeBatchExecuteCalls3,
|
|
3517
|
-
encodeBatchExecute,
|
|
3518
|
-
ENTRY_POINT_V08 as ENTRY_POINT_V083,
|
|
3519
|
-
getContractAddresses as getContractAddresses6,
|
|
3520
|
-
parseEip7702DelegatedAddress as parseEip7702DelegatedAddress2
|
|
3521
|
-
} from "@pafi-dev/core";
|
|
3159
|
+
import { getAddress as getAddress10 } from "viem";
|
|
3160
|
+
import { buildAndSignSponsorAuth, decodeKernelExecuteCalls as decodeKernelExecuteCalls3 } from "@pafi-dev/core";
|
|
3522
3161
|
var AdapterMisconfiguredError = class extends Error {
|
|
3162
|
+
static {
|
|
3163
|
+
__name(this, "AdapterMisconfiguredError");
|
|
3164
|
+
}
|
|
3523
3165
|
code = "ADAPTER_MISCONFIGURED";
|
|
3524
3166
|
constructor(message) {
|
|
3525
3167
|
super(message);
|
|
@@ -3527,35 +3169,28 @@ var AdapterMisconfiguredError = class extends Error {
|
|
|
3527
3169
|
}
|
|
3528
3170
|
};
|
|
3529
3171
|
var IssuerApiAdapter = class {
|
|
3172
|
+
static {
|
|
3173
|
+
__name(this, "IssuerApiAdapter");
|
|
3174
|
+
}
|
|
3530
3175
|
cfg;
|
|
3531
3176
|
constructor(config) {
|
|
3532
3177
|
if (config.ptClaimHandler) {
|
|
3533
3178
|
if (typeof config.ledger.bindMintUserOpHash !== "function") {
|
|
3534
|
-
throw new AdapterMisconfiguredError(
|
|
3535
|
-
"ledger.bindMintUserOpHash is required when ptClaimHandler is wired (mobile claim flow). Implement it on your IPointLedger or omit ptClaimHandler from IssuerApiAdapter config."
|
|
3536
|
-
);
|
|
3179
|
+
throw new AdapterMisconfiguredError("ledger.bindMintUserOpHash is required when ptClaimHandler is wired (mobile claim flow). Implement it on your IPointLedger or omit ptClaimHandler from IssuerApiAdapter config.");
|
|
3537
3180
|
}
|
|
3538
3181
|
if (typeof config.ledger.getMintLock !== "function") {
|
|
3539
|
-
throw new AdapterMisconfiguredError(
|
|
3540
|
-
"ledger.getMintLock is required when ptClaimHandler is wired \u2014 claimStatus uses it to look up the lock."
|
|
3541
|
-
);
|
|
3182
|
+
throw new AdapterMisconfiguredError("ledger.getMintLock is required when ptClaimHandler is wired \u2014 claimStatus uses it to look up the lock.");
|
|
3542
3183
|
}
|
|
3543
3184
|
}
|
|
3544
3185
|
if (config.ptRedeemHandler) {
|
|
3545
3186
|
if (typeof config.ledger.reservePendingCredit !== "function") {
|
|
3546
|
-
throw new AdapterMisconfiguredError(
|
|
3547
|
-
"ledger.reservePendingCredit is required when ptRedeemHandler is wired (burn/redeem reverse flow). PTRedeemHandler also enforces this at construction; see ledger/types.ts comments."
|
|
3548
|
-
);
|
|
3187
|
+
throw new AdapterMisconfiguredError("ledger.reservePendingCredit is required when ptRedeemHandler is wired (burn/redeem reverse flow). PTRedeemHandler also enforces this at construction; see ledger/types.ts comments.");
|
|
3549
3188
|
}
|
|
3550
3189
|
if (typeof config.ledger.bindCreditUserOpHash !== "function") {
|
|
3551
|
-
throw new AdapterMisconfiguredError(
|
|
3552
|
-
"ledger.bindCreditUserOpHash is required when ptRedeemHandler is wired (mobile redeem flow)."
|
|
3553
|
-
);
|
|
3190
|
+
throw new AdapterMisconfiguredError("ledger.bindCreditUserOpHash is required when ptRedeemHandler is wired (mobile redeem flow).");
|
|
3554
3191
|
}
|
|
3555
3192
|
if (typeof config.ledger.getPendingCredit !== "function") {
|
|
3556
|
-
throw new AdapterMisconfiguredError(
|
|
3557
|
-
"ledger.getPendingCredit is required when ptRedeemHandler is wired \u2014 redeemStatus uses it to look up the credit."
|
|
3558
|
-
);
|
|
3193
|
+
throw new AdapterMisconfiguredError("ledger.getPendingCredit is required when ptRedeemHandler is wired \u2014 redeemStatus uses it to look up the credit.");
|
|
3559
3194
|
}
|
|
3560
3195
|
}
|
|
3561
3196
|
this.cfg = config;
|
|
@@ -3570,24 +3205,25 @@ var IssuerApiAdapter = class {
|
|
|
3570
3205
|
}
|
|
3571
3206
|
async gasFee() {
|
|
3572
3207
|
const result = await this.cfg.issuerService.api.handleGasFee();
|
|
3573
|
-
return {
|
|
3208
|
+
return {
|
|
3209
|
+
gasFeeUsdt: result.gasFeeUsdt.toString()
|
|
3210
|
+
};
|
|
3574
3211
|
}
|
|
3575
3212
|
async pools(authenticatedAddress, chainId, pointTokenAddress) {
|
|
3576
|
-
const result = await this.cfg.issuerService.api.handlePools(
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
);
|
|
3580
|
-
return {
|
|
3213
|
+
const result = await this.cfg.issuerService.api.handlePools(authenticatedAddress, {
|
|
3214
|
+
chainId,
|
|
3215
|
+
pointTokenAddress: getAddress10(pointTokenAddress)
|
|
3216
|
+
});
|
|
3217
|
+
return {
|
|
3218
|
+
pools: result.pools
|
|
3219
|
+
};
|
|
3581
3220
|
}
|
|
3582
3221
|
async user(authenticatedAddress, chainId, userAddress, pointTokenAddress) {
|
|
3583
|
-
const result = await this.cfg.issuerService.api.handleUser(
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
pointTokenAddress: getAddress11(pointTokenAddress)
|
|
3589
|
-
}
|
|
3590
|
-
);
|
|
3222
|
+
const result = await this.cfg.issuerService.api.handleUser(authenticatedAddress, {
|
|
3223
|
+
chainId,
|
|
3224
|
+
userAddress: getAddress10(userAddress),
|
|
3225
|
+
pointTokenAddress: getAddress10(pointTokenAddress)
|
|
3226
|
+
});
|
|
3591
3227
|
return {
|
|
3592
3228
|
offChainBalance: result.offChainBalance.toString(),
|
|
3593
3229
|
onChainBalance: result.onChainBalance.toString(),
|
|
@@ -3600,12 +3236,8 @@ var IssuerApiAdapter = class {
|
|
|
3600
3236
|
// directly. Issuer SDK doesn't ship swap/quote anymore.
|
|
3601
3237
|
// ------------------------------ Action endpoints -------------------------
|
|
3602
3238
|
async claim(input) {
|
|
3603
|
-
const ptClaimHandler = this.assertHandler(
|
|
3604
|
-
|
|
3605
|
-
"ptClaimHandler",
|
|
3606
|
-
"claim"
|
|
3607
|
-
);
|
|
3608
|
-
const pointTokenAddress = getAddress11(input.pointTokenAddress);
|
|
3239
|
+
const ptClaimHandler = this.assertHandler(this.cfg.ptClaimHandler, "ptClaimHandler", "claim");
|
|
3240
|
+
const pointTokenAddress = getAddress10(input.pointTokenAddress);
|
|
3609
3241
|
const result = await ptClaimHandler.handle({
|
|
3610
3242
|
authenticatedAddress: input.authenticatedAddress,
|
|
3611
3243
|
userAddress: input.authenticatedAddress,
|
|
@@ -3614,12 +3246,7 @@ var IssuerApiAdapter = class {
|
|
|
3614
3246
|
chainId: input.chainId,
|
|
3615
3247
|
aaNonce: input.aaNonce
|
|
3616
3248
|
});
|
|
3617
|
-
const sponsorAuth = await this.buildSponsorAuth(
|
|
3618
|
-
input.authenticatedAddress,
|
|
3619
|
-
result.userOp.callData,
|
|
3620
|
-
input.chainId,
|
|
3621
|
-
"mint"
|
|
3622
|
-
);
|
|
3249
|
+
const sponsorAuth = await this.buildSponsorAuth(input.authenticatedAddress, result.userOp.callData, input.chainId, "mint");
|
|
3623
3250
|
return {
|
|
3624
3251
|
calls: result.calls,
|
|
3625
3252
|
callsFallback: result.callsFallback,
|
|
@@ -3631,7 +3258,7 @@ var IssuerApiAdapter = class {
|
|
|
3631
3258
|
}
|
|
3632
3259
|
async redeem(input) {
|
|
3633
3260
|
this.assertRedeemHandler();
|
|
3634
|
-
const pointTokenAddress =
|
|
3261
|
+
const pointTokenAddress = getAddress10(input.pointTokenAddress);
|
|
3635
3262
|
const response = await this.cfg.ptRedeemHandler.handle({
|
|
3636
3263
|
userAddress: input.authenticatedAddress,
|
|
3637
3264
|
authenticatedAddress: input.authenticatedAddress,
|
|
@@ -3640,15 +3267,10 @@ var IssuerApiAdapter = class {
|
|
|
3640
3267
|
aaNonce: input.aaNonce,
|
|
3641
3268
|
chainId: input.chainId
|
|
3642
3269
|
});
|
|
3643
|
-
const sponsorAuth = await this.buildSponsorAuth(
|
|
3644
|
-
input.authenticatedAddress,
|
|
3645
|
-
response.userOp.callData,
|
|
3646
|
-
input.chainId,
|
|
3647
|
-
"burn"
|
|
3648
|
-
);
|
|
3270
|
+
const sponsorAuth = await this.buildSponsorAuth(input.authenticatedAddress, response.userOp.callData, input.chainId, "burn");
|
|
3649
3271
|
return {
|
|
3650
|
-
calls:
|
|
3651
|
-
callsFallback: response.fallback ?
|
|
3272
|
+
calls: decodeKernelExecuteCalls3(response.userOp.callData),
|
|
3273
|
+
callsFallback: response.fallback ? decodeKernelExecuteCalls3(response.fallback.userOp.callData) : void 0,
|
|
3652
3274
|
feeAmount: response.feeAmount.toString(),
|
|
3653
3275
|
lockId: response.lockId,
|
|
3654
3276
|
lockIdFallback: response.fallback?.lockId,
|
|
@@ -3662,11 +3284,7 @@ var IssuerApiAdapter = class {
|
|
|
3662
3284
|
// swap() removed (2026-04-27) — moved to @pafi-dev/trading.
|
|
3663
3285
|
// PAFI's web FE calls TradingHandlers.handleSwap directly.
|
|
3664
3286
|
async perpDeposit(input) {
|
|
3665
|
-
const perpHandler = this.assertHandler(
|
|
3666
|
-
this.cfg.perpHandler,
|
|
3667
|
-
"perpHandler",
|
|
3668
|
-
"perpDeposit"
|
|
3669
|
-
);
|
|
3287
|
+
const perpHandler = this.assertHandler(this.cfg.perpHandler, "perpHandler", "perpDeposit");
|
|
3670
3288
|
const result = await perpHandler.handle({
|
|
3671
3289
|
userAddress: input.authenticatedAddress,
|
|
3672
3290
|
chainId: input.chainId,
|
|
@@ -3674,12 +3292,7 @@ var IssuerApiAdapter = class {
|
|
|
3674
3292
|
brokerId: input.brokerId,
|
|
3675
3293
|
aaNonce: input.aaNonce
|
|
3676
3294
|
});
|
|
3677
|
-
const sponsorAuth = await this.buildSponsorAuth(
|
|
3678
|
-
input.authenticatedAddress,
|
|
3679
|
-
result.userOp.callData,
|
|
3680
|
-
input.chainId,
|
|
3681
|
-
"perp-deposit"
|
|
3682
|
-
);
|
|
3295
|
+
const sponsorAuth = await this.buildSponsorAuth(input.authenticatedAddress, result.userOp.callData, input.chainId, "perp-deposit");
|
|
3683
3296
|
return {
|
|
3684
3297
|
calls: result.calls,
|
|
3685
3298
|
callsFallback: result.callsFallback,
|
|
@@ -3696,12 +3309,8 @@ var IssuerApiAdapter = class {
|
|
|
3696
3309
|
}
|
|
3697
3310
|
// ------------------------------ Mobile endpoints -------------------------
|
|
3698
3311
|
async claimPrepare(input) {
|
|
3699
|
-
const ptClaimHandler = this.assertHandler(
|
|
3700
|
-
|
|
3701
|
-
"ptClaimHandler",
|
|
3702
|
-
"claimPrepare"
|
|
3703
|
-
);
|
|
3704
|
-
const pointTokenAddress = getAddress11(input.pointTokenAddress);
|
|
3312
|
+
const ptClaimHandler = this.assertHandler(this.cfg.ptClaimHandler, "ptClaimHandler", "claimPrepare");
|
|
3313
|
+
const pointTokenAddress = getAddress10(input.pointTokenAddress);
|
|
3705
3314
|
const claimResult = await ptClaimHandler.handle({
|
|
3706
3315
|
authenticatedAddress: input.authenticatedAddress,
|
|
3707
3316
|
userAddress: input.authenticatedAddress,
|
|
@@ -3710,23 +3319,11 @@ var IssuerApiAdapter = class {
|
|
|
3710
3319
|
chainId: input.chainId,
|
|
3711
3320
|
aaNonce: input.aaNonce
|
|
3712
3321
|
});
|
|
3713
|
-
const prepared = await this.runMobilePrepare(
|
|
3714
|
-
input.authenticatedAddress,
|
|
3715
|
-
input.chainId,
|
|
3716
|
-
claimResult.lockId,
|
|
3717
|
-
claimResult.userOp,
|
|
3718
|
-
claimResult.fallback,
|
|
3719
|
-
"mint",
|
|
3720
|
-
pointTokenAddress,
|
|
3721
|
-
claimResult.expiresInSeconds,
|
|
3722
|
-
input.eip7702Auth
|
|
3723
|
-
);
|
|
3322
|
+
const prepared = await this.runMobilePrepare(input.authenticatedAddress, input.chainId, claimResult.lockId, claimResult.userOp, claimResult.fallback, "mint", pointTokenAddress, claimResult.expiresInSeconds, input.eip7702Auth);
|
|
3724
3323
|
return {
|
|
3725
3324
|
lockId: claimResult.lockId,
|
|
3726
3325
|
userOpHash: prepared.sponsored.userOpHash,
|
|
3727
|
-
typedData: prepared.sponsored.typedData,
|
|
3728
3326
|
userOpHashFallback: prepared.fallback?.userOpHash,
|
|
3729
|
-
typedDataFallback: prepared.fallback?.typedData,
|
|
3730
3327
|
feeAmount: claimResult.feeAmount.toString(),
|
|
3731
3328
|
signatureDeadline: claimResult.signatureDeadline.toString(),
|
|
3732
3329
|
expiresInSeconds: claimResult.expiresInSeconds,
|
|
@@ -3741,13 +3338,13 @@ var IssuerApiAdapter = class {
|
|
|
3741
3338
|
signature: input.signature,
|
|
3742
3339
|
variant: input.variant,
|
|
3743
3340
|
store: this.cfg.pendingUserOpStore,
|
|
3744
|
-
bindUserOpHash: (lockId, hash) => this.cfg.ledger.bindMintUserOpHash(lockId, hash),
|
|
3341
|
+
bindUserOpHash: /* @__PURE__ */ __name((lockId, hash) => this.cfg.ledger.bindMintUserOpHash(lockId, hash), "bindUserOpHash"),
|
|
3745
3342
|
pafiBackendClient: this.cfg.pafiBackendClient
|
|
3746
3343
|
});
|
|
3747
3344
|
}
|
|
3748
3345
|
async redeemPrepare(input) {
|
|
3749
3346
|
this.assertRedeemHandler();
|
|
3750
|
-
const pointTokenAddress =
|
|
3347
|
+
const pointTokenAddress = getAddress10(input.pointTokenAddress);
|
|
3751
3348
|
const redeemResponse = await this.cfg.ptRedeemHandler.handle({
|
|
3752
3349
|
userAddress: input.authenticatedAddress,
|
|
3753
3350
|
authenticatedAddress: input.authenticatedAddress,
|
|
@@ -3756,25 +3353,12 @@ var IssuerApiAdapter = class {
|
|
|
3756
3353
|
aaNonce: input.aaNonce,
|
|
3757
3354
|
chainId: input.chainId
|
|
3758
3355
|
});
|
|
3759
|
-
const prepared = await this.runMobilePrepare(
|
|
3760
|
-
input.authenticatedAddress,
|
|
3761
|
-
input.chainId,
|
|
3762
|
-
redeemResponse.lockId,
|
|
3763
|
-
redeemResponse.userOp,
|
|
3764
|
-
redeemResponse.fallback?.userOp,
|
|
3765
|
-
"burn",
|
|
3766
|
-
pointTokenAddress,
|
|
3767
|
-
redeemResponse.expiresInSeconds,
|
|
3768
|
-
input.eip7702Auth,
|
|
3769
|
-
redeemResponse.fallback?.lockId
|
|
3770
|
-
);
|
|
3356
|
+
const prepared = await this.runMobilePrepare(input.authenticatedAddress, input.chainId, redeemResponse.lockId, redeemResponse.userOp, redeemResponse.fallback?.userOp, "burn", pointTokenAddress, redeemResponse.expiresInSeconds, input.eip7702Auth, redeemResponse.fallback?.lockId);
|
|
3771
3357
|
return {
|
|
3772
3358
|
lockId: redeemResponse.lockId,
|
|
3773
3359
|
lockIdFallback: redeemResponse.fallback?.lockId,
|
|
3774
3360
|
userOpHash: prepared.sponsored.userOpHash,
|
|
3775
|
-
typedData: prepared.sponsored.typedData,
|
|
3776
3361
|
userOpHashFallback: prepared.fallback?.userOpHash,
|
|
3777
|
-
typedDataFallback: prepared.fallback?.typedData,
|
|
3778
3362
|
netCreditAmount: redeemResponse.netCreditAmount.toString(),
|
|
3779
3363
|
netCreditAmountFallback: redeemResponse.fallback?.netCreditAmount.toString(),
|
|
3780
3364
|
feeAmount: redeemResponse.feeAmount.toString(),
|
|
@@ -3791,7 +3375,7 @@ var IssuerApiAdapter = class {
|
|
|
3791
3375
|
signature: input.signature,
|
|
3792
3376
|
variant: input.variant,
|
|
3793
3377
|
store: this.cfg.pendingUserOpStore,
|
|
3794
|
-
bindUserOpHash: (lockId, hash) => this.cfg.ledger.bindCreditUserOpHash(lockId, hash),
|
|
3378
|
+
bindUserOpHash: /* @__PURE__ */ __name((lockId, hash) => this.cfg.ledger.bindCreditUserOpHash(lockId, hash), "bindUserOpHash"),
|
|
3795
3379
|
pafiBackendClient: this.cfg.pafiBackendClient
|
|
3796
3380
|
});
|
|
3797
3381
|
}
|
|
@@ -3815,83 +3399,15 @@ var IssuerApiAdapter = class {
|
|
|
3815
3399
|
onWarning: this.cfg.onWarning
|
|
3816
3400
|
});
|
|
3817
3401
|
}
|
|
3818
|
-
//
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
this.cfg.provider.getCode({ address: authenticatedAddress }),
|
|
3823
|
-
this.cfg.provider.getTransactionCount({
|
|
3824
|
-
address: authenticatedAddress,
|
|
3825
|
-
blockTag: "pending"
|
|
3826
|
-
})
|
|
3827
|
-
]);
|
|
3828
|
-
return {
|
|
3829
|
-
isDelegated: parseEip7702DelegatedAddress2(code) !== null,
|
|
3830
|
-
batchExecutorAddress: batchExecutor,
|
|
3831
|
-
delegationNonce: nonce.toString(),
|
|
3832
|
-
chainId
|
|
3833
|
-
};
|
|
3834
|
-
}
|
|
3835
|
-
/**
|
|
3836
|
-
* Build the delegation-anchor UserOp + obtain paymaster sponsorship
|
|
3837
|
-
* + persist as a pending entry. Mobile must:
|
|
3838
|
-
*
|
|
3839
|
-
* 1. Sign EIP-7702 authorization LOCALLY (Privy `signAuthorization`
|
|
3840
|
-
* with `{contractAddress: batchExecutorAddress, chainId,
|
|
3841
|
-
* nonce: delegationNonce}`) → 65-byte authSig hex.
|
|
3842
|
-
* 2. POST `/delegate/prepare` with `{ chainId, delegationNonce,
|
|
3843
|
-
* authSig }` → this method.
|
|
3844
|
-
* 3. Sign returned `userOpHash` LOCALLY (`signTypedData(typedData)`).
|
|
3845
|
-
* 4. POST `/delegate/submit` with `{ lockId, userOpSig }`.
|
|
3846
|
-
*
|
|
3847
|
-
* v0.7.7 — replaces single-shot delegateSubmit that tried to relay
|
|
3848
|
-
* a UserOp with empty `signature: "0x"` (Simple7702Account's
|
|
3849
|
-
* validateUserOp reverts `ECDSAInvalidSignatureLength` 0xfce698f7).
|
|
3850
|
-
*/
|
|
3851
|
-
async delegatePrepare(authenticatedAddress, input) {
|
|
3852
|
-
const { batchExecutor } = getContractAddresses6(input.chainId);
|
|
3853
|
-
const fees = await this.cfg.provider.estimateFeesPerGas();
|
|
3854
|
-
const lockId = randomUUID();
|
|
3855
|
-
const result = await handleDelegatePrepare({
|
|
3856
|
-
userAddress: authenticatedAddress,
|
|
3857
|
-
chainId: input.chainId,
|
|
3858
|
-
delegationNonce: input.delegationNonce,
|
|
3859
|
-
aaNonce: input.aaNonce,
|
|
3860
|
-
authSig: input.authSig,
|
|
3861
|
-
fees,
|
|
3862
|
-
lockId,
|
|
3863
|
-
store: this.cfg.pendingUserOpStore,
|
|
3864
|
-
ttlSeconds: 15 * 60,
|
|
3865
|
-
// 15min — match claim/redeem mobile lock duration
|
|
3866
|
-
pafiBackendClient: this.cfg.pafiBackendClient,
|
|
3867
|
-
onWarning: this.cfg.onWarning
|
|
3868
|
-
});
|
|
3869
|
-
return {
|
|
3870
|
-
lockId: result.lockId,
|
|
3871
|
-
userOpHash: result.userOpHash,
|
|
3872
|
-
typedData: result.typedData,
|
|
3873
|
-
expiresInSeconds: result.expiresInSeconds,
|
|
3874
|
-
isSponsored: result.isSponsored,
|
|
3875
|
-
delegationNonce: input.delegationNonce.toString(),
|
|
3876
|
-
batchExecutorAddress: batchExecutor,
|
|
3877
|
-
chainId: input.chainId
|
|
3878
|
-
};
|
|
3879
|
-
}
|
|
3880
|
-
async delegateSubmit(input) {
|
|
3881
|
-
const result = await handleDelegateSubmit({
|
|
3882
|
-
lockId: input.lockId,
|
|
3883
|
-
authenticatedAddress: input.authenticatedAddress,
|
|
3884
|
-
userOpSig: input.userOpSig,
|
|
3885
|
-
store: this.cfg.pendingUserOpStore,
|
|
3886
|
-
pafiBackendClient: this.cfg.pafiBackendClient
|
|
3887
|
-
});
|
|
3888
|
-
return { userOpHash: result.userOpHash };
|
|
3889
|
-
}
|
|
3402
|
+
// Delegate endpoints removed — folded-in delegation (via
|
|
3403
|
+
// `eip7702Auth` on claim/redeem prepare) is now the only path. The
|
|
3404
|
+
// bundler applies SetCode + handleOps in one tx, so there is no
|
|
3405
|
+
// separate `/delegate/*` flow. See `handleMobilePrepare`.
|
|
3890
3406
|
// ------------------------------ Internal helpers -------------------------
|
|
3891
3407
|
/**
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3408
|
+
* Build + sign a SponsorAuth payload. Returns `undefined` when no
|
|
3409
|
+
* issuer id is configured, so the controller can skip the field.
|
|
3410
|
+
*/
|
|
3895
3411
|
async buildSponsorAuth(authenticatedAddress, callData, chainId, scenario) {
|
|
3896
3412
|
if (!this.cfg.pafiIssuerId) return void 0;
|
|
3897
3413
|
return buildAndSignSponsorAuth({
|
|
@@ -3923,22 +3439,18 @@ var IssuerApiAdapter = class {
|
|
|
3923
3439
|
}
|
|
3924
3440
|
assertRedeemHandler() {
|
|
3925
3441
|
if (!this.cfg.ptRedeemHandler) {
|
|
3926
|
-
throw new Error(
|
|
3927
|
-
"PTRedeemHandler not wired \u2014 IssuerApiAdapter.redeem* require a configured ptRedeemHandler."
|
|
3928
|
-
);
|
|
3442
|
+
throw new Error("PTRedeemHandler not wired \u2014 IssuerApiAdapter.redeem* require a configured ptRedeemHandler.");
|
|
3929
3443
|
}
|
|
3930
3444
|
}
|
|
3931
3445
|
/**
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3446
|
+
* Narrow an optional handler to non-null and throw a clear error when
|
|
3447
|
+
* the issuer wired the adapter without it. Lets issuers opt out of
|
|
3448
|
+
* flows they don't expose (gg56 ships only mobile claim/redeem, so
|
|
3449
|
+
* `swapHandler` + `perpHandler` aren't constructed).
|
|
3450
|
+
*/
|
|
3937
3451
|
assertHandler(handler, fieldName, methodName) {
|
|
3938
3452
|
if (handler === null || handler === void 0) {
|
|
3939
|
-
throw new Error(
|
|
3940
|
-
`${fieldName} not wired \u2014 IssuerApiAdapter.${methodName}() requires a configured ${fieldName}.`
|
|
3941
|
-
);
|
|
3453
|
+
throw new Error(`${fieldName} not wired \u2014 IssuerApiAdapter.${methodName}() requires a configured ${fieldName}.`);
|
|
3942
3454
|
}
|
|
3943
3455
|
return handler;
|
|
3944
3456
|
}
|
|
@@ -3971,9 +3483,7 @@ function createSubgraphPoolsProvider(config = {}) {
|
|
|
3971
3483
|
}
|
|
3972
3484
|
} catch (err) {
|
|
3973
3485
|
if (err instanceof TypeError) {
|
|
3974
|
-
throw new Error(
|
|
3975
|
-
`subgraphPoolsProvider: invalid subgraphUrl: ${subgraphUrl}`
|
|
3976
|
-
);
|
|
3486
|
+
throw new Error(`subgraphPoolsProvider: invalid subgraphUrl: ${subgraphUrl}`);
|
|
3977
3487
|
}
|
|
3978
3488
|
throw err;
|
|
3979
3489
|
}
|
|
@@ -3983,64 +3493,61 @@ function createSubgraphPoolsProvider(config = {}) {
|
|
|
3983
3493
|
const onError = config.onError;
|
|
3984
3494
|
const cache = /* @__PURE__ */ new Map();
|
|
3985
3495
|
if (!fetchImpl) {
|
|
3986
|
-
throw new Error(
|
|
3987
|
-
"createSubgraphPoolsProvider: no fetch implementation available \u2014 pass `fetchImpl` or run on Node 18+"
|
|
3988
|
-
);
|
|
3496
|
+
throw new Error("createSubgraphPoolsProvider: no fetch implementation available \u2014 pass `fetchImpl` or run on Node 18+");
|
|
3989
3497
|
}
|
|
3990
|
-
const reportError = (err) => {
|
|
3498
|
+
const reportError = /* @__PURE__ */ __name((err) => {
|
|
3991
3499
|
if (!onError) return;
|
|
3992
3500
|
try {
|
|
3993
3501
|
onError(err);
|
|
3994
3502
|
} catch {
|
|
3995
3503
|
}
|
|
3996
|
-
};
|
|
3504
|
+
}, "reportError");
|
|
3997
3505
|
return async (request) => {
|
|
3998
3506
|
const cacheKey = `${request.chainId}:${request.pointTokenAddress.toLowerCase()}`;
|
|
3999
3507
|
if (cacheTtl > 0) {
|
|
4000
3508
|
const cached = cache.get(cacheKey);
|
|
4001
3509
|
if (cached && cached.expiresAt > now()) {
|
|
4002
|
-
return {
|
|
3510
|
+
return {
|
|
3511
|
+
pools: cached.pools
|
|
3512
|
+
};
|
|
4003
3513
|
}
|
|
4004
3514
|
}
|
|
4005
|
-
const pools = await fetchPoolsFromSubgraph(
|
|
4006
|
-
fetchImpl,
|
|
4007
|
-
subgraphUrl,
|
|
4008
|
-
request.pointTokenAddress,
|
|
4009
|
-
reportError
|
|
4010
|
-
);
|
|
3515
|
+
const pools = await fetchPoolsFromSubgraph(fetchImpl, subgraphUrl, request.pointTokenAddress, reportError);
|
|
4011
3516
|
if (cacheTtl > 0) {
|
|
4012
3517
|
cache.set(cacheKey, {
|
|
4013
3518
|
expiresAt: now() + cacheTtl,
|
|
4014
3519
|
pools
|
|
4015
3520
|
});
|
|
4016
3521
|
}
|
|
4017
|
-
return {
|
|
3522
|
+
return {
|
|
3523
|
+
pools
|
|
3524
|
+
};
|
|
4018
3525
|
};
|
|
4019
3526
|
}
|
|
3527
|
+
__name(createSubgraphPoolsProvider, "createSubgraphPoolsProvider");
|
|
4020
3528
|
async function fetchPoolsFromSubgraph(fetchImpl, subgraphUrl, pointTokenAddress, reportError) {
|
|
4021
3529
|
let response;
|
|
4022
3530
|
try {
|
|
4023
3531
|
response = await fetchImpl(subgraphUrl, {
|
|
4024
3532
|
method: "POST",
|
|
4025
|
-
headers: {
|
|
3533
|
+
headers: {
|
|
3534
|
+
"Content-Type": "application/json"
|
|
3535
|
+
},
|
|
4026
3536
|
body: JSON.stringify({
|
|
4027
3537
|
query: POOL_QUERY,
|
|
4028
|
-
variables: {
|
|
3538
|
+
variables: {
|
|
3539
|
+
id: pointTokenAddress.toLowerCase()
|
|
3540
|
+
}
|
|
4029
3541
|
})
|
|
4030
3542
|
});
|
|
4031
3543
|
} catch (err) {
|
|
4032
3544
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
4033
|
-
console.warn(
|
|
4034
|
-
"[subgraphPoolsProvider] subgraph unreachable:",
|
|
4035
|
-
error.message
|
|
4036
|
-
);
|
|
3545
|
+
console.warn("[subgraphPoolsProvider] subgraph unreachable:", error.message);
|
|
4037
3546
|
reportError(error);
|
|
4038
3547
|
return [];
|
|
4039
3548
|
}
|
|
4040
3549
|
if (!response.ok) {
|
|
4041
|
-
const error = new Error(
|
|
4042
|
-
`subgraph returned HTTP ${response.status}`
|
|
4043
|
-
);
|
|
3550
|
+
const error = new Error(`subgraph returned HTTP ${response.status}`);
|
|
4044
3551
|
console.warn(`[subgraphPoolsProvider] ${error.message}`);
|
|
4045
3552
|
reportError(error);
|
|
4046
3553
|
return [];
|
|
@@ -4050,10 +3557,7 @@ async function fetchPoolsFromSubgraph(fetchImpl, subgraphUrl, pointTokenAddress,
|
|
|
4050
3557
|
json = await response.json();
|
|
4051
3558
|
} catch (err) {
|
|
4052
3559
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
4053
|
-
console.warn(
|
|
4054
|
-
"[subgraphPoolsProvider] subgraph returned non-JSON:",
|
|
4055
|
-
error.message
|
|
4056
|
-
);
|
|
3560
|
+
console.warn("[subgraphPoolsProvider] subgraph returned non-JSON:", error.message);
|
|
4057
3561
|
reportError(error);
|
|
4058
3562
|
return [];
|
|
4059
3563
|
}
|
|
@@ -4069,36 +3573,39 @@ async function fetchPoolsFromSubgraph(fetchImpl, subgraphUrl, pointTokenAddress,
|
|
|
4069
3573
|
}
|
|
4070
3574
|
const { pool } = token;
|
|
4071
3575
|
if (!isAddress(pool.token0.id) || !isAddress(pool.token1.id)) {
|
|
4072
|
-
const error = new Error(
|
|
4073
|
-
"[PAFI] SubgraphPoolsProvider: invalid token address in response"
|
|
4074
|
-
);
|
|
3576
|
+
const error = new Error("[PAFI] SubgraphPoolsProvider: invalid token address in response");
|
|
4075
3577
|
console.error(error.message, "\u2014 skipping pool");
|
|
4076
3578
|
reportError(error);
|
|
4077
3579
|
return [];
|
|
4078
3580
|
}
|
|
4079
3581
|
const feeNum = Number(pool.feeTier);
|
|
4080
3582
|
if (!Number.isInteger(feeNum) || feeNum < 0 || feeNum >= MAX_REASONABLE_FEE_TIER) {
|
|
4081
|
-
const error = new Error(
|
|
4082
|
-
`[PAFI] SubgraphPoolsProvider: invalid feeTier value: ${pool.feeTier}`
|
|
4083
|
-
);
|
|
3583
|
+
const error = new Error(`[PAFI] SubgraphPoolsProvider: invalid feeTier value: ${pool.feeTier}`);
|
|
4084
3584
|
console.error(error.message, "\u2014 skipping pool");
|
|
4085
3585
|
reportError(error);
|
|
4086
3586
|
return [];
|
|
4087
3587
|
}
|
|
4088
|
-
const [token0, token1] = sortTokens(
|
|
4089
|
-
pool.token0.id,
|
|
4090
|
-
pool.token1.id
|
|
4091
|
-
);
|
|
3588
|
+
const [token0, token1] = sortTokens(pool.token0.id, pool.token1.id);
|
|
4092
3589
|
const poolKey = {
|
|
4093
3590
|
token0,
|
|
4094
3591
|
token1,
|
|
4095
3592
|
fee: feeNum
|
|
4096
3593
|
};
|
|
4097
|
-
return [
|
|
3594
|
+
return [
|
|
3595
|
+
poolKey
|
|
3596
|
+
];
|
|
4098
3597
|
}
|
|
3598
|
+
__name(fetchPoolsFromSubgraph, "fetchPoolsFromSubgraph");
|
|
4099
3599
|
function sortTokens(a, b) {
|
|
4100
|
-
return a.toLowerCase() < b.toLowerCase() ? [
|
|
3600
|
+
return a.toLowerCase() < b.toLowerCase() ? [
|
|
3601
|
+
a,
|
|
3602
|
+
b
|
|
3603
|
+
] : [
|
|
3604
|
+
b,
|
|
3605
|
+
a
|
|
3606
|
+
];
|
|
4101
3607
|
}
|
|
3608
|
+
__name(sortTokens, "sortTokens");
|
|
4102
3609
|
|
|
4103
3610
|
// src/pools/subgraphNativeUsdtQuoter.ts
|
|
4104
3611
|
var DEFAULT_CACHE_TTL_MS2 = 3e4;
|
|
@@ -4121,9 +3628,7 @@ function createSubgraphNativeUsdtQuoter(config = {}) {
|
|
|
4121
3628
|
}
|
|
4122
3629
|
} catch (err) {
|
|
4123
3630
|
if (err instanceof TypeError) {
|
|
4124
|
-
throw new Error(
|
|
4125
|
-
`createSubgraphNativeUsdtQuoter: invalid subgraphUrl: ${subgraphUrl}`
|
|
4126
|
-
);
|
|
3631
|
+
throw new Error(`createSubgraphNativeUsdtQuoter: invalid subgraphUrl: ${subgraphUrl}`);
|
|
4127
3632
|
}
|
|
4128
3633
|
throw err;
|
|
4129
3634
|
}
|
|
@@ -4134,23 +3639,15 @@ function createSubgraphNativeUsdtQuoter(config = {}) {
|
|
|
4134
3639
|
const fetchImpl = config.fetchImpl ?? globalThis.fetch;
|
|
4135
3640
|
const now = config.now ?? (() => Date.now());
|
|
4136
3641
|
if (!fetchImpl) {
|
|
4137
|
-
throw new Error(
|
|
4138
|
-
"createSubgraphNativeUsdtQuoter: no fetch implementation available \u2014 pass `fetchImpl` or run on Node 18+"
|
|
4139
|
-
);
|
|
3642
|
+
throw new Error("createSubgraphNativeUsdtQuoter: no fetch implementation available \u2014 pass `fetchImpl` or run on Node 18+");
|
|
4140
3643
|
}
|
|
4141
3644
|
let cached;
|
|
4142
3645
|
async function getUsdtPerNative() {
|
|
4143
3646
|
if (cacheTtl > 0 && cached && cached.expiresAt > now()) {
|
|
4144
3647
|
return cached.usdtPerNative;
|
|
4145
3648
|
}
|
|
4146
|
-
const price = await fetchEthPriceFromSubgraph(
|
|
4147
|
-
|
|
4148
|
-
subgraphUrl
|
|
4149
|
-
);
|
|
4150
|
-
const usdtPerNative = toUsdtPerNative(
|
|
4151
|
-
price ?? fallbackPrice,
|
|
4152
|
-
usdtDecimals
|
|
4153
|
-
);
|
|
3649
|
+
const price = await fetchEthPriceFromSubgraph(fetchImpl, subgraphUrl);
|
|
3650
|
+
const usdtPerNative = toUsdtPerNative(price ?? fallbackPrice, usdtDecimals);
|
|
4154
3651
|
if (cacheTtl > 0) {
|
|
4155
3652
|
cached = {
|
|
4156
3653
|
usdtPerNative,
|
|
@@ -4159,66 +3656,62 @@ function createSubgraphNativeUsdtQuoter(config = {}) {
|
|
|
4159
3656
|
}
|
|
4160
3657
|
return usdtPerNative;
|
|
4161
3658
|
}
|
|
3659
|
+
__name(getUsdtPerNative, "getUsdtPerNative");
|
|
4162
3660
|
return async (amountNative) => {
|
|
4163
3661
|
if (amountNative === 0n) return 0n;
|
|
4164
3662
|
const usdtPerNative = await getUsdtPerNative();
|
|
4165
3663
|
return amountNative * usdtPerNative / 10n ** BigInt(nativeDecimals);
|
|
4166
3664
|
};
|
|
4167
3665
|
}
|
|
3666
|
+
__name(createSubgraphNativeUsdtQuoter, "createSubgraphNativeUsdtQuoter");
|
|
4168
3667
|
async function fetchEthPriceFromSubgraph(fetchImpl, subgraphUrl) {
|
|
4169
3668
|
let response;
|
|
4170
3669
|
try {
|
|
4171
3670
|
response = await fetchImpl(subgraphUrl, {
|
|
4172
3671
|
method: "POST",
|
|
4173
|
-
headers: {
|
|
4174
|
-
|
|
3672
|
+
headers: {
|
|
3673
|
+
"Content-Type": "application/json"
|
|
3674
|
+
},
|
|
3675
|
+
body: JSON.stringify({
|
|
3676
|
+
query: PRICE_QUERY
|
|
3677
|
+
})
|
|
4175
3678
|
});
|
|
4176
3679
|
} catch (err) {
|
|
4177
|
-
console.warn(
|
|
4178
|
-
"[subgraphNativeUsdtQuoter] subgraph unreachable:",
|
|
4179
|
-
err.message
|
|
4180
|
-
);
|
|
3680
|
+
console.warn("[subgraphNativeUsdtQuoter] subgraph unreachable:", err.message);
|
|
4181
3681
|
return null;
|
|
4182
3682
|
}
|
|
4183
3683
|
if (!response.ok) {
|
|
4184
|
-
console.warn(
|
|
4185
|
-
`[subgraphNativeUsdtQuoter] subgraph returned ${response.status}`
|
|
4186
|
-
);
|
|
3684
|
+
console.warn(`[subgraphNativeUsdtQuoter] subgraph returned ${response.status}`);
|
|
4187
3685
|
return null;
|
|
4188
3686
|
}
|
|
4189
3687
|
const json = await response.json();
|
|
4190
3688
|
if (json.errors && json.errors.length > 0) {
|
|
4191
|
-
console.warn(
|
|
4192
|
-
"[subgraphNativeUsdtQuoter] subgraph errors:",
|
|
4193
|
-
json.errors.map((e) => e.message).join("; ")
|
|
4194
|
-
);
|
|
3689
|
+
console.warn("[subgraphNativeUsdtQuoter] subgraph errors:", json.errors.map((e) => e.message).join("; "));
|
|
4195
3690
|
return null;
|
|
4196
3691
|
}
|
|
4197
3692
|
const raw = json.data?.bundle?.ethPriceUSD;
|
|
4198
3693
|
if (!raw) return null;
|
|
4199
3694
|
const parsed = Number(raw);
|
|
4200
3695
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
4201
|
-
console.warn(
|
|
4202
|
-
`[subgraphNativeUsdtQuoter] invalid ethPriceUSD from subgraph: ${raw}`
|
|
4203
|
-
);
|
|
3696
|
+
console.warn(`[subgraphNativeUsdtQuoter] invalid ethPriceUSD from subgraph: ${raw}`);
|
|
4204
3697
|
return null;
|
|
4205
3698
|
}
|
|
4206
3699
|
const MIN_REASONABLE_ETH_PRICE = 100;
|
|
4207
3700
|
const MAX_REASONABLE_ETH_PRICE = 1e5;
|
|
4208
3701
|
if (parsed < MIN_REASONABLE_ETH_PRICE || parsed > MAX_REASONABLE_ETH_PRICE) {
|
|
4209
|
-
console.warn(
|
|
4210
|
-
`[PAFI] SubgraphNativeUsdtQuoter: ETH/USD price ${parsed} is outside reasonable range. Using fallback.`
|
|
4211
|
-
);
|
|
3702
|
+
console.warn(`[PAFI] SubgraphNativeUsdtQuoter: ETH/USD price ${parsed} is outside reasonable range. Using fallback.`);
|
|
4212
3703
|
return null;
|
|
4213
3704
|
}
|
|
4214
3705
|
return parsed;
|
|
4215
3706
|
}
|
|
3707
|
+
__name(fetchEthPriceFromSubgraph, "fetchEthPriceFromSubgraph");
|
|
4216
3708
|
function toUsdtPerNative(priceFloat, usdtDecimals) {
|
|
4217
3709
|
const fixed = priceFloat.toFixed(usdtDecimals);
|
|
4218
3710
|
const [whole, fraction = ""] = fixed.split(".");
|
|
4219
3711
|
const padded = (fraction + "0".repeat(usdtDecimals)).slice(0, usdtDecimals);
|
|
4220
3712
|
return BigInt(whole + padded);
|
|
4221
3713
|
}
|
|
3714
|
+
__name(toUsdtPerNative, "toUsdtPerNative");
|
|
4222
3715
|
|
|
4223
3716
|
// src/pools/nativePtQuoter.ts
|
|
4224
3717
|
import { parseAbi } from "viem";
|
|
@@ -4239,18 +3732,7 @@ var POOL_PRICE_QUERY = `
|
|
|
4239
3732
|
}
|
|
4240
3733
|
`;
|
|
4241
3734
|
function createNativePtQuoter(config) {
|
|
4242
|
-
const {
|
|
4243
|
-
provider,
|
|
4244
|
-
pointTokenAddress,
|
|
4245
|
-
chainlinkFeedAddress = "0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70",
|
|
4246
|
-
subgraphUrl = PAFI_SUBGRAPH_URL,
|
|
4247
|
-
cacheTtlMs = 3e4,
|
|
4248
|
-
fallbackEthPriceUsd = 3e3,
|
|
4249
|
-
fallbackPtPriceUsdt = 0.1,
|
|
4250
|
-
failClosed = false,
|
|
4251
|
-
fetchImpl = globalThis.fetch,
|
|
4252
|
-
now = () => Date.now()
|
|
4253
|
-
} = config;
|
|
3735
|
+
const { provider, pointTokenAddress, chainlinkFeedAddress = "0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70", subgraphUrl = PAFI_SUBGRAPH_URL, cacheTtlMs = 3e4, fallbackEthPriceUsd = 3e3, fallbackPtPriceUsdt = 0.1, failClosed = false, fetchImpl = globalThis.fetch, now = /* @__PURE__ */ __name(() => Date.now(), "now") } = config;
|
|
4254
3736
|
let ethPriceCache;
|
|
4255
3737
|
let ptPriceCache;
|
|
4256
3738
|
async function getEthPrice8dec() {
|
|
@@ -4269,28 +3751,34 @@ function createNativePtQuoter(config) {
|
|
|
4269
3751
|
if (ageS > CHAINLINK_MAX_AGE_S) {
|
|
4270
3752
|
throw new Error(`Chainlink: price stale by ${ageS}s`);
|
|
4271
3753
|
}
|
|
4272
|
-
ethPriceCache = {
|
|
3754
|
+
ethPriceCache = {
|
|
3755
|
+
value: answer,
|
|
3756
|
+
expiresAt: ts + cacheTtlMs
|
|
3757
|
+
};
|
|
4273
3758
|
return answer;
|
|
4274
3759
|
} catch (err) {
|
|
4275
3760
|
if (failClosed) {
|
|
4276
|
-
throw new Error(
|
|
4277
|
-
`[nativePtQuoter] Chainlink unavailable in fail-closed mode: ${err.message}`
|
|
4278
|
-
);
|
|
3761
|
+
throw new Error(`[nativePtQuoter] Chainlink unavailable in fail-closed mode: ${err.message}`);
|
|
4279
3762
|
}
|
|
4280
3763
|
console.warn("[nativePtQuoter] Chainlink unavailable, using fallback:", err.message);
|
|
4281
3764
|
return BigInt(Math.round(fallbackEthPriceUsd * 1e8));
|
|
4282
3765
|
}
|
|
4283
3766
|
}
|
|
3767
|
+
__name(getEthPrice8dec, "getEthPrice8dec");
|
|
4284
3768
|
async function getPtPerUsdt18dec() {
|
|
4285
3769
|
const ts = now();
|
|
4286
3770
|
if (ptPriceCache && ptPriceCache.expiresAt > ts) return ptPriceCache.value;
|
|
4287
3771
|
try {
|
|
4288
3772
|
const response = await fetchImpl(subgraphUrl, {
|
|
4289
3773
|
method: "POST",
|
|
4290
|
-
headers: {
|
|
3774
|
+
headers: {
|
|
3775
|
+
"Content-Type": "application/json"
|
|
3776
|
+
},
|
|
4291
3777
|
body: JSON.stringify({
|
|
4292
3778
|
query: POOL_PRICE_QUERY,
|
|
4293
|
-
variables: {
|
|
3779
|
+
variables: {
|
|
3780
|
+
id: pointTokenAddress.toLowerCase()
|
|
3781
|
+
}
|
|
4294
3782
|
})
|
|
4295
3783
|
});
|
|
4296
3784
|
if (!response.ok) throw new Error(`subgraph HTTP ${response.status}`);
|
|
@@ -4306,19 +3794,21 @@ function createNativePtQuoter(config) {
|
|
|
4306
3794
|
const raw = parseBigDecimalTo18(ptPerUsdtStr);
|
|
4307
3795
|
if (raw === 0n) throw new Error(`pool price parsed to zero: ${ptPerUsdtStr}`);
|
|
4308
3796
|
const value = 10n ** 24n / raw;
|
|
4309
|
-
ptPriceCache = {
|
|
3797
|
+
ptPriceCache = {
|
|
3798
|
+
value,
|
|
3799
|
+
expiresAt: ts + cacheTtlMs
|
|
3800
|
+
};
|
|
4310
3801
|
return value;
|
|
4311
3802
|
} catch (err) {
|
|
4312
3803
|
if (failClosed) {
|
|
4313
|
-
throw new Error(
|
|
4314
|
-
`[nativePtQuoter] subgraph miss for ${pointTokenAddress} in fail-closed mode: ${err.message}`
|
|
4315
|
-
);
|
|
3804
|
+
throw new Error(`[nativePtQuoter] subgraph miss for ${pointTokenAddress} in fail-closed mode: ${err.message}`);
|
|
4316
3805
|
}
|
|
4317
3806
|
console.warn("[nativePtQuoter] subgraph unavailable, using fallback:", err.message);
|
|
4318
3807
|
const ptPerUsdtHuman = 1 / fallbackPtPriceUsdt;
|
|
4319
3808
|
return parseBigDecimalTo18(ptPerUsdtHuman.toFixed(18));
|
|
4320
3809
|
}
|
|
4321
3810
|
}
|
|
3811
|
+
__name(getPtPerUsdt18dec, "getPtPerUsdt18dec");
|
|
4322
3812
|
return async (amountNative) => {
|
|
4323
3813
|
if (amountNative === 0n) return 0n;
|
|
4324
3814
|
const [ethPrice8dec, ptPerUsdt18dec] = await Promise.all([
|
|
@@ -4328,12 +3818,14 @@ function createNativePtQuoter(config) {
|
|
|
4328
3818
|
return amountNative * ethPrice8dec * ptPerUsdt18dec / 10n ** 26n;
|
|
4329
3819
|
};
|
|
4330
3820
|
}
|
|
3821
|
+
__name(createNativePtQuoter, "createNativePtQuoter");
|
|
4331
3822
|
function parseBigDecimalTo18(s) {
|
|
4332
3823
|
const SCALE = 18;
|
|
4333
3824
|
const [whole = "0", frac = ""] = s.split(".");
|
|
4334
3825
|
const padded = (frac + "0".repeat(SCALE)).slice(0, SCALE);
|
|
4335
3826
|
return BigInt(whole + padded);
|
|
4336
3827
|
}
|
|
3828
|
+
__name(parseBigDecimalTo18, "parseBigDecimalTo18");
|
|
4337
3829
|
|
|
4338
3830
|
// src/pafi-backend/client.ts
|
|
4339
3831
|
import { getPafiServiceUrls } from "@pafi-dev/core";
|
|
@@ -4341,15 +3833,24 @@ function extractPafiErrorFields(json, status) {
|
|
|
4341
3833
|
const inner = typeof json.error === "object" && json.error !== null ? json.error : null;
|
|
4342
3834
|
const code = inner?.code ?? json.code ?? "INTERNAL_ERROR";
|
|
4343
3835
|
const message = inner?.message ?? json.message ?? `HTTP ${status}`;
|
|
4344
|
-
return {
|
|
3836
|
+
return {
|
|
3837
|
+
code,
|
|
3838
|
+
message
|
|
3839
|
+
};
|
|
4345
3840
|
}
|
|
3841
|
+
__name(extractPafiErrorFields, "extractPafiErrorFields");
|
|
4346
3842
|
function serializeBigInt(_key, value) {
|
|
4347
3843
|
return typeof value === "bigint" ? value.toString(10) : value;
|
|
4348
3844
|
}
|
|
3845
|
+
__name(serializeBigInt, "serializeBigInt");
|
|
4349
3846
|
function sleep(ms) {
|
|
4350
3847
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
4351
3848
|
}
|
|
3849
|
+
__name(sleep, "sleep");
|
|
4352
3850
|
var PafiBackendClient = class {
|
|
3851
|
+
static {
|
|
3852
|
+
__name(this, "PafiBackendClient");
|
|
3853
|
+
}
|
|
4353
3854
|
config;
|
|
4354
3855
|
baseUrl;
|
|
4355
3856
|
constructor(config) {
|
|
@@ -4387,11 +3888,11 @@ var PafiBackendClient = class {
|
|
|
4387
3888
|
throw lastError;
|
|
4388
3889
|
}
|
|
4389
3890
|
/**
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
3891
|
+
* Fetch ERC-4337 UserOp receipt via PAFI's authenticated bundler proxy.
|
|
3892
|
+
* Returns `null` when the bundler hasn't seen the userOp yet — caller
|
|
3893
|
+
* should keep polling. Used by status endpoints to short-circuit the
|
|
3894
|
+
* on-chain indexer when several PENDING locks share the same amount.
|
|
3895
|
+
*/
|
|
4395
3896
|
async getUserOpReceipt(userOpHash) {
|
|
4396
3897
|
const fetchFn = this.config.fetchImpl ?? fetch;
|
|
4397
3898
|
const url = `${this.baseUrl}/bundler/receipt`;
|
|
@@ -4404,14 +3905,12 @@ var PafiBackendClient = class {
|
|
|
4404
3905
|
Authorization: `Bearer ${this.config.apiKey}`,
|
|
4405
3906
|
"X-Issuer-Id": this.config.issuerId
|
|
4406
3907
|
},
|
|
4407
|
-
body: JSON.stringify({
|
|
3908
|
+
body: JSON.stringify({
|
|
3909
|
+
userOpHash
|
|
3910
|
+
})
|
|
4408
3911
|
});
|
|
4409
3912
|
} catch (err) {
|
|
4410
|
-
throw new PafiBackendError(
|
|
4411
|
-
"NETWORK_ERROR",
|
|
4412
|
-
`Network error: ${err instanceof Error ? err.message : String(err)}`,
|
|
4413
|
-
0
|
|
4414
|
-
);
|
|
3913
|
+
throw new PafiBackendError("NETWORK_ERROR", `Network error: ${err instanceof Error ? err.message : String(err)}`, 0);
|
|
4415
3914
|
}
|
|
4416
3915
|
const text = await response.text();
|
|
4417
3916
|
let json = {};
|
|
@@ -4445,11 +3944,7 @@ var PafiBackendClient = class {
|
|
|
4445
3944
|
body: JSON.stringify(request)
|
|
4446
3945
|
});
|
|
4447
3946
|
} catch (err) {
|
|
4448
|
-
throw new PafiBackendError(
|
|
4449
|
-
"NETWORK_ERROR",
|
|
4450
|
-
`Network error: ${err instanceof Error ? err.message : String(err)}`,
|
|
4451
|
-
0
|
|
4452
|
-
);
|
|
3947
|
+
throw new PafiBackendError("NETWORK_ERROR", `Network error: ${err instanceof Error ? err.message : String(err)}`, 0);
|
|
4453
3948
|
}
|
|
4454
3949
|
const text = await response.text();
|
|
4455
3950
|
let json = {};
|
|
@@ -4461,7 +3956,9 @@ var PafiBackendClient = class {
|
|
|
4461
3956
|
const { code, message } = extractPafiErrorFields(json, response.status);
|
|
4462
3957
|
throw new PafiBackendError(code, message, response.status, json);
|
|
4463
3958
|
}
|
|
4464
|
-
return {
|
|
3959
|
+
return {
|
|
3960
|
+
userOpHash: json.userOpHash
|
|
3961
|
+
};
|
|
4465
3962
|
}
|
|
4466
3963
|
async _doRequest(request) {
|
|
4467
3964
|
const fetchFn = this.config.fetchImpl ?? fetch;
|
|
@@ -4479,11 +3976,7 @@ var PafiBackendClient = class {
|
|
|
4479
3976
|
body
|
|
4480
3977
|
});
|
|
4481
3978
|
} catch (err) {
|
|
4482
|
-
throw new PafiBackendError(
|
|
4483
|
-
"NETWORK_ERROR",
|
|
4484
|
-
`Network error: ${err instanceof Error ? err.message : String(err)}`,
|
|
4485
|
-
0
|
|
4486
|
-
);
|
|
3979
|
+
throw new PafiBackendError("NETWORK_ERROR", `Network error: ${err instanceof Error ? err.message : String(err)}`, 0);
|
|
4487
3980
|
}
|
|
4488
3981
|
const text = await response.text();
|
|
4489
3982
|
let json = {};
|
|
@@ -4504,9 +3997,7 @@ var PafiBackendClient = class {
|
|
|
4504
3997
|
return {
|
|
4505
3998
|
paymaster: json.paymaster,
|
|
4506
3999
|
paymasterData: json.paymasterData,
|
|
4507
|
-
paymasterVerificationGasLimit: BigInt(
|
|
4508
|
-
json.paymasterVerificationGasLimit
|
|
4509
|
-
),
|
|
4000
|
+
paymasterVerificationGasLimit: BigInt(json.paymasterVerificationGasLimit),
|
|
4510
4001
|
paymasterPostOpGasLimit: BigInt(json.paymasterPostOpGasLimit),
|
|
4511
4002
|
callGasLimit: json.callGasLimit != null ? BigInt(json.callGasLimit) : void 0,
|
|
4512
4003
|
verificationGasLimit: json.verificationGasLimit != null ? BigInt(json.verificationGasLimit) : void 0,
|
|
@@ -4519,8 +4010,8 @@ var PafiBackendClient = class {
|
|
|
4519
4010
|
};
|
|
4520
4011
|
|
|
4521
4012
|
// src/config.ts
|
|
4522
|
-
import { getAddress as
|
|
4523
|
-
import { getContractAddresses as
|
|
4013
|
+
import { getAddress as getAddress11 } from "viem";
|
|
4014
|
+
import { getContractAddresses as getContractAddresses5 } from "@pafi-dev/core";
|
|
4524
4015
|
|
|
4525
4016
|
// src/redemption/evaluator.ts
|
|
4526
4017
|
var SECONDS_PER_DAY = 24 * 60 * 60;
|
|
@@ -4530,10 +4021,7 @@ function evaluateRedemption(input) {
|
|
|
4530
4021
|
const cooldownUntilUnixSec = history.lastRedeemedAtUnixSec !== null ? history.lastRedeemedAtUnixSec + policy.cooldownSec : null;
|
|
4531
4022
|
const inCooldown = cooldownUntilUnixSec !== null && cooldownUntilUnixSec > nowUnixSec;
|
|
4532
4023
|
const activeBlackout = findActiveBlackout(policy.blackoutWindows, nowUnixSec);
|
|
4533
|
-
const nextBlackoutEnd = nextBlackoutEndAfter(
|
|
4534
|
-
policy.blackoutWindows,
|
|
4535
|
-
nowUnixSec
|
|
4536
|
-
);
|
|
4024
|
+
const nextBlackoutEnd = nextBlackoutEndAfter(policy.blackoutWindows, nowUnixSec);
|
|
4537
4025
|
let availableAmountPt = 0n;
|
|
4538
4026
|
if (!inCooldown && !activeBlackout) {
|
|
4539
4027
|
const headroom = dailyRemaining < policy.perTxMaxPt ? dailyRemaining : policy.perTxMaxPt;
|
|
@@ -4550,7 +4038,11 @@ function evaluateRedemption(input) {
|
|
|
4550
4038
|
policySource
|
|
4551
4039
|
};
|
|
4552
4040
|
if (amountPt <= 0n) {
|
|
4553
|
-
return {
|
|
4041
|
+
return {
|
|
4042
|
+
allowed: false,
|
|
4043
|
+
preview,
|
|
4044
|
+
denial: rejectAmountBelowMin(policy)
|
|
4045
|
+
};
|
|
4554
4046
|
}
|
|
4555
4047
|
const denial = firstDenial({
|
|
4556
4048
|
amountPt,
|
|
@@ -4560,25 +4052,29 @@ function evaluateRedemption(input) {
|
|
|
4560
4052
|
cooldownUntilUnixSec,
|
|
4561
4053
|
activeBlackout
|
|
4562
4054
|
});
|
|
4563
|
-
if (denial) return {
|
|
4564
|
-
|
|
4055
|
+
if (denial) return {
|
|
4056
|
+
allowed: false,
|
|
4057
|
+
denial,
|
|
4058
|
+
preview
|
|
4059
|
+
};
|
|
4060
|
+
return {
|
|
4061
|
+
allowed: true,
|
|
4062
|
+
preview
|
|
4063
|
+
};
|
|
4565
4064
|
}
|
|
4065
|
+
__name(evaluateRedemption, "evaluateRedemption");
|
|
4566
4066
|
function firstDenial(args) {
|
|
4567
4067
|
const { amountPt, policy, dailyRemaining, inCooldown, cooldownUntilUnixSec, activeBlackout } = args;
|
|
4568
4068
|
if (activeBlackout) {
|
|
4569
4069
|
return {
|
|
4570
4070
|
code: "BLACKOUT_WINDOW",
|
|
4571
|
-
message: `Redemption is blocked until ${new Date(
|
|
4572
|
-
activeBlackout.endUnixSec * 1e3
|
|
4573
|
-
).toISOString()}${activeBlackout.reason ? ` (${activeBlackout.reason})` : ""}`
|
|
4071
|
+
message: `Redemption is blocked until ${new Date(activeBlackout.endUnixSec * 1e3).toISOString()}${activeBlackout.reason ? ` (${activeBlackout.reason})` : ""}`
|
|
4574
4072
|
};
|
|
4575
4073
|
}
|
|
4576
4074
|
if (inCooldown && cooldownUntilUnixSec !== null) {
|
|
4577
4075
|
return {
|
|
4578
4076
|
code: "COOLDOWN_ACTIVE",
|
|
4579
|
-
message: `Cooldown active until ${new Date(
|
|
4580
|
-
cooldownUntilUnixSec * 1e3
|
|
4581
|
-
).toISOString()}`
|
|
4077
|
+
message: `Cooldown active until ${new Date(cooldownUntilUnixSec * 1e3).toISOString()}`
|
|
4582
4078
|
};
|
|
4583
4079
|
}
|
|
4584
4080
|
if (amountPt < policy.perTxMinPt) {
|
|
@@ -4601,18 +4097,21 @@ function firstDenial(args) {
|
|
|
4601
4097
|
}
|
|
4602
4098
|
return null;
|
|
4603
4099
|
}
|
|
4100
|
+
__name(firstDenial, "firstDenial");
|
|
4604
4101
|
function rejectAmountBelowMin(policy) {
|
|
4605
4102
|
return {
|
|
4606
4103
|
code: "AMOUNT_BELOW_MIN",
|
|
4607
4104
|
message: `amount must be >= ${policy.perTxMinPt}`
|
|
4608
4105
|
};
|
|
4609
4106
|
}
|
|
4107
|
+
__name(rejectAmountBelowMin, "rejectAmountBelowMin");
|
|
4610
4108
|
function findActiveBlackout(windows, nowUnixSec) {
|
|
4611
4109
|
for (const w of windows) {
|
|
4612
4110
|
if (w.startUnixSec <= nowUnixSec && nowUnixSec < w.endUnixSec) return w;
|
|
4613
4111
|
}
|
|
4614
4112
|
return null;
|
|
4615
4113
|
}
|
|
4114
|
+
__name(findActiveBlackout, "findActiveBlackout");
|
|
4616
4115
|
function nextBlackoutEndAfter(windows, nowUnixSec) {
|
|
4617
4116
|
let earliest = null;
|
|
4618
4117
|
for (const w of windows) {
|
|
@@ -4622,17 +4121,19 @@ function nextBlackoutEndAfter(windows, nowUnixSec) {
|
|
|
4622
4121
|
}
|
|
4623
4122
|
return earliest;
|
|
4624
4123
|
}
|
|
4124
|
+
__name(nextBlackoutEndAfter, "nextBlackoutEndAfter");
|
|
4625
4125
|
var REDEMPTION_HISTORY_WINDOW_SEC = SECONDS_PER_DAY;
|
|
4626
4126
|
|
|
4627
4127
|
// src/redemption/policyProvider.ts
|
|
4628
4128
|
import { PafiSdkError as PafiSdkError2 } from "@pafi-dev/core";
|
|
4629
4129
|
|
|
4630
4130
|
// src/redemption/settlementClient.ts
|
|
4631
|
-
import {
|
|
4632
|
-
getPafiServiceUrls as getPafiServiceUrls2
|
|
4633
|
-
} from "@pafi-dev/core";
|
|
4131
|
+
import { getPafiServiceUrls as getPafiServiceUrls2 } from "@pafi-dev/core";
|
|
4634
4132
|
var DEFAULT_TIMEOUT_MS = 1e3;
|
|
4635
4133
|
var SettlementClient = class {
|
|
4134
|
+
static {
|
|
4135
|
+
__name(this, "SettlementClient");
|
|
4136
|
+
}
|
|
4636
4137
|
config;
|
|
4637
4138
|
constructor(config) {
|
|
4638
4139
|
if (!config.chainId) throw new Error("SettlementClient: chainId is required");
|
|
@@ -4652,10 +4153,7 @@ var SettlementClient = class {
|
|
|
4652
4153
|
const fetchFn = this.config.fetchImpl ?? fetch;
|
|
4653
4154
|
const url = `${this.config.baseUrl}/issuers/${encodeURIComponent(this.config.issuerId)}/redemption-policy`;
|
|
4654
4155
|
const controller = new AbortController();
|
|
4655
|
-
const timer = setTimeout(
|
|
4656
|
-
() => controller.abort(),
|
|
4657
|
-
this.config.fetchTimeoutMs
|
|
4658
|
-
);
|
|
4156
|
+
const timer = setTimeout(() => controller.abort(), this.config.fetchTimeoutMs);
|
|
4659
4157
|
let response;
|
|
4660
4158
|
try {
|
|
4661
4159
|
response = await fetchFn(url, {
|
|
@@ -4669,30 +4167,56 @@ var SettlementClient = class {
|
|
|
4669
4167
|
});
|
|
4670
4168
|
} catch (err) {
|
|
4671
4169
|
const isAbort = err instanceof Error && (err.name === "AbortError" || /aborted|timeout/i.test(err.message ?? ""));
|
|
4672
|
-
return {
|
|
4170
|
+
return {
|
|
4171
|
+
ok: false,
|
|
4172
|
+
reason: isAbort ? "TIMEOUT" : "NETWORK"
|
|
4173
|
+
};
|
|
4673
4174
|
} finally {
|
|
4674
4175
|
clearTimeout(timer);
|
|
4675
4176
|
}
|
|
4676
4177
|
if (response.status === 404) {
|
|
4677
|
-
return {
|
|
4178
|
+
return {
|
|
4179
|
+
ok: false,
|
|
4180
|
+
reason: "NOT_FOUND",
|
|
4181
|
+
status: 404
|
|
4182
|
+
};
|
|
4678
4183
|
}
|
|
4679
4184
|
if (response.status === 401 || response.status === 403) {
|
|
4680
|
-
return {
|
|
4185
|
+
return {
|
|
4186
|
+
ok: false,
|
|
4187
|
+
reason: "UNAUTHORIZED",
|
|
4188
|
+
status: response.status
|
|
4189
|
+
};
|
|
4681
4190
|
}
|
|
4682
4191
|
if (!response.ok) {
|
|
4683
|
-
return {
|
|
4192
|
+
return {
|
|
4193
|
+
ok: false,
|
|
4194
|
+
reason: "SERVER_ERROR",
|
|
4195
|
+
status: response.status
|
|
4196
|
+
};
|
|
4684
4197
|
}
|
|
4685
4198
|
let raw;
|
|
4686
4199
|
try {
|
|
4687
4200
|
raw = await response.json();
|
|
4688
4201
|
} catch {
|
|
4689
|
-
return {
|
|
4202
|
+
return {
|
|
4203
|
+
ok: false,
|
|
4204
|
+
reason: "INVALID_RESPONSE",
|
|
4205
|
+
status: response.status
|
|
4206
|
+
};
|
|
4690
4207
|
}
|
|
4691
4208
|
const parsed = parsePolicyDto(raw);
|
|
4692
4209
|
if (!parsed) {
|
|
4693
|
-
return {
|
|
4210
|
+
return {
|
|
4211
|
+
ok: false,
|
|
4212
|
+
reason: "INVALID_RESPONSE",
|
|
4213
|
+
status: response.status
|
|
4214
|
+
};
|
|
4694
4215
|
}
|
|
4695
|
-
return {
|
|
4216
|
+
return {
|
|
4217
|
+
ok: true,
|
|
4218
|
+
policy: parsed
|
|
4219
|
+
};
|
|
4696
4220
|
}
|
|
4697
4221
|
};
|
|
4698
4222
|
function parsePolicyDto(raw) {
|
|
@@ -4715,6 +4239,7 @@ function parsePolicyDto(raw) {
|
|
|
4715
4239
|
return null;
|
|
4716
4240
|
}
|
|
4717
4241
|
}
|
|
4242
|
+
__name(parsePolicyDto, "parsePolicyDto");
|
|
4718
4243
|
function normalizeBlackout(raw) {
|
|
4719
4244
|
if (!raw || typeof raw !== "object") return null;
|
|
4720
4245
|
const win = raw;
|
|
@@ -4727,6 +4252,7 @@ function normalizeBlackout(raw) {
|
|
|
4727
4252
|
reason: typeof win.reason === "string" ? win.reason : void 0
|
|
4728
4253
|
};
|
|
4729
4254
|
}
|
|
4255
|
+
__name(normalizeBlackout, "normalizeBlackout");
|
|
4730
4256
|
|
|
4731
4257
|
// src/redemption/defaults.ts
|
|
4732
4258
|
var PT_DECIMALS = 10n ** 18n;
|
|
@@ -4740,23 +4266,34 @@ var DEFAULT_REDEMPTION_POLICY = {
|
|
|
4740
4266
|
version: "default-v1"
|
|
4741
4267
|
};
|
|
4742
4268
|
function defaultPolicyFor(issuerId) {
|
|
4743
|
-
return {
|
|
4269
|
+
return {
|
|
4270
|
+
...DEFAULT_REDEMPTION_POLICY,
|
|
4271
|
+
issuerId
|
|
4272
|
+
};
|
|
4744
4273
|
}
|
|
4274
|
+
__name(defaultPolicyFor, "defaultPolicyFor");
|
|
4745
4275
|
|
|
4746
4276
|
// src/redemption/policyProvider.ts
|
|
4747
4277
|
var DEFAULT_CACHE_TTL_MS3 = 5 * 60 * 1e3;
|
|
4748
4278
|
var PolicyProviderUnavailableError = class extends PafiSdkError2 {
|
|
4279
|
+
static {
|
|
4280
|
+
__name(this, "PolicyProviderUnavailableError");
|
|
4281
|
+
}
|
|
4749
4282
|
code = "POLICY_PROVIDER_UNAVAILABLE";
|
|
4750
4283
|
httpStatus = "service_unavailable";
|
|
4751
4284
|
details;
|
|
4752
4285
|
constructor(issuerId, reason) {
|
|
4753
|
-
super(
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4286
|
+
super(`Redemption policy provider unavailable for issuer ${issuerId}: ${reason}. Pre-flight redeem limit cannot be enforced \u2014 refusing to sign BurnRequest. Mobile FE: surface "try again shortly" and retry with backoff.`);
|
|
4287
|
+
this.details = {
|
|
4288
|
+
issuerId,
|
|
4289
|
+
reason
|
|
4290
|
+
};
|
|
4757
4291
|
}
|
|
4758
4292
|
};
|
|
4759
4293
|
var PolicyProvider = class {
|
|
4294
|
+
static {
|
|
4295
|
+
__name(this, "PolicyProvider");
|
|
4296
|
+
}
|
|
4760
4297
|
client;
|
|
4761
4298
|
issuerId;
|
|
4762
4299
|
cacheTtlMs;
|
|
@@ -4775,7 +4312,10 @@ var PolicyProvider = class {
|
|
|
4775
4312
|
}
|
|
4776
4313
|
async getPolicy() {
|
|
4777
4314
|
const fresh = this.readCache();
|
|
4778
|
-
if (fresh) return {
|
|
4315
|
+
if (fresh) return {
|
|
4316
|
+
policy: fresh,
|
|
4317
|
+
source: "cache"
|
|
4318
|
+
};
|
|
4779
4319
|
if (this.inflight) return this.inflight;
|
|
4780
4320
|
this.inflight = this.fetchAndStore().finally(() => {
|
|
4781
4321
|
this.inflight = null;
|
|
@@ -4801,19 +4341,22 @@ var PolicyProvider = class {
|
|
|
4801
4341
|
policy: result.policy,
|
|
4802
4342
|
expiresAtMs: this.now() + this.cacheTtlMs
|
|
4803
4343
|
};
|
|
4804
|
-
return {
|
|
4344
|
+
return {
|
|
4345
|
+
policy: result.policy,
|
|
4346
|
+
source: "settlement"
|
|
4347
|
+
};
|
|
4805
4348
|
}
|
|
4806
4349
|
const reason = "reason" in result && typeof result.reason === "string" ? result.reason : "unknown";
|
|
4807
4350
|
if (this.onFetchFailure === "permissive-default") {
|
|
4808
|
-
this.onWarning?.(
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4351
|
+
this.onWarning?.("PolicyProvider: settlement-api unreachable, falling back to permissive default. Pre-flight redeem limit is DEGRADED until settlement-api recovers.", {
|
|
4352
|
+
event: "policy_provider_fallback",
|
|
4353
|
+
issuerId: this.issuerId,
|
|
4354
|
+
reason
|
|
4355
|
+
});
|
|
4356
|
+
return {
|
|
4357
|
+
policy: defaultPolicyFor(this.issuerId),
|
|
4358
|
+
source: "default"
|
|
4359
|
+
};
|
|
4817
4360
|
}
|
|
4818
4361
|
throw new PolicyProviderUnavailableError(this.issuerId, reason);
|
|
4819
4362
|
}
|
|
@@ -4821,6 +4364,9 @@ var PolicyProvider = class {
|
|
|
4821
4364
|
|
|
4822
4365
|
// src/redemption/service.ts
|
|
4823
4366
|
var RedemptionService = class {
|
|
4367
|
+
static {
|
|
4368
|
+
__name(this, "RedemptionService");
|
|
4369
|
+
}
|
|
4824
4370
|
policyProvider;
|
|
4825
4371
|
historyStore;
|
|
4826
4372
|
nowUnixSec;
|
|
@@ -4837,17 +4383,16 @@ var RedemptionService = class {
|
|
|
4837
4383
|
const { policy, source } = await this.policyProvider.getPolicy();
|
|
4838
4384
|
const now = this.nowUnixSec();
|
|
4839
4385
|
const [redeemedLast24hPt, lastRedeemedAtUnixSec] = await Promise.all([
|
|
4840
|
-
this.historyStore.sumRedeemedSince(
|
|
4841
|
-
user,
|
|
4842
|
-
now - REDEMPTION_HISTORY_WINDOW_SEC,
|
|
4843
|
-
pointTokenAddress
|
|
4844
|
-
),
|
|
4386
|
+
this.historyStore.sumRedeemedSince(user, now - REDEMPTION_HISTORY_WINDOW_SEC, pointTokenAddress),
|
|
4845
4387
|
this.historyStore.getLastRedeemedAtUnixSec(user, pointTokenAddress)
|
|
4846
4388
|
]);
|
|
4847
4389
|
return evaluateRedemption({
|
|
4848
4390
|
policy,
|
|
4849
4391
|
policySource: source,
|
|
4850
|
-
history: {
|
|
4392
|
+
history: {
|
|
4393
|
+
redeemedLast24hPt,
|
|
4394
|
+
lastRedeemedAtUnixSec
|
|
4395
|
+
},
|
|
4851
4396
|
amountPt,
|
|
4852
4397
|
nowUnixSec: now
|
|
4853
4398
|
});
|
|
@@ -4874,16 +4419,18 @@ async function createIssuerService(config) {
|
|
|
4874
4419
|
if (!config.auth?.domain) {
|
|
4875
4420
|
throw new Error("createIssuerService: auth.domain is required");
|
|
4876
4421
|
}
|
|
4877
|
-
const rawAddresses = config.pointTokenAddresses && config.pointTokenAddresses.length > 0 ? config.pointTokenAddresses : config.pointTokenAddress ? [
|
|
4422
|
+
const rawAddresses = config.pointTokenAddresses && config.pointTokenAddresses.length > 0 ? config.pointTokenAddresses : config.pointTokenAddress ? [
|
|
4423
|
+
config.pointTokenAddress
|
|
4424
|
+
] : [];
|
|
4878
4425
|
if (rawAddresses.length === 0) {
|
|
4879
|
-
throw new Error(
|
|
4880
|
-
"createIssuerService: at least one of pointTokenAddress / pointTokenAddresses is required"
|
|
4881
|
-
);
|
|
4426
|
+
throw new Error("createIssuerService: at least one of pointTokenAddress / pointTokenAddresses is required");
|
|
4882
4427
|
}
|
|
4883
|
-
const tokenAddresses = rawAddresses.map((a) =>
|
|
4428
|
+
const tokenAddresses = rawAddresses.map((a) => getAddress11(a));
|
|
4884
4429
|
const ledger = config.ledger;
|
|
4885
4430
|
const sessionStore = config.sessionStore ?? new MemorySessionStore();
|
|
4886
|
-
const policy = config.policy ?? new DefaultPolicyEngine({
|
|
4431
|
+
const policy = config.policy ?? new DefaultPolicyEngine({
|
|
4432
|
+
ledger
|
|
4433
|
+
});
|
|
4887
4434
|
const authServiceConfig = {
|
|
4888
4435
|
sessionStore,
|
|
4889
4436
|
jwtSecret: config.auth.jwtSecret,
|
|
@@ -4905,15 +4452,13 @@ async function createIssuerService(config) {
|
|
|
4905
4452
|
provider: config.provider
|
|
4906
4453
|
});
|
|
4907
4454
|
}
|
|
4908
|
-
const sdkWrapperAddress =
|
|
4455
|
+
const sdkWrapperAddress = getContractAddresses5(config.chainId).mintFeeWrapper;
|
|
4909
4456
|
const wrapperOverride = config.indexer?.mintFeeWrapperAddress;
|
|
4910
4457
|
const resolvedWrapperAddress = wrapperOverride !== void 0 ? wrapperOverride : sdkWrapperAddress;
|
|
4911
4458
|
const baseCursorStore = config.indexer?.cursorStore;
|
|
4912
4459
|
const sharedCursorWithMultipleTokens = baseCursorStore !== void 0 && typeof baseCursorStore.forKey !== "function" && tokenAddresses.length > 1;
|
|
4913
4460
|
if (sharedCursorWithMultipleTokens) {
|
|
4914
|
-
console.warn(
|
|
4915
|
-
`[@pafi-dev/issuer] cursorStore lacks forKey() and ${tokenAddresses.length} PointTokens are configured. All PointIndexers will share one cursor row, causing token-skipping. Implement IIndexerCursorStore.forKey to return per-token derived stores. This permissive path will be removed in a future major release.`
|
|
4916
|
-
);
|
|
4461
|
+
console.warn(`[@pafi-dev/issuer] cursorStore lacks forKey() and ${tokenAddresses.length} PointTokens are configured. All PointIndexers will share one cursor row, causing token-skipping. Implement IIndexerCursorStore.forKey to return per-token derived stores. This permissive path will be removed in a future major release.`);
|
|
4917
4462
|
}
|
|
4918
4463
|
const indexers = /* @__PURE__ */ new Map();
|
|
4919
4464
|
for (const tokenAddress of tokenAddresses) {
|
|
@@ -4929,9 +4474,7 @@ async function createIssuerService(config) {
|
|
|
4929
4474
|
indexerConfig.fromBlock = config.indexer.fromBlock;
|
|
4930
4475
|
}
|
|
4931
4476
|
if (baseCursorStore) {
|
|
4932
|
-
indexerConfig.cursorStore = typeof baseCursorStore.forKey === "function" ? baseCursorStore.forKey(
|
|
4933
|
-
`point-indexer:${tokenAddress.toLowerCase()}`
|
|
4934
|
-
) : baseCursorStore;
|
|
4477
|
+
indexerConfig.cursorStore = typeof baseCursorStore.forKey === "function" ? baseCursorStore.forKey(`point-indexer:${tokenAddress.toLowerCase()}`) : baseCursorStore;
|
|
4935
4478
|
}
|
|
4936
4479
|
if (config.indexer?.confirmations !== void 0) {
|
|
4937
4480
|
indexerConfig.confirmations = config.indexer.confirmations;
|
|
@@ -4944,9 +4487,9 @@ async function createIssuerService(config) {
|
|
|
4944
4487
|
}
|
|
4945
4488
|
indexers.set(tokenAddress, new PointIndexer(indexerConfig));
|
|
4946
4489
|
}
|
|
4947
|
-
const chainAddresses =
|
|
4490
|
+
const chainAddresses = getContractAddresses5(config.chainId);
|
|
4948
4491
|
const resolvedContracts = {
|
|
4949
|
-
|
|
4492
|
+
kernel: chainAddresses.kernel,
|
|
4950
4493
|
usdt: chainAddresses.usdt,
|
|
4951
4494
|
issuerRegistry: chainAddresses.issuerRegistry,
|
|
4952
4495
|
mintingOracle: chainAddresses.mintingOracle,
|
|
@@ -5002,9 +4545,7 @@ async function createIssuerService(config) {
|
|
|
5002
4545
|
if (config.indexer?.autoStart) {
|
|
5003
4546
|
const lock = config.indexer.singletonLock;
|
|
5004
4547
|
if (!lock) {
|
|
5005
|
-
console.warn(
|
|
5006
|
-
"[@pafi-dev/issuer] indexer.autoStart=true without singletonLock \u2014 this is UNSAFE in multi-replica deployments. Either set replicas=1 + INDEXER_AUTOSTART=false on non-leader pods, or pass `singletonLock: makePostgresSingletonLock(dataSource)`. This permissive path will be removed in a future major release."
|
|
5007
|
-
);
|
|
4548
|
+
console.warn("[@pafi-dev/issuer] indexer.autoStart=true without singletonLock \u2014 this is UNSAFE in multi-replica deployments. Either set replicas=1 + INDEXER_AUTOSTART=false on non-leader pods, or pass `singletonLock: makePostgresSingletonLock(dataSource)`. This permissive path will be removed in a future major release.");
|
|
5008
4549
|
for (const idx of indexers.values()) {
|
|
5009
4550
|
idx.start();
|
|
5010
4551
|
}
|
|
@@ -5033,42 +4574,42 @@ async function createIssuerService(config) {
|
|
|
5033
4574
|
redemption
|
|
5034
4575
|
};
|
|
5035
4576
|
}
|
|
4577
|
+
__name(createIssuerService, "createIssuerService");
|
|
5036
4578
|
|
|
5037
4579
|
// src/issuer-state/validator.ts
|
|
5038
|
-
import { getAddress as
|
|
5039
|
-
import {
|
|
5040
|
-
POINT_TOKEN_ABI as POINT_TOKEN_ABI3,
|
|
5041
|
-
issuerRegistryAbi,
|
|
5042
|
-
getContractAddresses as getContractAddresses8
|
|
5043
|
-
} from "@pafi-dev/core";
|
|
4580
|
+
import { getAddress as getAddress12 } from "viem";
|
|
4581
|
+
import { POINT_TOKEN_ABI as POINT_TOKEN_ABI3, issuerRegistryAbi, getContractAddresses as getContractAddresses6 } from "@pafi-dev/core";
|
|
5044
4582
|
var ISSUER_RECORD_TTL_MS = 1e4;
|
|
5045
4583
|
var IssuerStateValidator = class _IssuerStateValidator {
|
|
5046
|
-
|
|
5047
|
-
this
|
|
5048
|
-
this.registryAddress = registryAddress;
|
|
4584
|
+
static {
|
|
4585
|
+
__name(this, "IssuerStateValidator");
|
|
5049
4586
|
}
|
|
5050
4587
|
provider;
|
|
5051
4588
|
registryAddress;
|
|
5052
4589
|
pointTokenIssuerCache = /* @__PURE__ */ new Map();
|
|
5053
4590
|
stateCache = /* @__PURE__ */ new Map();
|
|
5054
4591
|
inflight = /* @__PURE__ */ new Map();
|
|
4592
|
+
constructor(provider, registryAddress) {
|
|
4593
|
+
this.provider = provider;
|
|
4594
|
+
this.registryAddress = registryAddress;
|
|
4595
|
+
}
|
|
5055
4596
|
/**
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
|
|
4597
|
+
* Convenience factory — reads `registryAddress` from the SDK
|
|
4598
|
+
* `CONTRACT_ADDRESSES` map for the given chain.
|
|
4599
|
+
*/
|
|
5059
4600
|
static forChain(provider, chainId) {
|
|
5060
|
-
const { issuerRegistry } =
|
|
4601
|
+
const { issuerRegistry } = getContractAddresses6(chainId);
|
|
5061
4602
|
return new _IssuerStateValidator(provider, issuerRegistry);
|
|
5062
4603
|
}
|
|
5063
4604
|
/**
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
4605
|
+
* Invalidate cached state for one PointToken, or everything if omitted.
|
|
4606
|
+
* Call after admin txs that change registry or cap settings — closes
|
|
4607
|
+
* the split-brain window described
|
|
4608
|
+
* passive TTL. Idempotent: safe to call when no entry exists.
|
|
4609
|
+
*/
|
|
5069
4610
|
invalidate(pointToken) {
|
|
5070
4611
|
if (pointToken) {
|
|
5071
|
-
const key =
|
|
4612
|
+
const key = getAddress12(pointToken);
|
|
5072
4613
|
this.pointTokenIssuerCache.delete(key);
|
|
5073
4614
|
this.stateCache.delete(key);
|
|
5074
4615
|
this.inflight.delete(key);
|
|
@@ -5079,11 +4620,11 @@ var IssuerStateValidator = class _IssuerStateValidator {
|
|
|
5079
4620
|
}
|
|
5080
4621
|
}
|
|
5081
4622
|
/**
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
4623
|
+
* Resolve `PointToken.issuer()` once per token and memoize.
|
|
4624
|
+
* The issuer field is set at `initialize()` and never changes.
|
|
4625
|
+
*/
|
|
5085
4626
|
async getIssuerAddressForPointToken(pointToken) {
|
|
5086
|
-
const key =
|
|
4627
|
+
const key = getAddress12(pointToken);
|
|
5087
4628
|
const cached = this.pointTokenIssuerCache.get(key);
|
|
5088
4629
|
if (cached) return cached;
|
|
5089
4630
|
const issuer = await this.provider.readContract({
|
|
@@ -5091,15 +4632,15 @@ var IssuerStateValidator = class _IssuerStateValidator {
|
|
|
5091
4632
|
abi: POINT_TOKEN_ABI3,
|
|
5092
4633
|
functionName: "issuer"
|
|
5093
4634
|
});
|
|
5094
|
-
this.pointTokenIssuerCache.set(key,
|
|
5095
|
-
return
|
|
4635
|
+
this.pointTokenIssuerCache.set(key, getAddress12(issuer));
|
|
4636
|
+
return getAddress12(issuer);
|
|
5096
4637
|
}
|
|
5097
4638
|
/**
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
4639
|
+
* Read registry record + totalSupply, with 30s cache and in-flight
|
|
4640
|
+
* deduplication. Does NOT throw on inactive/missing — returns raw state.
|
|
4641
|
+
*/
|
|
5101
4642
|
async getIssuerState(pointToken) {
|
|
5102
|
-
const tokenAddr =
|
|
4643
|
+
const tokenAddr = getAddress12(pointToken);
|
|
5103
4644
|
const now = Date.now();
|
|
5104
4645
|
const cached = this.stateCache.get(tokenAddr);
|
|
5105
4646
|
if (cached && cached.expiresAt > now) return cached.value;
|
|
@@ -5118,49 +4659,41 @@ var IssuerStateValidator = class _IssuerStateValidator {
|
|
|
5118
4659
|
return promise;
|
|
5119
4660
|
}
|
|
5120
4661
|
/**
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
4662
|
+
* Validate that `amount` PT can be minted on `pointToken` right now.
|
|
4663
|
+
*
|
|
4664
|
+
* Throws `IssuerStateError` with:
|
|
4665
|
+
* - `ISSUER_NOT_REGISTERED` — registry has no record for this issuer
|
|
4666
|
+
* - `ISSUER_INACTIVE` — issuer.active is false
|
|
4667
|
+
* - `MINT_CAP_EXCEEDED` — totalSupply + amount would exceed hardCap
|
|
4668
|
+
*
|
|
4669
|
+
* Returns the fetched state on success so callers can log without a
|
|
4670
|
+
* second RPC round-trip.
|
|
4671
|
+
*/
|
|
5131
4672
|
async preValidateMint(pointToken, amount) {
|
|
5132
4673
|
let state;
|
|
5133
4674
|
try {
|
|
5134
4675
|
state = await this.getIssuerState(pointToken);
|
|
5135
4676
|
} catch (err) {
|
|
5136
4677
|
if (err.message.includes("IssuerNotFound")) {
|
|
5137
|
-
throw new IssuerStateError(
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
{ pointToken }
|
|
5141
|
-
);
|
|
4678
|
+
throw new IssuerStateError("ISSUER_NOT_REGISTERED", `IssuerRegistry has no record for PointToken ${pointToken}`, {
|
|
4679
|
+
pointToken
|
|
4680
|
+
});
|
|
5142
4681
|
}
|
|
5143
4682
|
throw err;
|
|
5144
4683
|
}
|
|
5145
4684
|
const { issuer, equityCap, equitySupply, remaining } = state;
|
|
5146
4685
|
if (!issuer.active) {
|
|
5147
|
-
throw new IssuerStateError(
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
{ pointToken }
|
|
5151
|
-
);
|
|
4686
|
+
throw new IssuerStateError("ISSUER_INACTIVE", `Issuer "${issuer.name}" is deactivated on IssuerRegistry`, {
|
|
4687
|
+
pointToken
|
|
4688
|
+
});
|
|
5152
4689
|
}
|
|
5153
4690
|
if (equitySupply + amount > equityCap.hardCap) {
|
|
5154
|
-
throw new IssuerStateError(
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
equityMinted: equitySupply.toString(),
|
|
5161
|
-
remaining: remaining.toString()
|
|
5162
|
-
}
|
|
5163
|
-
);
|
|
4691
|
+
throw new IssuerStateError("MINT_CAP_EXCEEDED", `Requested ${amount} PT would exceed EQUITY mint cap. Cap=${equityCap.hardCap}, equityMinted=${equitySupply}, remaining=${remaining}`, {
|
|
4692
|
+
requested: amount.toString(),
|
|
4693
|
+
cap: equityCap.hardCap.toString(),
|
|
4694
|
+
equityMinted: equitySupply.toString(),
|
|
4695
|
+
remaining: remaining.toString()
|
|
4696
|
+
});
|
|
5164
4697
|
}
|
|
5165
4698
|
return state;
|
|
5166
4699
|
}
|
|
@@ -5170,7 +4703,9 @@ var IssuerStateValidator = class _IssuerStateValidator {
|
|
|
5170
4703
|
address: this.registryAddress,
|
|
5171
4704
|
abi: issuerRegistryAbi,
|
|
5172
4705
|
functionName: "getIssuer",
|
|
5173
|
-
args: [
|
|
4706
|
+
args: [
|
|
4707
|
+
issuerAddr
|
|
4708
|
+
]
|
|
5174
4709
|
});
|
|
5175
4710
|
const issuer = {
|
|
5176
4711
|
signerAddress: issuerStruct.signerAddress,
|
|
@@ -5202,6 +4737,9 @@ var IssuerStateValidator = class _IssuerStateValidator {
|
|
|
5202
4737
|
|
|
5203
4738
|
// src/redemption/memoryHistoryStore.ts
|
|
5204
4739
|
var MemoryRedemptionHistoryStore = class {
|
|
4740
|
+
static {
|
|
4741
|
+
__name(this, "MemoryRedemptionHistoryStore");
|
|
4742
|
+
}
|
|
5205
4743
|
entries = [];
|
|
5206
4744
|
async sumRedeemedSince(user, sinceUnixSec, pointTokenAddress) {
|
|
5207
4745
|
const userKey = user.toLowerCase();
|
|
@@ -5237,7 +4775,7 @@ var MemoryRedemptionHistoryStore = class {
|
|
|
5237
4775
|
};
|
|
5238
4776
|
|
|
5239
4777
|
// src/index.ts
|
|
5240
|
-
var PAFI_ISSUER_SDK_VERSION = true ? "0.
|
|
4778
|
+
var PAFI_ISSUER_SDK_VERSION = true ? "0.40.0" : "dev";
|
|
5241
4779
|
export {
|
|
5242
4780
|
AdapterMisconfiguredError,
|
|
5243
4781
|
AuthError,
|
|
@@ -5301,7 +4839,6 @@ export {
|
|
|
5301
4839
|
defaultPolicyFor,
|
|
5302
4840
|
evaluateRedemption,
|
|
5303
4841
|
handleClaimStatus,
|
|
5304
|
-
handleDelegateSubmit,
|
|
5305
4842
|
handleMobilePrepare,
|
|
5306
4843
|
handleMobileSubmit,
|
|
5307
4844
|
handleRedeemStatus,
|
|
@@ -5313,7 +4850,6 @@ export {
|
|
|
5313
4850
|
prepareMobileUserOp,
|
|
5314
4851
|
relayUserOp,
|
|
5315
4852
|
requestPaymaster,
|
|
5316
|
-
serializeEntryToJsonRpc
|
|
5317
|
-
serializeUserOpTypedData
|
|
4853
|
+
serializeEntryToJsonRpc
|
|
5318
4854
|
};
|
|
5319
4855
|
//# sourceMappingURL=index.js.map
|