@pafi-dev/issuer 0.39.1 → 0.39.3
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.d.cts +25 -10
- package/dist/direct-auth/index.d.ts +25 -10
- package/dist/direct-auth/index.js +305 -135
- 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 +1096 -1280
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1094 -1355
- 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 +4 -2
- package/dist/chunk-BRKEJJFQ.js +0 -17
- /package/dist/{chunk-BRKEJJFQ.js.map → chunk-7QVYU63E.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -3,6 +3,7 @@ var __defProp = Object.defineProperty;
|
|
|
3
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
6
7
|
var __export = (target, all) => {
|
|
7
8
|
for (var name in all)
|
|
8
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -104,6 +105,9 @@ var import_core = require("@pafi-dev/core");
|
|
|
104
105
|
var import_core2 = require("@pafi-dev/core");
|
|
105
106
|
var import_core3 = require("@pafi-dev/core");
|
|
106
107
|
var ConfigurationError = class extends import_core2.PafiSdkError {
|
|
108
|
+
static {
|
|
109
|
+
__name(this, "ConfigurationError");
|
|
110
|
+
}
|
|
107
111
|
httpStatus = "service_unavailable";
|
|
108
112
|
code;
|
|
109
113
|
details;
|
|
@@ -128,6 +132,7 @@ function buildSdkErrorBody(err) {
|
|
|
128
132
|
if (err.details !== void 0) body.details = err.details;
|
|
129
133
|
return body;
|
|
130
134
|
}
|
|
135
|
+
__name(buildSdkErrorBody, "buildSdkErrorBody");
|
|
131
136
|
function createSdkErrorMapper(factories) {
|
|
132
137
|
return (err) => {
|
|
133
138
|
if (!(err instanceof import_core.PafiSdkError)) {
|
|
@@ -146,17 +151,20 @@ function createSdkErrorMapper(factories) {
|
|
|
146
151
|
}
|
|
147
152
|
};
|
|
148
153
|
}
|
|
154
|
+
__name(createSdkErrorMapper, "createSdkErrorMapper");
|
|
149
155
|
|
|
150
156
|
// src/http/errorEnvelope.ts
|
|
151
157
|
function isValidationPipeBody(body) {
|
|
152
158
|
return Array.isArray(body["message"]) && typeof body["error"] === "string" && body["error"] === "Bad Request";
|
|
153
159
|
}
|
|
160
|
+
__name(isValidationPipeBody, "isValidationPipeBody");
|
|
154
161
|
function extractParamFromValidatorMessage(message) {
|
|
155
162
|
const idx = message.indexOf(" ");
|
|
156
163
|
if (idx <= 0) return void 0;
|
|
157
164
|
const candidate = message.slice(0, idx);
|
|
158
165
|
return /^[A-Za-z_][\w.]*$/.test(candidate) ? candidate : void 0;
|
|
159
166
|
}
|
|
167
|
+
__name(extractParamFromValidatorMessage, "extractParamFromValidatorMessage");
|
|
160
168
|
function normalizeValidationPipeBody(body) {
|
|
161
169
|
const fields = {};
|
|
162
170
|
for (const msg of body.message) {
|
|
@@ -171,11 +179,14 @@ function normalizeValidationPipeBody(body) {
|
|
|
171
179
|
code: "VALIDATION_FAILED",
|
|
172
180
|
message: body.message.join("; "),
|
|
173
181
|
safeToRetry: false,
|
|
174
|
-
metadata: {
|
|
182
|
+
metadata: {
|
|
183
|
+
fieldErrors: fields
|
|
184
|
+
}
|
|
175
185
|
};
|
|
176
186
|
if (param) payload.param = param;
|
|
177
187
|
return payload;
|
|
178
188
|
}
|
|
189
|
+
__name(normalizeValidationPipeBody, "normalizeValidationPipeBody");
|
|
179
190
|
function normalizeHttpExceptionBody(desc) {
|
|
180
191
|
const { statusCode, responseBody, exceptionName, fallbackMessage } = desc;
|
|
181
192
|
const defaultType = (0, import_core.defaultErrorTypeForStatus)(statusCode);
|
|
@@ -216,6 +227,7 @@ function normalizeHttpExceptionBody(desc) {
|
|
|
216
227
|
safeToRetry: false
|
|
217
228
|
};
|
|
218
229
|
}
|
|
230
|
+
__name(normalizeHttpExceptionBody, "normalizeHttpExceptionBody");
|
|
219
231
|
function payloadFromPafiSdkError(err) {
|
|
220
232
|
const body = buildSdkErrorBody(err);
|
|
221
233
|
const payload = {
|
|
@@ -229,10 +241,12 @@ function payloadFromPafiSdkError(err) {
|
|
|
229
241
|
if (body.details !== void 0) payload.details = body.details;
|
|
230
242
|
return payload;
|
|
231
243
|
}
|
|
244
|
+
__name(payloadFromPafiSdkError, "payloadFromPafiSdkError");
|
|
232
245
|
function sanitizeDbErrorMessage(message) {
|
|
233
246
|
if (/^[A-Z_]+: /.test(message) && message.length < 256) return message;
|
|
234
247
|
return "Internal database error";
|
|
235
248
|
}
|
|
249
|
+
__name(sanitizeDbErrorMessage, "sanitizeDbErrorMessage");
|
|
236
250
|
function buildErrorEnvelope(input) {
|
|
237
251
|
const now = (input.ctx.now ?? (() => /* @__PURE__ */ new Date()))();
|
|
238
252
|
return {
|
|
@@ -246,9 +260,11 @@ function buildErrorEnvelope(input) {
|
|
|
246
260
|
}
|
|
247
261
|
};
|
|
248
262
|
}
|
|
263
|
+
__name(buildErrorEnvelope, "buildErrorEnvelope");
|
|
249
264
|
function payloadFromHttpException(desc) {
|
|
250
265
|
return normalizeHttpExceptionBody(desc);
|
|
251
266
|
}
|
|
267
|
+
__name(payloadFromHttpException, "payloadFromHttpException");
|
|
252
268
|
function payloadFromGenericError(err) {
|
|
253
269
|
const name = err.name || "INTERNAL_SERVER_ERROR";
|
|
254
270
|
const isDbError = name === "QueryFailedError" || name === "EntityNotFoundError";
|
|
@@ -259,9 +275,13 @@ function payloadFromGenericError(err) {
|
|
|
259
275
|
safeToRetry: false
|
|
260
276
|
};
|
|
261
277
|
}
|
|
278
|
+
__name(payloadFromGenericError, "payloadFromGenericError");
|
|
262
279
|
|
|
263
280
|
// src/policy/defaultPolicy.ts
|
|
264
281
|
var DefaultPolicyEngine = class {
|
|
282
|
+
static {
|
|
283
|
+
__name(this, "DefaultPolicyEngine");
|
|
284
|
+
}
|
|
265
285
|
ledger;
|
|
266
286
|
provider;
|
|
267
287
|
mintingOracleAddress;
|
|
@@ -277,20 +297,18 @@ var DefaultPolicyEngine = class {
|
|
|
277
297
|
if (opts.resolveIssuer) this.resolveIssuer = opts.resolveIssuer;
|
|
278
298
|
if (!opts.mintingOracleAddress || !opts.provider || !opts.verifyMintCap || !opts.resolveIssuer) {
|
|
279
299
|
if (process.env.NODE_ENV === "production") {
|
|
280
|
-
throw new Error(
|
|
281
|
-
"[PAFI] DefaultPolicyEngine: on-chain MintingOracle cap check is required in production. Configure mintingOracleAddress, provider, verifyMintCap, and resolveIssuer."
|
|
282
|
-
);
|
|
300
|
+
throw new Error("[PAFI] DefaultPolicyEngine: on-chain MintingOracle cap check is required in production. Configure mintingOracleAddress, provider, verifyMintCap, and resolveIssuer.");
|
|
283
301
|
}
|
|
284
302
|
}
|
|
285
303
|
}
|
|
286
304
|
async evaluate(request) {
|
|
287
305
|
if (request.amount <= 0n) {
|
|
288
|
-
return {
|
|
306
|
+
return {
|
|
307
|
+
approved: false,
|
|
308
|
+
reason: "Amount must be positive"
|
|
309
|
+
};
|
|
289
310
|
}
|
|
290
|
-
const available = await this.ledger.getBalance(
|
|
291
|
-
request.userAddress,
|
|
292
|
-
request.pointTokenAddress
|
|
293
|
-
);
|
|
311
|
+
const available = await this.ledger.getBalance(request.userAddress, request.pointTokenAddress);
|
|
294
312
|
if (available < request.amount) {
|
|
295
313
|
return {
|
|
296
314
|
approved: false,
|
|
@@ -300,12 +318,7 @@ var DefaultPolicyEngine = class {
|
|
|
300
318
|
if (this.mintingOracleAddress && this.provider && this.verifyMintCap && this.resolveIssuer) {
|
|
301
319
|
try {
|
|
302
320
|
const issuer = await this.resolveIssuer(request.pointTokenAddress);
|
|
303
|
-
await this.verifyMintCap(
|
|
304
|
-
this.provider,
|
|
305
|
-
this.mintingOracleAddress,
|
|
306
|
-
issuer,
|
|
307
|
-
request.amount
|
|
308
|
-
);
|
|
321
|
+
await this.verifyMintCap(this.provider, this.mintingOracleAddress, issuer, request.amount);
|
|
309
322
|
} catch (err) {
|
|
310
323
|
const msg = err instanceof Error ? err.message : String(err);
|
|
311
324
|
return {
|
|
@@ -314,7 +327,9 @@ var DefaultPolicyEngine = class {
|
|
|
314
327
|
};
|
|
315
328
|
}
|
|
316
329
|
}
|
|
317
|
-
return {
|
|
330
|
+
return {
|
|
331
|
+
approved: true
|
|
332
|
+
};
|
|
318
333
|
}
|
|
319
334
|
};
|
|
320
335
|
|
|
@@ -323,17 +338,16 @@ var import_node_crypto = require("crypto");
|
|
|
323
338
|
var import_viem = require("viem");
|
|
324
339
|
var DEFAULT_NONCE_TTL_MS = 5 * 60 * 1e3;
|
|
325
340
|
var MemorySessionStore = class {
|
|
341
|
+
static {
|
|
342
|
+
__name(this, "MemorySessionStore");
|
|
343
|
+
}
|
|
326
344
|
nonces = /* @__PURE__ */ new Map();
|
|
327
|
-
// nonce → expiresAt
|
|
328
345
|
sessions = /* @__PURE__ */ new Map();
|
|
329
|
-
// tokenId → session
|
|
330
346
|
nonceTtlMs;
|
|
331
347
|
now;
|
|
332
348
|
constructor(opts = {}) {
|
|
333
349
|
if (process.env.NODE_ENV === "production" && !opts.dangerouslyAllowMemoryStoreInProduction) {
|
|
334
|
-
throw new Error(
|
|
335
|
-
"[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`."
|
|
336
|
-
);
|
|
350
|
+
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`.");
|
|
337
351
|
}
|
|
338
352
|
this.nonceTtlMs = opts.nonceTtlMs ?? DEFAULT_NONCE_TTL_MS;
|
|
339
353
|
this.now = opts.now ?? (() => Date.now());
|
|
@@ -373,7 +387,9 @@ var MemorySessionStore = class {
|
|
|
373
387
|
this.sessions.delete(tokenId);
|
|
374
388
|
return null;
|
|
375
389
|
}
|
|
376
|
-
return {
|
|
390
|
+
return {
|
|
391
|
+
...session
|
|
392
|
+
};
|
|
377
393
|
}
|
|
378
394
|
async revokeSession(tokenId) {
|
|
379
395
|
this.sessions.delete(tokenId);
|
|
@@ -405,18 +421,21 @@ var MemorySessionStore = class {
|
|
|
405
421
|
|
|
406
422
|
// src/auth/nonceManager.ts
|
|
407
423
|
var NonceManager = class {
|
|
424
|
+
static {
|
|
425
|
+
__name(this, "NonceManager");
|
|
426
|
+
}
|
|
427
|
+
store;
|
|
408
428
|
constructor(store) {
|
|
409
429
|
this.store = store;
|
|
410
430
|
}
|
|
411
|
-
store;
|
|
412
431
|
/** Generate a fresh login nonce. The store is responsible for TTL. */
|
|
413
432
|
async generate() {
|
|
414
433
|
return this.store.createNonce();
|
|
415
434
|
}
|
|
416
435
|
/**
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
436
|
+
* Atomically validate + consume a nonce. Returns `true` iff the nonce
|
|
437
|
+
* was known and unexpired (and is now removed from the store).
|
|
438
|
+
*/
|
|
420
439
|
async consume(nonce) {
|
|
421
440
|
return this.store.consumeNonce(nonce);
|
|
422
441
|
}
|
|
@@ -448,7 +467,11 @@ function statusForCode(code) {
|
|
|
448
467
|
return "not_found";
|
|
449
468
|
}
|
|
450
469
|
}
|
|
470
|
+
__name(statusForCode, "statusForCode");
|
|
451
471
|
var AuthError = class extends import_core.PafiSdkError {
|
|
472
|
+
static {
|
|
473
|
+
__name(this, "AuthError");
|
|
474
|
+
}
|
|
452
475
|
code;
|
|
453
476
|
httpStatus;
|
|
454
477
|
constructor(code, message) {
|
|
@@ -461,37 +484,28 @@ var AuthError = class extends import_core.PafiSdkError {
|
|
|
461
484
|
// src/auth/loginVerifier.ts
|
|
462
485
|
function assertJwtSecretStrength(secret) {
|
|
463
486
|
if (!secret) {
|
|
464
|
-
throw new Error(
|
|
465
|
-
"AuthService: jwtSecret is required. Generate via `node -e \"console.log(require('crypto').randomBytes(32).toString('hex'))\"`"
|
|
466
|
-
);
|
|
487
|
+
throw new Error("AuthService: jwtSecret is required. Generate via `node -e \"console.log(require('crypto').randomBytes(32).toString('hex'))\"`");
|
|
467
488
|
}
|
|
468
489
|
if (secret.length < 32) {
|
|
469
|
-
throw new Error(
|
|
470
|
-
`AuthService: jwtSecret too short (${secret.length} chars; need \u2265 32). HS256 brute-force becomes feasible below this threshold.`
|
|
471
|
-
);
|
|
490
|
+
throw new Error(`AuthService: jwtSecret too short (${secret.length} chars; need \u2265 32). HS256 brute-force becomes feasible below this threshold.`);
|
|
472
491
|
}
|
|
473
492
|
const uniqueChars = new Set(secret).size;
|
|
474
493
|
if (uniqueChars < 16) {
|
|
475
|
-
throw new Error(
|
|
476
|
-
`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')\`.`
|
|
477
|
-
);
|
|
494
|
+
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')\`.`);
|
|
478
495
|
}
|
|
479
496
|
const entropy = shannonEntropyBitsPerChar(secret);
|
|
480
497
|
if (entropy < 3.5) {
|
|
481
|
-
throw new Error(
|
|
482
|
-
`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).`
|
|
483
|
-
);
|
|
498
|
+
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).`);
|
|
484
499
|
}
|
|
485
500
|
for (let period = 1; period <= secret.length / 4; period++) {
|
|
486
501
|
if (secret.length % period !== 0) continue;
|
|
487
502
|
const head = secret.slice(0, period);
|
|
488
503
|
if (secret === head.repeat(secret.length / period)) {
|
|
489
|
-
throw new Error(
|
|
490
|
-
`AuthService: jwtSecret is a repeating pattern of period ${period}. Use \`crypto.randomBytes(32).toString('hex')\`.`
|
|
491
|
-
);
|
|
504
|
+
throw new Error(`AuthService: jwtSecret is a repeating pattern of period ${period}. Use \`crypto.randomBytes(32).toString('hex')\`.`);
|
|
492
505
|
}
|
|
493
506
|
}
|
|
494
507
|
}
|
|
508
|
+
__name(assertJwtSecretStrength, "assertJwtSecretStrength");
|
|
495
509
|
function shannonEntropyBitsPerChar(s) {
|
|
496
510
|
const counts = /* @__PURE__ */ new Map();
|
|
497
511
|
for (const c of s) counts.set(c, (counts.get(c) ?? 0) + 1);
|
|
@@ -503,6 +517,7 @@ function shannonEntropyBitsPerChar(s) {
|
|
|
503
517
|
}
|
|
504
518
|
return h;
|
|
505
519
|
}
|
|
520
|
+
__name(shannonEntropyBitsPerChar, "shannonEntropyBitsPerChar");
|
|
506
521
|
function decodeExpiredJwtJti(token) {
|
|
507
522
|
try {
|
|
508
523
|
const parts = token.split(".");
|
|
@@ -512,13 +527,19 @@ function decodeExpiredJwtJti(token) {
|
|
|
512
527
|
const padded = payloadB64.replace(/-/g, "+").replace(/_/g, "/") + "===".slice((payloadB64.length + 3) % 4);
|
|
513
528
|
const json = Buffer.from(padded, "base64").toString("utf-8");
|
|
514
529
|
const claims = JSON.parse(json);
|
|
515
|
-
return typeof claims.jti === "string" ? {
|
|
530
|
+
return typeof claims.jti === "string" ? {
|
|
531
|
+
jti: claims.jti
|
|
532
|
+
} : {};
|
|
516
533
|
} catch {
|
|
517
534
|
return {};
|
|
518
535
|
}
|
|
519
536
|
}
|
|
537
|
+
__name(decodeExpiredJwtJti, "decodeExpiredJwtJti");
|
|
520
538
|
var DEFAULT_EXPIRES_IN = "24h";
|
|
521
539
|
var AuthService = class {
|
|
540
|
+
static {
|
|
541
|
+
__name(this, "AuthService");
|
|
542
|
+
}
|
|
522
543
|
sessionStore;
|
|
523
544
|
jwtSecret;
|
|
524
545
|
jwtExpiresIn;
|
|
@@ -548,9 +569,9 @@ var AuthService = class {
|
|
|
548
569
|
return this.nonceManager.generate();
|
|
549
570
|
}
|
|
550
571
|
/**
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
572
|
+
* Verify a signed login message and issue a JWT on success.
|
|
573
|
+
* Throws an `AuthError` on any validation failure.
|
|
574
|
+
*/
|
|
554
575
|
async login(message, signature) {
|
|
555
576
|
let parsed;
|
|
556
577
|
try {
|
|
@@ -560,46 +581,28 @@ var AuthService = class {
|
|
|
560
581
|
throw new AuthError("INVALID_MESSAGE", `Could not parse login message: ${msg}`);
|
|
561
582
|
}
|
|
562
583
|
if (parsed.expirationTime == null) {
|
|
563
|
-
throw new AuthError(
|
|
564
|
-
"INVALID_MESSAGE",
|
|
565
|
-
"login message must include expirationTime"
|
|
566
|
-
);
|
|
584
|
+
throw new AuthError("INVALID_MESSAGE", "login message must include expirationTime");
|
|
567
585
|
}
|
|
568
586
|
if (parsed.domain !== this.domain) {
|
|
569
|
-
throw new AuthError(
|
|
570
|
-
"DOMAIN_MISMATCH",
|
|
571
|
-
`Expected domain "${this.domain}", got "${parsed.domain}"`
|
|
572
|
-
);
|
|
587
|
+
throw new AuthError("DOMAIN_MISMATCH", `Expected domain "${this.domain}", got "${parsed.domain}"`);
|
|
573
588
|
}
|
|
574
589
|
if (parsed.chainId !== this.chainId) {
|
|
575
|
-
throw new AuthError(
|
|
576
|
-
"CHAIN_MISMATCH",
|
|
577
|
-
`Expected chainId ${this.chainId}, got ${parsed.chainId}`
|
|
578
|
-
);
|
|
590
|
+
throw new AuthError("CHAIN_MISMATCH", `Expected chainId ${this.chainId}, got ${parsed.chainId}`);
|
|
579
591
|
}
|
|
580
592
|
const now = this.now();
|
|
581
593
|
if (parsed.notBefore && parsed.notBefore.getTime() > now.getTime()) {
|
|
582
|
-
throw new AuthError(
|
|
583
|
-
"MESSAGE_NOT_YET_VALID",
|
|
584
|
-
"Login message is not yet valid"
|
|
585
|
-
);
|
|
594
|
+
throw new AuthError("MESSAGE_NOT_YET_VALID", "Login message is not yet valid");
|
|
586
595
|
}
|
|
587
596
|
if (parsed.expirationTime && parsed.expirationTime.getTime() <= now.getTime()) {
|
|
588
597
|
throw new AuthError("MESSAGE_EXPIRED", "Login message has expired");
|
|
589
598
|
}
|
|
590
599
|
const verifyResult = await (0, import_core4.verifyLoginMessage)(message, signature);
|
|
591
600
|
if (!verifyResult.valid) {
|
|
592
|
-
throw new AuthError(
|
|
593
|
-
"SIGNATURE_INVALID",
|
|
594
|
-
"Signature does not match the address in the login message"
|
|
595
|
-
);
|
|
601
|
+
throw new AuthError("SIGNATURE_INVALID", "Signature does not match the address in the login message");
|
|
596
602
|
}
|
|
597
603
|
const nonceOk = await this.nonceManager.consume(parsed.nonce);
|
|
598
604
|
if (!nonceOk) {
|
|
599
|
-
throw new AuthError(
|
|
600
|
-
"NONCE_INVALID",
|
|
601
|
-
"Nonce is unknown, expired, or already used"
|
|
602
|
-
);
|
|
605
|
+
throw new AuthError("NONCE_INVALID", "Nonce is unknown, expired, or already used");
|
|
603
606
|
}
|
|
604
607
|
const userAddress = (0, import_viem2.getAddress)(verifyResult.address);
|
|
605
608
|
const tokenId = (0, import_node_crypto2.randomBytes)(16).toString("hex");
|
|
@@ -616,11 +619,18 @@ var AuthService = class {
|
|
|
616
619
|
let signer = new import_jose.SignJWT({
|
|
617
620
|
userAddress,
|
|
618
621
|
chainId: this.chainId
|
|
619
|
-
}).setProtectedHeader({
|
|
622
|
+
}).setProtectedHeader({
|
|
623
|
+
alg: "HS256"
|
|
624
|
+
}).setJti(tokenId).setIssuedAt(Math.floor(issuedAt.getTime() / 1e3)).setExpirationTime(Math.floor(expiresAt.getTime() / 1e3));
|
|
620
625
|
if (this.issuer) signer = signer.setIssuer(this.issuer);
|
|
621
626
|
if (this.audience) signer = signer.setAudience(this.audience);
|
|
622
627
|
const token = await signer.sign(this.jwtSecret);
|
|
623
|
-
return {
|
|
628
|
+
return {
|
|
629
|
+
token,
|
|
630
|
+
userAddress,
|
|
631
|
+
tokenId,
|
|
632
|
+
expiresAt
|
|
633
|
+
};
|
|
624
634
|
}
|
|
625
635
|
/** Revoke the session backing the given JWT (logout). */
|
|
626
636
|
async logout(token) {
|
|
@@ -628,9 +638,12 @@ var AuthService = class {
|
|
|
628
638
|
try {
|
|
629
639
|
const result = await (0, import_jose.jwtVerify)(token, this.jwtSecret, {
|
|
630
640
|
clockTolerance: 60,
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
641
|
+
...this.issuer ? {
|
|
642
|
+
issuer: this.issuer
|
|
643
|
+
} : {},
|
|
644
|
+
...this.audience ? {
|
|
645
|
+
audience: this.audience
|
|
646
|
+
} : {}
|
|
634
647
|
});
|
|
635
648
|
payload = result.payload;
|
|
636
649
|
} catch (err) {
|
|
@@ -648,10 +661,7 @@ var AuthService = class {
|
|
|
648
661
|
if (err instanceof import_jose.errors.JWSSignatureVerificationFailed || err instanceof import_jose.errors.JWSInvalid || err instanceof import_jose.errors.JWTInvalid) {
|
|
649
662
|
throw new AuthError("TOKEN_INVALID", "JWT verification failed");
|
|
650
663
|
}
|
|
651
|
-
throw new AuthError(
|
|
652
|
-
"TOKEN_INVALID",
|
|
653
|
-
`JWT verification failed: ${err instanceof Error ? err.message : String(err)}`
|
|
654
|
-
);
|
|
664
|
+
throw new AuthError("TOKEN_INVALID", `JWT verification failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
655
665
|
}
|
|
656
666
|
if (payload.jti) {
|
|
657
667
|
try {
|
|
@@ -667,16 +677,20 @@ var AuthService = class {
|
|
|
667
677
|
console.error("[PAFI] AuthService logout: session store error", err);
|
|
668
678
|
}
|
|
669
679
|
/**
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
680
|
+
* Verify a JWT and return the authenticated user context. Throws an
|
|
681
|
+
* `AuthError` if the token is missing, malformed, expired, revoked, or
|
|
682
|
+
* signed by a different key.
|
|
683
|
+
*/
|
|
674
684
|
async verifyToken(token) {
|
|
675
685
|
let payload;
|
|
676
686
|
try {
|
|
677
687
|
const result = await (0, import_jose.jwtVerify)(token, this.jwtSecret, {
|
|
678
|
-
...this.issuer ? {
|
|
679
|
-
|
|
688
|
+
...this.issuer ? {
|
|
689
|
+
issuer: this.issuer
|
|
690
|
+
} : {},
|
|
691
|
+
...this.audience ? {
|
|
692
|
+
audience: this.audience
|
|
693
|
+
} : {}
|
|
680
694
|
});
|
|
681
695
|
payload = result.payload;
|
|
682
696
|
} catch (err) {
|
|
@@ -694,10 +708,7 @@ var AuthService = class {
|
|
|
694
708
|
}
|
|
695
709
|
const session = await this.sessionStore.getSession(tokenId);
|
|
696
710
|
if (!session) {
|
|
697
|
-
throw new AuthError(
|
|
698
|
-
"SESSION_REVOKED",
|
|
699
|
-
"Session is no longer active (revoked or expired)"
|
|
700
|
-
);
|
|
711
|
+
throw new AuthError("SESSION_REVOKED", "Session is no longer active (revoked or expired)");
|
|
701
712
|
}
|
|
702
713
|
const userAddress = payload.userAddress;
|
|
703
714
|
const chainId = payload.chainId;
|
|
@@ -714,51 +725,50 @@ var AuthService = class {
|
|
|
714
725
|
function parseExpiry(from, expiresIn) {
|
|
715
726
|
const match = expiresIn.match(/^(\d+)\s*(s|m|h|d)$/i);
|
|
716
727
|
if (!match) {
|
|
717
|
-
throw new Error(
|
|
718
|
-
`AuthService: unsupported jwtExpiresIn "${expiresIn}" \u2014 use e.g. "24h"`
|
|
719
|
-
);
|
|
728
|
+
throw new Error(`AuthService: unsupported jwtExpiresIn "${expiresIn}" \u2014 use e.g. "24h"`);
|
|
720
729
|
}
|
|
721
730
|
const n = Number(match[1]);
|
|
722
731
|
const unit = match[2].toLowerCase();
|
|
723
732
|
const multiplier = unit === "s" ? 1e3 : unit === "m" ? 6e4 : unit === "h" ? 36e5 : 864e5;
|
|
724
733
|
return new Date(from.getTime() + n * multiplier);
|
|
725
734
|
}
|
|
735
|
+
__name(parseExpiry, "parseExpiry");
|
|
726
736
|
|
|
727
737
|
// src/auth/jwtMiddleware.ts
|
|
728
738
|
async function authenticateRequest(authHeader, authService) {
|
|
729
739
|
if (!authHeader) {
|
|
730
|
-
throw new AuthError(
|
|
731
|
-
"MISSING_TOKEN",
|
|
732
|
-
"Authorization header is required"
|
|
733
|
-
);
|
|
740
|
+
throw new AuthError("MISSING_TOKEN", "Authorization header is required");
|
|
734
741
|
}
|
|
735
742
|
const match = authHeader.match(/^Bearer\s+(\S+)\s*$/i);
|
|
736
743
|
if (!match) {
|
|
737
|
-
throw new AuthError(
|
|
738
|
-
"MALFORMED_TOKEN",
|
|
739
|
-
"Authorization header must be in the form 'Bearer <token>'"
|
|
740
|
-
);
|
|
744
|
+
throw new AuthError("MALFORMED_TOKEN", "Authorization header must be in the form 'Bearer <token>'");
|
|
741
745
|
}
|
|
742
746
|
const token = match[1];
|
|
743
747
|
return authService.verifyToken(token);
|
|
744
748
|
}
|
|
749
|
+
__name(authenticateRequest, "authenticateRequest");
|
|
745
750
|
|
|
746
751
|
// src/auth/rateLimiter.ts
|
|
747
752
|
var DEFAULT_LIMITS = {
|
|
748
|
-
auth_nonce: {
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
753
|
+
auth_nonce: {
|
|
754
|
+
max: 30,
|
|
755
|
+
windowMs: 6e4
|
|
756
|
+
},
|
|
757
|
+
auth_login: {
|
|
758
|
+
max: 5,
|
|
759
|
+
windowMs: 6e4
|
|
760
|
+
}
|
|
752
761
|
};
|
|
753
762
|
var MemoryRateLimiter = class {
|
|
763
|
+
static {
|
|
764
|
+
__name(this, "MemoryRateLimiter");
|
|
765
|
+
}
|
|
754
766
|
buckets = /* @__PURE__ */ new Map();
|
|
755
767
|
limits;
|
|
756
768
|
now;
|
|
757
769
|
constructor(config = {}) {
|
|
758
770
|
if (process.env.NODE_ENV === "production" && !process.env.PAFI_ALLOW_MEMORY_RATE_LIMITER_IN_PROD) {
|
|
759
|
-
console.warn(
|
|
760
|
-
"[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."
|
|
761
|
-
);
|
|
771
|
+
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.");
|
|
762
772
|
}
|
|
763
773
|
this.limits = {
|
|
764
774
|
...DEFAULT_LIMITS,
|
|
@@ -768,47 +778,62 @@ var MemoryRateLimiter = class {
|
|
|
768
778
|
}
|
|
769
779
|
async consume(key, action) {
|
|
770
780
|
const limit = this.limits[action];
|
|
771
|
-
if (!limit) return {
|
|
781
|
+
if (!limit) return {
|
|
782
|
+
allowed: true
|
|
783
|
+
};
|
|
772
784
|
const bucketKey = `${action}:${key}`;
|
|
773
785
|
const now = this.now();
|
|
774
786
|
const bucket = this.buckets.get(bucketKey);
|
|
775
787
|
if (!bucket || now - bucket.windowStartedAt >= limit.windowMs) {
|
|
776
|
-
this.buckets.set(bucketKey, {
|
|
777
|
-
|
|
788
|
+
this.buckets.set(bucketKey, {
|
|
789
|
+
count: 1,
|
|
790
|
+
windowStartedAt: now
|
|
791
|
+
});
|
|
792
|
+
return {
|
|
793
|
+
allowed: true
|
|
794
|
+
};
|
|
778
795
|
}
|
|
779
796
|
if (bucket.count < limit.max) {
|
|
780
797
|
bucket.count += 1;
|
|
781
|
-
return {
|
|
798
|
+
return {
|
|
799
|
+
allowed: true
|
|
800
|
+
};
|
|
782
801
|
}
|
|
783
|
-
const retryAfterMs = Math.max(
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
802
|
+
const retryAfterMs = Math.max(0, bucket.windowStartedAt + limit.windowMs - now);
|
|
803
|
+
return {
|
|
804
|
+
allowed: false,
|
|
805
|
+
retryAfterMs
|
|
806
|
+
};
|
|
788
807
|
}
|
|
789
808
|
/**
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
809
|
+
* Test helper — clear all buckets. Not part of `IRateLimiter`; only
|
|
810
|
+
* exposed on the in-memory impl for unit tests.
|
|
811
|
+
*/
|
|
793
812
|
reset() {
|
|
794
813
|
this.buckets.clear();
|
|
795
814
|
}
|
|
796
815
|
};
|
|
797
816
|
var NoopRateLimiter = class {
|
|
817
|
+
static {
|
|
818
|
+
__name(this, "NoopRateLimiter");
|
|
819
|
+
}
|
|
798
820
|
warned = false;
|
|
799
821
|
async consume() {
|
|
800
822
|
if (!this.warned && process.env.NODE_ENV === "production") {
|
|
801
|
-
console.warn(
|
|
802
|
-
"[PAFI] NoopRateLimiter active \u2014 `/auth/nonce` and `/auth/login` are NOT throttled. Wire a `MemoryRateLimiter` (dev) or Redis-backed impl (prod) via `IssuerApiHandlersConfig.rateLimiter`."
|
|
803
|
-
);
|
|
823
|
+
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`.");
|
|
804
824
|
this.warned = true;
|
|
805
825
|
}
|
|
806
|
-
return {
|
|
826
|
+
return {
|
|
827
|
+
allowed: true
|
|
828
|
+
};
|
|
807
829
|
}
|
|
808
830
|
};
|
|
809
831
|
|
|
810
832
|
// src/relay/types.ts
|
|
811
833
|
var RelayError = class extends import_core.PafiSdkError {
|
|
834
|
+
static {
|
|
835
|
+
__name(this, "RelayError");
|
|
836
|
+
}
|
|
812
837
|
httpStatus = "unprocessable";
|
|
813
838
|
code;
|
|
814
839
|
constructor(code, message, cause) {
|
|
@@ -824,6 +849,9 @@ var RelayError = class extends import_core.PafiSdkError {
|
|
|
824
849
|
var import_viem3 = require("viem");
|
|
825
850
|
var import_core5 = require("@pafi-dev/core");
|
|
826
851
|
var RelayService = class {
|
|
852
|
+
static {
|
|
853
|
+
__name(this, "RelayService");
|
|
854
|
+
}
|
|
827
855
|
provider;
|
|
828
856
|
chainId;
|
|
829
857
|
constructor(config = {}) {
|
|
@@ -831,25 +859,28 @@ var RelayService = class {
|
|
|
831
859
|
this.chainId = config.chainId;
|
|
832
860
|
}
|
|
833
861
|
/**
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
862
|
+
* Resolve the fee recipient + amount applied to the next UserOp:
|
|
863
|
+
*
|
|
864
|
+
* - If caller passed an explicit `feeRecipient`, use it (testing
|
|
865
|
+
* only — sponsor-relayer's L1 will reject any non-canonical
|
|
866
|
+
* recipient with `INSUFFICIENT_FEE`). Otherwise, default to
|
|
867
|
+
* `getContractAddresses(chainId).pafiFeeRecipient` when the
|
|
868
|
+
* service has a `chainId` configured.
|
|
869
|
+
* - If caller passed `feeAmount`, use it. Otherwise, when the
|
|
870
|
+
* service has both `provider` + `chainId`, auto-quote via
|
|
871
|
+
* `quoteOperatorFeePt`.
|
|
872
|
+
* - When the service is unconfigured AND caller passed nothing,
|
|
873
|
+
* return `{ feeAmount: 0n, feeRecipient: undefined }` — legacy
|
|
874
|
+
* "no fee" behavior, caller must opt in for the gas-reimbursement
|
|
875
|
+
* transfer to be added to the batch.
|
|
876
|
+
*/
|
|
849
877
|
async resolveFee(params) {
|
|
850
878
|
const feeRecipient = params.feeRecipient ?? (this.chainId !== void 0 ? (0, import_core5.getContractAddresses)(this.chainId).pafiFeeRecipient : void 0);
|
|
851
879
|
if (params.feeAmount !== void 0) {
|
|
852
|
-
return {
|
|
880
|
+
return {
|
|
881
|
+
feeAmount: params.feeAmount,
|
|
882
|
+
feeRecipient
|
|
883
|
+
};
|
|
853
884
|
}
|
|
854
885
|
if (this.provider && this.chainId !== void 0) {
|
|
855
886
|
const feeAmount = await (0, import_core5.quoteOperatorFeePt)({
|
|
@@ -857,44 +888,39 @@ var RelayService = class {
|
|
|
857
888
|
chainId: this.chainId,
|
|
858
889
|
pointTokenAddress: params.pointTokenAddress,
|
|
859
890
|
allowStaleFallback: true,
|
|
860
|
-
onFallback: (info) => {
|
|
861
|
-
console.warn(
|
|
862
|
-
|
|
863
|
-
);
|
|
864
|
-
}
|
|
891
|
+
onFallback: /* @__PURE__ */ __name((info) => {
|
|
892
|
+
console.warn(`[RelayService] operatorFeeQuoter fallback (${info.source}): ${info.reason} \u2192 using ${info.fallbackValue}`);
|
|
893
|
+
}, "onFallback")
|
|
865
894
|
});
|
|
866
|
-
return {
|
|
895
|
+
return {
|
|
896
|
+
feeAmount,
|
|
897
|
+
feeRecipient
|
|
898
|
+
};
|
|
867
899
|
}
|
|
868
|
-
return {
|
|
900
|
+
return {
|
|
901
|
+
feeAmount: 0n,
|
|
902
|
+
feeRecipient
|
|
903
|
+
};
|
|
869
904
|
}
|
|
870
905
|
/**
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
906
|
+
* Build an unsigned UserOp for Scenario 1 (Mint) — sig-gated
|
|
907
|
+
* `PointToken.mint(to, amount, deadline, minterSig)`.
|
|
908
|
+
*/
|
|
874
909
|
async prepareMint(params) {
|
|
875
910
|
if (!params.batchExecutorAddress) {
|
|
876
|
-
throw new RelayError(
|
|
877
|
-
"ENCODE_FAILED",
|
|
878
|
-
"prepareMint: batchExecutorAddress required"
|
|
879
|
-
);
|
|
911
|
+
throw new RelayError("ENCODE_FAILED", "prepareMint: batchExecutorAddress required");
|
|
880
912
|
}
|
|
881
913
|
if (!params.userAddress) {
|
|
882
914
|
throw new RelayError("ENCODE_FAILED", "prepareMint: userAddress required");
|
|
883
915
|
}
|
|
884
916
|
if (!params.pointTokenAddress) {
|
|
885
|
-
throw new RelayError(
|
|
886
|
-
"ENCODE_FAILED",
|
|
887
|
-
"prepareMint: pointTokenAddress required"
|
|
888
|
-
);
|
|
917
|
+
throw new RelayError("ENCODE_FAILED", "prepareMint: pointTokenAddress required");
|
|
889
918
|
}
|
|
890
919
|
if (params.amount <= 0n) {
|
|
891
920
|
throw new RelayError("ENCODE_FAILED", "prepareMint: amount must be positive");
|
|
892
921
|
}
|
|
893
922
|
if (!params.issuerSignerWallet) {
|
|
894
|
-
throw new RelayError(
|
|
895
|
-
"ENCODE_FAILED",
|
|
896
|
-
"prepareMint: issuerSignerWallet required (for MintRequest EIP-712 signature)"
|
|
897
|
-
);
|
|
923
|
+
throw new RelayError("ENCODE_FAILED", "prepareMint: issuerSignerWallet required (for MintRequest EIP-712 signature)");
|
|
898
924
|
}
|
|
899
925
|
if (params.deadline <= 0n) {
|
|
900
926
|
throw new RelayError("ENCODE_FAILED", "prepareMint: deadline must be positive");
|
|
@@ -905,35 +931,24 @@ var RelayService = class {
|
|
|
905
931
|
}
|
|
906
932
|
const MAX_DEADLINE_WINDOW = 3600n;
|
|
907
933
|
if (params.deadline > nowSecs + MAX_DEADLINE_WINDOW) {
|
|
908
|
-
throw new RelayError(
|
|
909
|
-
"ENCODE_FAILED",
|
|
910
|
-
"prepareMint: deadline exceeds maximum allowed window (1 hour)"
|
|
911
|
-
);
|
|
934
|
+
throw new RelayError("ENCODE_FAILED", "prepareMint: deadline exceeds maximum allowed window (1 hour)");
|
|
912
935
|
}
|
|
913
936
|
const MINT_SOURCE = import_core5.Source.EQUITY;
|
|
914
937
|
const useWrapper = params.mintFeeWrapperAddress !== void 0;
|
|
915
938
|
const receiverForSig = useWrapper ? params.mintFeeWrapperAddress : params.userAddress;
|
|
916
939
|
let minterSig;
|
|
917
940
|
try {
|
|
918
|
-
const sig = await (0, import_core5.signMintRequest)(
|
|
919
|
-
params.
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
nonce: params.mintRequestNonce,
|
|
927
|
-
deadline: params.deadline
|
|
928
|
-
}
|
|
929
|
-
);
|
|
941
|
+
const sig = await (0, import_core5.signMintRequest)(params.issuerSignerWallet, params.domain, {
|
|
942
|
+
user: params.userAddress,
|
|
943
|
+
receiver: receiverForSig,
|
|
944
|
+
amount: params.amount,
|
|
945
|
+
source: MINT_SOURCE,
|
|
946
|
+
nonce: params.mintRequestNonce,
|
|
947
|
+
deadline: params.deadline
|
|
948
|
+
});
|
|
930
949
|
minterSig = sig.serialized;
|
|
931
950
|
} catch (err) {
|
|
932
|
-
throw new RelayError(
|
|
933
|
-
"ENCODE_FAILED",
|
|
934
|
-
`prepareMint: failed to sign MintForRequest: ${errorMessage(err)}`,
|
|
935
|
-
err
|
|
936
|
-
);
|
|
951
|
+
throw new RelayError("ENCODE_FAILED", `prepareMint: failed to sign MintForRequest: ${errorMessage(err)}`, err);
|
|
937
952
|
}
|
|
938
953
|
let mintCallData;
|
|
939
954
|
let mintTarget;
|
|
@@ -966,11 +981,7 @@ var RelayService = class {
|
|
|
966
981
|
mintTarget = params.pointTokenAddress;
|
|
967
982
|
}
|
|
968
983
|
} catch (err) {
|
|
969
|
-
throw new RelayError(
|
|
970
|
-
"ENCODE_FAILED",
|
|
971
|
-
`prepareMint: failed to encode mint call: ${errorMessage(err)}`,
|
|
972
|
-
err
|
|
973
|
-
);
|
|
984
|
+
throw new RelayError("ENCODE_FAILED", `prepareMint: failed to encode mint call: ${errorMessage(err)}`, err);
|
|
974
985
|
}
|
|
975
986
|
const operations = [
|
|
976
987
|
{
|
|
@@ -986,16 +997,10 @@ var RelayService = class {
|
|
|
986
997
|
});
|
|
987
998
|
if (feeAmount > 0n) {
|
|
988
999
|
if (!feeRecipient) {
|
|
989
|
-
throw new RelayError(
|
|
990
|
-
"ENCODE_FAILED",
|
|
991
|
-
"prepareMint: feeRecipient could not be resolved \u2014 pass `feeRecipient` explicitly or construct RelayService with a `chainId`."
|
|
992
|
-
);
|
|
1000
|
+
throw new RelayError("ENCODE_FAILED", "prepareMint: feeRecipient could not be resolved \u2014 pass `feeRecipient` explicitly or construct RelayService with a `chainId`.");
|
|
993
1001
|
}
|
|
994
1002
|
if (feeRecipient === "0x0000000000000000000000000000000000000000") {
|
|
995
|
-
throw new RelayError(
|
|
996
|
-
"ENCODE_FAILED",
|
|
997
|
-
"prepareMint: feeRecipient must not be zero address"
|
|
998
|
-
);
|
|
1003
|
+
throw new RelayError("ENCODE_FAILED", "prepareMint: feeRecipient must not be zero address");
|
|
999
1004
|
}
|
|
1000
1005
|
operations.push({
|
|
1001
1006
|
target: params.pointTokenAddress,
|
|
@@ -1003,7 +1008,10 @@ var RelayService = class {
|
|
|
1003
1008
|
data: (0, import_viem3.encodeFunctionData)({
|
|
1004
1009
|
abi: import_viem3.erc20Abi,
|
|
1005
1010
|
functionName: "transfer",
|
|
1006
|
-
args: [
|
|
1011
|
+
args: [
|
|
1012
|
+
feeRecipient,
|
|
1013
|
+
feeAmount
|
|
1014
|
+
]
|
|
1007
1015
|
})
|
|
1008
1016
|
});
|
|
1009
1017
|
}
|
|
@@ -1019,29 +1027,23 @@ var RelayService = class {
|
|
|
1019
1027
|
});
|
|
1020
1028
|
}
|
|
1021
1029
|
/**
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
+
* Build an unsigned UserOp for Scenario 2 (Burn/Redeem) — sig-gated
|
|
1031
|
+
* `PointToken.burn(from, amount, deadline, burnerSig)`. Caller
|
|
1032
|
+
* provides a pre-signed `BurnRequest` + sig bytes (typically from
|
|
1033
|
+
* `PTRedeemHandler`).
|
|
1034
|
+
*
|
|
1035
|
+
* Direct burn (no sig) is not used — every burn goes through the
|
|
1036
|
+
* issuer-signed `BurnRequest` path.
|
|
1037
|
+
*/
|
|
1030
1038
|
async prepareBurn(params) {
|
|
1031
1039
|
if (!params.pointTokenAddress) {
|
|
1032
1040
|
throw new RelayError("ENCODE_FAILED", "prepareBurn: pointTokenAddress required");
|
|
1033
1041
|
}
|
|
1034
1042
|
if (!params.batchExecutorAddress) {
|
|
1035
|
-
throw new RelayError(
|
|
1036
|
-
"ENCODE_FAILED",
|
|
1037
|
-
"prepareBurn: batchExecutorAddress required"
|
|
1038
|
-
);
|
|
1043
|
+
throw new RelayError("ENCODE_FAILED", "prepareBurn: batchExecutorAddress required");
|
|
1039
1044
|
}
|
|
1040
1045
|
if (!params.burnRequest || !params.burnerSignature) {
|
|
1041
|
-
throw new RelayError(
|
|
1042
|
-
"ENCODE_FAILED",
|
|
1043
|
-
"prepareBurn: burnRequest + burnerSignature required"
|
|
1044
|
-
);
|
|
1046
|
+
throw new RelayError("ENCODE_FAILED", "prepareBurn: burnRequest + burnerSignature required");
|
|
1045
1047
|
}
|
|
1046
1048
|
let burnCallData;
|
|
1047
1049
|
try {
|
|
@@ -1057,11 +1059,7 @@ var RelayService = class {
|
|
|
1057
1059
|
]
|
|
1058
1060
|
});
|
|
1059
1061
|
} catch (err) {
|
|
1060
|
-
throw new RelayError(
|
|
1061
|
-
"ENCODE_FAILED",
|
|
1062
|
-
`prepareBurn: failed to encode burn call: ${errorMessage(err)}`,
|
|
1063
|
-
err
|
|
1064
|
-
);
|
|
1062
|
+
throw new RelayError("ENCODE_FAILED", `prepareBurn: failed to encode burn call: ${errorMessage(err)}`, err);
|
|
1065
1063
|
}
|
|
1066
1064
|
const operations = [];
|
|
1067
1065
|
const { feeAmount, feeRecipient } = await this.resolveFee({
|
|
@@ -1071,16 +1069,10 @@ var RelayService = class {
|
|
|
1071
1069
|
});
|
|
1072
1070
|
if (feeAmount > 0n) {
|
|
1073
1071
|
if (!feeRecipient) {
|
|
1074
|
-
throw new RelayError(
|
|
1075
|
-
"ENCODE_FAILED",
|
|
1076
|
-
"prepareBurn: feeRecipient could not be resolved \u2014 pass `feeRecipient` explicitly or construct RelayService with a `chainId`."
|
|
1077
|
-
);
|
|
1072
|
+
throw new RelayError("ENCODE_FAILED", "prepareBurn: feeRecipient could not be resolved \u2014 pass `feeRecipient` explicitly or construct RelayService with a `chainId`.");
|
|
1078
1073
|
}
|
|
1079
1074
|
if (feeRecipient === "0x0000000000000000000000000000000000000000") {
|
|
1080
|
-
throw new RelayError(
|
|
1081
|
-
"ENCODE_FAILED",
|
|
1082
|
-
"prepareBurn: feeRecipient must not be zero address"
|
|
1083
|
-
);
|
|
1075
|
+
throw new RelayError("ENCODE_FAILED", "prepareBurn: feeRecipient must not be zero address");
|
|
1084
1076
|
}
|
|
1085
1077
|
operations.push({
|
|
1086
1078
|
target: params.pointTokenAddress,
|
|
@@ -1088,7 +1080,10 @@ var RelayService = class {
|
|
|
1088
1080
|
data: (0, import_viem3.encodeFunctionData)({
|
|
1089
1081
|
abi: import_viem3.erc20Abi,
|
|
1090
1082
|
functionName: "transfer",
|
|
1091
|
-
args: [
|
|
1083
|
+
args: [
|
|
1084
|
+
feeRecipient,
|
|
1085
|
+
feeAmount
|
|
1086
|
+
]
|
|
1092
1087
|
})
|
|
1093
1088
|
});
|
|
1094
1089
|
}
|
|
@@ -1123,10 +1118,10 @@ var RelayService = class {
|
|
|
1123
1118
|
// call seeds the cache; subsequent ones hit it.
|
|
1124
1119
|
// =========================================================================
|
|
1125
1120
|
/**
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1121
|
+
* Build a dummy `PartialUserOperation` for the mint scenario, suitable
|
|
1122
|
+
* for `feeManager.estimateGasFee({ partialUserOp, ... })`. NO signing —
|
|
1123
|
+
* uses a 65-byte zero signature placeholder.
|
|
1124
|
+
*/
|
|
1130
1125
|
previewMintUserOp(params) {
|
|
1131
1126
|
const useWrapper = params.mintFeeWrapperAddress !== void 0;
|
|
1132
1127
|
let mintCallData;
|
|
@@ -1161,7 +1156,13 @@ var RelayService = class {
|
|
|
1161
1156
|
return (0, import_core5.buildPartialUserOperation)({
|
|
1162
1157
|
sender: params.userAddress,
|
|
1163
1158
|
nonce: params.aaNonce,
|
|
1164
|
-
operations: [
|
|
1159
|
+
operations: [
|
|
1160
|
+
{
|
|
1161
|
+
target: mintTarget,
|
|
1162
|
+
value: 0n,
|
|
1163
|
+
data: mintCallData
|
|
1164
|
+
}
|
|
1165
|
+
],
|
|
1165
1166
|
// Gas limits ignored by bundler estimate — it computes them.
|
|
1166
1167
|
gasLimits: {
|
|
1167
1168
|
callGasLimit: 1n,
|
|
@@ -1187,7 +1188,11 @@ var RelayService = class {
|
|
|
1187
1188
|
sender: params.userAddress,
|
|
1188
1189
|
nonce: params.aaNonce,
|
|
1189
1190
|
operations: [
|
|
1190
|
-
{
|
|
1191
|
+
{
|
|
1192
|
+
target: params.pointTokenAddress,
|
|
1193
|
+
value: 0n,
|
|
1194
|
+
data: burnCallData
|
|
1195
|
+
}
|
|
1191
1196
|
],
|
|
1192
1197
|
gasLimits: {
|
|
1193
1198
|
callGasLimit: 1n,
|
|
@@ -1201,11 +1206,15 @@ var PLACEHOLDER_SIG_65 = `0x${"00".repeat(65)}`;
|
|
|
1201
1206
|
function errorMessage(err) {
|
|
1202
1207
|
return err instanceof Error ? err.message : String(err);
|
|
1203
1208
|
}
|
|
1209
|
+
__name(errorMessage, "errorMessage");
|
|
1204
1210
|
|
|
1205
1211
|
// src/relay/feeManager.ts
|
|
1206
1212
|
var DEFAULT_GAS_UNITS = 500000n;
|
|
1207
1213
|
var DEFAULT_PREMIUM_BPS = 1e4;
|
|
1208
1214
|
var FeeManager = class _FeeManager {
|
|
1215
|
+
static {
|
|
1216
|
+
__name(this, "FeeManager");
|
|
1217
|
+
}
|
|
1209
1218
|
provider;
|
|
1210
1219
|
fallbackGasUnits;
|
|
1211
1220
|
gasPremiumBps;
|
|
@@ -1222,8 +1231,7 @@ var FeeManager = class _FeeManager {
|
|
|
1222
1231
|
static FEE_CACHE_TTL_MS = 1e4;
|
|
1223
1232
|
constructor(config) {
|
|
1224
1233
|
if (!config.provider) throw new Error("FeeManager: provider required");
|
|
1225
|
-
if (!config.quoteNativeToFee)
|
|
1226
|
-
throw new Error("FeeManager: quoteNativeToFee required");
|
|
1234
|
+
if (!config.quoteNativeToFee) throw new Error("FeeManager: quoteNativeToFee required");
|
|
1227
1235
|
this.provider = config.provider;
|
|
1228
1236
|
this.fallbackGasUnits = config.gasUnits ?? DEFAULT_GAS_UNITS;
|
|
1229
1237
|
this.gasPremiumBps = config.gasPremiumBps ?? DEFAULT_PREMIUM_BPS;
|
|
@@ -1232,16 +1240,16 @@ var FeeManager = class _FeeManager {
|
|
|
1232
1240
|
this.metrics = config.metrics;
|
|
1233
1241
|
}
|
|
1234
1242
|
/**
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1243
|
+
* Estimate the operator fee for the next sponsored UserOp.
|
|
1244
|
+
*
|
|
1245
|
+
* Without `opts` → legacy path: `gasUnits × gasPrice × premium →
|
|
1246
|
+
* quoteNativeToFee`. Cached for 10 s to absorb bursts.
|
|
1247
|
+
*
|
|
1248
|
+
* With `opts` AND `bundlerClient` → estimator path. Each call may
|
|
1249
|
+
* hit a different bundler-cached result; the SDK does NOT add its
|
|
1250
|
+
* own value cache because the estimator's cache TTL is the source
|
|
1251
|
+
* of truth for "how long is this estimate good for".
|
|
1252
|
+
*/
|
|
1245
1253
|
async estimateGasFee(opts = {}) {
|
|
1246
1254
|
const isLegacyCall = !opts.partialUserOp && !opts.scenario && !opts.contractAddress;
|
|
1247
1255
|
const now = Date.now();
|
|
@@ -1251,14 +1259,12 @@ var FeeManager = class _FeeManager {
|
|
|
1251
1259
|
const t0 = Date.now();
|
|
1252
1260
|
const { gasUnits, source } = await this.resolveGasUnits(opts);
|
|
1253
1261
|
const latencyMs = Date.now() - t0;
|
|
1254
|
-
this.safeEmit(
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
})
|
|
1261
|
-
);
|
|
1262
|
+
this.safeEmit(() => this.metrics?.onEstimate?.({
|
|
1263
|
+
source,
|
|
1264
|
+
scenario: opts.scenario,
|
|
1265
|
+
gasUnits,
|
|
1266
|
+
latencyMs
|
|
1267
|
+
}));
|
|
1262
1268
|
const gasPrice = await this.provider.getGasPrice();
|
|
1263
1269
|
const nativeCost = gasPrice * gasUnits;
|
|
1264
1270
|
const withPremium = nativeCost * BigInt(this.gasPremiumBps) / 10000n;
|
|
@@ -1276,7 +1282,10 @@ var FeeManager = class _FeeManager {
|
|
|
1276
1282
|
}
|
|
1277
1283
|
async resolveGasUnits(opts) {
|
|
1278
1284
|
if (!this.bundlerClient || !opts.partialUserOp || !opts.scenario || !opts.contractAddress) {
|
|
1279
|
-
return {
|
|
1285
|
+
return {
|
|
1286
|
+
gasUnits: this.fallbackGasUnits,
|
|
1287
|
+
source: "fallback"
|
|
1288
|
+
};
|
|
1280
1289
|
}
|
|
1281
1290
|
try {
|
|
1282
1291
|
const result = await this.bundlerClient.getGasUnits({
|
|
@@ -1285,16 +1294,20 @@ var FeeManager = class _FeeManager {
|
|
|
1285
1294
|
paymasterAddress: opts.paymasterAddress,
|
|
1286
1295
|
partialUserOp: opts.partialUserOp
|
|
1287
1296
|
});
|
|
1288
|
-
return {
|
|
1297
|
+
return {
|
|
1298
|
+
gasUnits: result.gasUnits,
|
|
1299
|
+
source: "estimator"
|
|
1300
|
+
};
|
|
1289
1301
|
} catch (err) {
|
|
1290
1302
|
const reason = err instanceof Error ? err.message : String(err);
|
|
1291
|
-
this.safeEmit(
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1303
|
+
this.safeEmit(() => this.metrics?.onEstimatorError?.({
|
|
1304
|
+
scenario: opts.scenario,
|
|
1305
|
+
reason
|
|
1306
|
+
}));
|
|
1307
|
+
return {
|
|
1308
|
+
gasUnits: this.fallbackGasUnits,
|
|
1309
|
+
source: "fallback"
|
|
1310
|
+
};
|
|
1298
1311
|
}
|
|
1299
1312
|
}
|
|
1300
1313
|
safeEmit(fn) {
|
|
@@ -1307,6 +1320,9 @@ var FeeManager = class _FeeManager {
|
|
|
1307
1320
|
|
|
1308
1321
|
// src/relay/bundlerEstimator.ts
|
|
1309
1322
|
var PafiEstimatorHttpError = class extends Error {
|
|
1323
|
+
static {
|
|
1324
|
+
__name(this, "PafiEstimatorHttpError");
|
|
1325
|
+
}
|
|
1310
1326
|
status;
|
|
1311
1327
|
body;
|
|
1312
1328
|
constructor(status, body, message) {
|
|
@@ -1322,9 +1338,7 @@ function createPafiEstimatorClient(config) {
|
|
|
1322
1338
|
if (!issuerId) throw new Error("createPafiEstimatorClient: issuerId required");
|
|
1323
1339
|
const fetchImpl = config.fetchImpl ?? globalThis.fetch;
|
|
1324
1340
|
if (!fetchImpl) {
|
|
1325
|
-
throw new Error(
|
|
1326
|
-
"createPafiEstimatorClient: no fetch implementation available \u2014 pass `fetchImpl`"
|
|
1327
|
-
);
|
|
1341
|
+
throw new Error("createPafiEstimatorClient: no fetch implementation available \u2014 pass `fetchImpl`");
|
|
1328
1342
|
}
|
|
1329
1343
|
const url = `${baseUrl.replace(/\/$/, "")}/v1/estimate-gas-fee`;
|
|
1330
1344
|
return {
|
|
@@ -1368,15 +1382,19 @@ function createPafiEstimatorClient(config) {
|
|
|
1368
1382
|
}
|
|
1369
1383
|
};
|
|
1370
1384
|
}
|
|
1385
|
+
__name(createPafiEstimatorClient, "createPafiEstimatorClient");
|
|
1371
1386
|
|
|
1372
1387
|
// src/indexer/types.ts
|
|
1373
1388
|
var InMemoryCursorStore = class _InMemoryCursorStore {
|
|
1389
|
+
static {
|
|
1390
|
+
__name(this, "InMemoryCursorStore");
|
|
1391
|
+
}
|
|
1374
1392
|
cursor;
|
|
1375
1393
|
/**
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1394
|
+
* Child stores keyed by `forKey()`. Each child has its own cursor,
|
|
1395
|
+
* so a single InMemoryCursorStore can back N PointIndexers in tests
|
|
1396
|
+
* / single-process callers.
|
|
1397
|
+
*/
|
|
1380
1398
|
children = /* @__PURE__ */ new Map();
|
|
1381
1399
|
async load() {
|
|
1382
1400
|
return this.cursor;
|
|
@@ -1397,21 +1415,18 @@ var InMemoryCursorStore = class _InMemoryCursorStore {
|
|
|
1397
1415
|
// src/indexer/pointIndexer.ts
|
|
1398
1416
|
var import_viem4 = require("viem");
|
|
1399
1417
|
var PointIndexerFinalizeError = class extends Error {
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
this.context = context;
|
|
1403
|
-
this.cause = cause;
|
|
1404
|
-
this.name = "PointIndexerFinalizeError";
|
|
1418
|
+
static {
|
|
1419
|
+
__name(this, "PointIndexerFinalizeError");
|
|
1405
1420
|
}
|
|
1406
1421
|
context;
|
|
1407
1422
|
cause;
|
|
1423
|
+
constructor(message, context, cause) {
|
|
1424
|
+
super(message), this.context = context, this.cause = cause;
|
|
1425
|
+
this.name = "PointIndexerFinalizeError";
|
|
1426
|
+
}
|
|
1408
1427
|
};
|
|
1409
|
-
var TRANSFER_EVENT = (0, import_viem4.parseAbiItem)(
|
|
1410
|
-
|
|
1411
|
-
);
|
|
1412
|
-
var MINT_WITH_FEE_EVENT = (0, import_viem4.parseAbiItem)(
|
|
1413
|
-
"event MintWithFee(address indexed pointToken, address indexed to, uint256 grossAmount, uint256 netAmount, uint256 feeAmount)"
|
|
1414
|
-
);
|
|
1428
|
+
var TRANSFER_EVENT = (0, import_viem4.parseAbiItem)("event Transfer(address indexed from, address indexed to, uint256 value)");
|
|
1429
|
+
var MINT_WITH_FEE_EVENT = (0, import_viem4.parseAbiItem)("event MintWithFee(address indexed pointToken, address indexed to, uint256 grossAmount, uint256 netAmount, uint256 feeAmount)");
|
|
1415
1430
|
var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
1416
1431
|
var DEAD_ADDRESS = "0x000000000000000000000000000000000000dEaD";
|
|
1417
1432
|
var DEFAULT_CONFIRMATIONS = 3;
|
|
@@ -1422,7 +1437,11 @@ function isNoWrapper(addr) {
|
|
|
1422
1437
|
const checksummed = (0, import_viem4.getAddress)(addr);
|
|
1423
1438
|
return checksummed === ZERO_ADDRESS || checksummed === DEAD_ADDRESS;
|
|
1424
1439
|
}
|
|
1440
|
+
__name(isNoWrapper, "isNoWrapper");
|
|
1425
1441
|
var PointIndexer = class {
|
|
1442
|
+
static {
|
|
1443
|
+
__name(this, "PointIndexer");
|
|
1444
|
+
}
|
|
1426
1445
|
provider;
|
|
1427
1446
|
pointTokenAddress;
|
|
1428
1447
|
mintFeeWrapperAddress;
|
|
@@ -1437,8 +1456,7 @@ var PointIndexer = class {
|
|
|
1437
1456
|
timer;
|
|
1438
1457
|
constructor(config) {
|
|
1439
1458
|
if (!config.provider) throw new Error("PointIndexer: provider required");
|
|
1440
|
-
if (!config.pointTokenAddress)
|
|
1441
|
-
throw new Error("PointIndexer: pointTokenAddress required");
|
|
1459
|
+
if (!config.pointTokenAddress) throw new Error("PointIndexer: pointTokenAddress required");
|
|
1442
1460
|
if (!config.ledger) throw new Error("PointIndexer: ledger required");
|
|
1443
1461
|
this.provider = config.provider;
|
|
1444
1462
|
this.pointTokenAddress = (0, import_viem4.getAddress)(config.pointTokenAddress);
|
|
@@ -1469,10 +1487,10 @@ var PointIndexer = class {
|
|
|
1469
1487
|
}
|
|
1470
1488
|
}
|
|
1471
1489
|
/**
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1490
|
+
* Run one poll cycle: load cursor → scan [cursor, safeHead] in
|
|
1491
|
+
* `batchSize` chunks → persist new cursor. Swallows any error and
|
|
1492
|
+
* schedules the next tick. Visible for test harnesses via a public name.
|
|
1493
|
+
*/
|
|
1476
1494
|
async tick() {
|
|
1477
1495
|
if (!this.running) return;
|
|
1478
1496
|
try {
|
|
@@ -1507,19 +1525,16 @@ var PointIndexer = class {
|
|
|
1507
1525
|
}
|
|
1508
1526
|
scheduleNext() {
|
|
1509
1527
|
if (!this.running) return;
|
|
1510
|
-
this.timer = setTimeout(
|
|
1511
|
-
() => this.tick().catch((err) => this.handleTickError(err)),
|
|
1512
|
-
this.pollIntervalMs
|
|
1513
|
-
);
|
|
1528
|
+
this.timer = setTimeout(() => this.tick().catch((err) => this.handleTickError(err)), this.pollIntervalMs);
|
|
1514
1529
|
}
|
|
1515
1530
|
// -------------------------------------------------------------------------
|
|
1516
1531
|
// Block scanning
|
|
1517
1532
|
// -------------------------------------------------------------------------
|
|
1518
1533
|
/**
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1534
|
+
* Scan `[from, to]` inclusive for mint events in `batchSize` chunks.
|
|
1535
|
+
* Callers can use this directly to backfill a specific range without
|
|
1536
|
+
* engaging `start()`. On completion, the cursor is advanced to `to + 1`.
|
|
1537
|
+
*/
|
|
1523
1538
|
async processBlockRange(from, to) {
|
|
1524
1539
|
if (from > to) return;
|
|
1525
1540
|
let cursor = from;
|
|
@@ -1543,15 +1558,17 @@ var PointIndexer = class {
|
|
|
1543
1558
|
// Event fetching — two modes (wrapper vs direct)
|
|
1544
1559
|
// -------------------------------------------------------------------------
|
|
1545
1560
|
/**
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1561
|
+
* Wrapper mode: listen for `MintWithFee` on the wrapper,
|
|
1562
|
+
* filtered to events for THIS pointToken only. The event's `to` field
|
|
1563
|
+
* is the actual end user, and `grossAmount` matches the lock amount.
|
|
1564
|
+
*/
|
|
1550
1565
|
async fetchWrapperMintEvents(fromBlock, toBlock) {
|
|
1551
1566
|
const logs = await this.provider.getLogs({
|
|
1552
1567
|
address: this.mintFeeWrapperAddress,
|
|
1553
1568
|
event: MINT_WITH_FEE_EVENT,
|
|
1554
|
-
args: {
|
|
1569
|
+
args: {
|
|
1570
|
+
pointToken: this.pointTokenAddress
|
|
1571
|
+
},
|
|
1555
1572
|
fromBlock,
|
|
1556
1573
|
toBlock
|
|
1557
1574
|
});
|
|
@@ -1573,14 +1590,16 @@ var PointIndexer = class {
|
|
|
1573
1590
|
return out;
|
|
1574
1591
|
}
|
|
1575
1592
|
/**
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1593
|
+
* Direct mode (legacy / chains without wrapper): listen for
|
|
1594
|
+
* `Transfer(from=0x0 → to)` on the PointToken itself.
|
|
1595
|
+
*/
|
|
1579
1596
|
async fetchTransferMintEvents(fromBlock, toBlock) {
|
|
1580
1597
|
const logs = await this.provider.getLogs({
|
|
1581
1598
|
address: this.pointTokenAddress,
|
|
1582
1599
|
event: TRANSFER_EVENT,
|
|
1583
|
-
args: {
|
|
1600
|
+
args: {
|
|
1601
|
+
from: ZERO_ADDRESS
|
|
1602
|
+
},
|
|
1584
1603
|
fromBlock,
|
|
1585
1604
|
toBlock
|
|
1586
1605
|
});
|
|
@@ -1607,42 +1626,30 @@ var PointIndexer = class {
|
|
|
1607
1626
|
// Finalization
|
|
1608
1627
|
// -------------------------------------------------------------------------
|
|
1609
1628
|
/**
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1629
|
+
* Finalize a single mint event: match it to a PENDING lock in the
|
|
1630
|
+
* ledger, then call `deductBalance` (which also resolves the lock in
|
|
1631
|
+
* the default `MemoryPointLedger`).
|
|
1632
|
+
*
|
|
1633
|
+
* No-matching-lock is a valid state: it means either the lock already
|
|
1634
|
+
* expired, or the mint was authorized out-of-band (e.g. a direct
|
|
1635
|
+
* `PointToken.mint()` from an EOA minter for testing). In that case we
|
|
1636
|
+
* do NOT touch the ledger — crediting here would silently allow the
|
|
1637
|
+
* issuer to mint without going through the gateway.
|
|
1638
|
+
*/
|
|
1620
1639
|
async finalize(evt) {
|
|
1621
|
-
const locks = await this.ledger.getLockedRequests(
|
|
1622
|
-
evt.to,
|
|
1623
|
-
this.pointTokenAddress
|
|
1624
|
-
);
|
|
1640
|
+
const locks = await this.ledger.getLockedRequests(evt.to, this.pointTokenAddress);
|
|
1625
1641
|
const match = pickMatchingLock(locks, evt.amount);
|
|
1626
1642
|
if (!match) return;
|
|
1627
1643
|
try {
|
|
1628
|
-
await this.ledger.deductBalance(
|
|
1629
|
-
evt.to,
|
|
1630
|
-
evt.amount,
|
|
1631
|
-
evt.txHash,
|
|
1632
|
-
this.pointTokenAddress
|
|
1633
|
-
);
|
|
1644
|
+
await this.ledger.deductBalance(evt.to, evt.amount, evt.txHash, this.pointTokenAddress);
|
|
1634
1645
|
} catch (err) {
|
|
1635
|
-
throw new PointIndexerFinalizeError(
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
blockNumber: evt.blockNumber
|
|
1643
|
-
},
|
|
1644
|
-
err
|
|
1645
|
-
);
|
|
1646
|
+
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.`, {
|
|
1647
|
+
pointToken: this.pointTokenAddress,
|
|
1648
|
+
to: evt.to,
|
|
1649
|
+
amount: evt.amount,
|
|
1650
|
+
txHash: evt.txHash,
|
|
1651
|
+
blockNumber: evt.blockNumber
|
|
1652
|
+
}, err);
|
|
1646
1653
|
}
|
|
1647
1654
|
try {
|
|
1648
1655
|
await this.ledger.updateMintStatus(match.lockId, "MINTED", evt.txHash);
|
|
@@ -1661,32 +1668,35 @@ function pickMatchingLock(locks, amount) {
|
|
|
1661
1668
|
}
|
|
1662
1669
|
return best;
|
|
1663
1670
|
}
|
|
1671
|
+
__name(pickMatchingLock, "pickMatchingLock");
|
|
1664
1672
|
|
|
1665
1673
|
// src/indexer/burnIndexer.ts
|
|
1666
1674
|
var import_viem5 = require("viem");
|
|
1667
1675
|
var BurnIndexerFinalizeError = class extends Error {
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
this.context = context;
|
|
1671
|
-
this.cause = cause;
|
|
1672
|
-
this.name = "BurnIndexerFinalizeError";
|
|
1676
|
+
static {
|
|
1677
|
+
__name(this, "BurnIndexerFinalizeError");
|
|
1673
1678
|
}
|
|
1674
1679
|
context;
|
|
1675
1680
|
cause;
|
|
1681
|
+
constructor(message, context, cause) {
|
|
1682
|
+
super(message), this.context = context, this.cause = cause;
|
|
1683
|
+
this.name = "BurnIndexerFinalizeError";
|
|
1684
|
+
}
|
|
1676
1685
|
};
|
|
1677
|
-
var TRANSFER_EVENT2 = (0, import_viem5.parseAbiItem)(
|
|
1678
|
-
"event Transfer(address indexed from, address indexed to, uint256 value)"
|
|
1679
|
-
);
|
|
1686
|
+
var TRANSFER_EVENT2 = (0, import_viem5.parseAbiItem)("event Transfer(address indexed from, address indexed to, uint256 value)");
|
|
1680
1687
|
var ZERO_ADDRESS2 = "0x0000000000000000000000000000000000000000";
|
|
1681
1688
|
var DEFAULT_CONFIRMATIONS2 = 3;
|
|
1682
1689
|
var DEFAULT_BATCH_SIZE2 = 2000n;
|
|
1683
1690
|
var DEFAULT_POLL_INTERVAL_MS2 = 5e3;
|
|
1684
1691
|
var BurnIndexer = class {
|
|
1692
|
+
static {
|
|
1693
|
+
__name(this, "BurnIndexer");
|
|
1694
|
+
}
|
|
1685
1695
|
provider;
|
|
1686
1696
|
/**
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1697
|
+
* The PointToken this indexer watches. Exposed so callers can key
|
|
1698
|
+
* leader-election locks / cursor stores by token
|
|
1699
|
+
*/
|
|
1690
1700
|
pointTokenAddress;
|
|
1691
1701
|
ledger;
|
|
1692
1702
|
cursorStore;
|
|
@@ -1700,24 +1710,19 @@ var BurnIndexer = class {
|
|
|
1700
1710
|
timer;
|
|
1701
1711
|
constructor(config) {
|
|
1702
1712
|
if (!config.provider) throw new Error("BurnIndexer: provider required");
|
|
1703
|
-
if (!config.pointTokenAddress)
|
|
1704
|
-
throw new Error("BurnIndexer: pointTokenAddress required");
|
|
1713
|
+
if (!config.pointTokenAddress) throw new Error("BurnIndexer: pointTokenAddress required");
|
|
1705
1714
|
if (!config.ledger) throw new Error("BurnIndexer: ledger required");
|
|
1706
1715
|
this.provider = config.provider;
|
|
1707
1716
|
this.pointTokenAddress = config.pointTokenAddress;
|
|
1708
1717
|
this.ledger = config.ledger;
|
|
1709
1718
|
this.cursorStore = config.cursorStore ?? new InMemoryCursorStore();
|
|
1710
1719
|
this.startBlock = config.fromBlock ?? 0n;
|
|
1711
|
-
this.confirmations = BigInt(
|
|
1712
|
-
config.confirmations ?? DEFAULT_CONFIRMATIONS2
|
|
1713
|
-
);
|
|
1720
|
+
this.confirmations = BigInt(config.confirmations ?? DEFAULT_CONFIRMATIONS2);
|
|
1714
1721
|
this.batchSize = BigInt(config.batchSize ?? Number(DEFAULT_BATCH_SIZE2));
|
|
1715
1722
|
this.pollIntervalMs = config.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS2;
|
|
1716
1723
|
if (config.onTickError) this.onTickError = config.onTickError;
|
|
1717
1724
|
if (!config.matchLockId) {
|
|
1718
|
-
throw new Error(
|
|
1719
|
-
"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."
|
|
1720
|
-
);
|
|
1725
|
+
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.");
|
|
1721
1726
|
}
|
|
1722
1727
|
this.matchLockId = config.matchLockId;
|
|
1723
1728
|
}
|
|
@@ -1767,16 +1772,13 @@ var BurnIndexer = class {
|
|
|
1767
1772
|
}
|
|
1768
1773
|
scheduleNext() {
|
|
1769
1774
|
if (!this.running) return;
|
|
1770
|
-
this.timer = setTimeout(
|
|
1771
|
-
() => this.tick().catch((err) => this.handleTickError(err)),
|
|
1772
|
-
this.pollIntervalMs
|
|
1773
|
-
);
|
|
1775
|
+
this.timer = setTimeout(() => this.tick().catch((err) => this.handleTickError(err)), this.pollIntervalMs);
|
|
1774
1776
|
}
|
|
1775
1777
|
/**
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1778
|
+
* Scan `[from, to]` inclusive for burn events. Callers can drive this
|
|
1779
|
+
* directly to backfill a specific range without `start()`. Cursor is
|
|
1780
|
+
* advanced to `to + 1` on completion.
|
|
1781
|
+
*/
|
|
1780
1782
|
async processBlockRange(from, to) {
|
|
1781
1783
|
if (from > to) return;
|
|
1782
1784
|
let cursor = from;
|
|
@@ -1785,8 +1787,9 @@ var BurnIndexer = class {
|
|
|
1785
1787
|
const logs = await this.provider.getLogs({
|
|
1786
1788
|
address: this.pointTokenAddress,
|
|
1787
1789
|
event: TRANSFER_EVENT2,
|
|
1788
|
-
args: {
|
|
1789
|
-
|
|
1790
|
+
args: {
|
|
1791
|
+
to: ZERO_ADDRESS2
|
|
1792
|
+
},
|
|
1790
1793
|
fromBlock: cursor,
|
|
1791
1794
|
toBlock: chunkEnd
|
|
1792
1795
|
});
|
|
@@ -1822,17 +1825,15 @@ var BurnIndexer = class {
|
|
|
1822
1825
|
return out;
|
|
1823
1826
|
}
|
|
1824
1827
|
/**
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1828
|
+
* Resolve a matching pending credit for this burn event and call
|
|
1829
|
+
* `ledger.resolveCreditByBurnTx(lockId, txHash)`. If no match found,
|
|
1830
|
+
* log + skip.
|
|
1831
|
+
*/
|
|
1829
1832
|
async finalize(evt) {
|
|
1830
1833
|
const txHash = evt.txHash;
|
|
1831
1834
|
const lockId = await this.matchLockId(evt);
|
|
1832
1835
|
if (lockId === void 0) {
|
|
1833
|
-
console.warn(
|
|
1834
|
-
"[PAFI] BurnIndexer: matchLockId returned undefined for burn tx " + txHash + ". This burn will NOT be credited. Implement matchLockId to map burn events to lock IDs."
|
|
1835
|
-
);
|
|
1836
|
+
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.");
|
|
1836
1837
|
return;
|
|
1837
1838
|
}
|
|
1838
1839
|
if (!this.ledger.resolveCreditByBurnTx) {
|
|
@@ -1841,18 +1842,14 @@ var BurnIndexer = class {
|
|
|
1841
1842
|
try {
|
|
1842
1843
|
await this.ledger.resolveCreditByBurnTx(lockId, evt.txHash);
|
|
1843
1844
|
} catch (err) {
|
|
1844
|
-
throw new BurnIndexerFinalizeError(
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
lockId
|
|
1853
|
-
},
|
|
1854
|
-
err
|
|
1855
|
-
);
|
|
1845
|
+
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.`, {
|
|
1846
|
+
pointToken: this.pointTokenAddress,
|
|
1847
|
+
from: evt.from,
|
|
1848
|
+
amount: evt.amount,
|
|
1849
|
+
txHash: evt.txHash,
|
|
1850
|
+
blockNumber: evt.blockNumber,
|
|
1851
|
+
lockId
|
|
1852
|
+
}, err);
|
|
1856
1853
|
}
|
|
1857
1854
|
}
|
|
1858
1855
|
};
|
|
@@ -1862,10 +1859,9 @@ function makePostgresSingletonLock(runner) {
|
|
|
1862
1859
|
return {
|
|
1863
1860
|
async acquire(key) {
|
|
1864
1861
|
const lockId = hashKeyToInt64(key);
|
|
1865
|
-
const rows = await runner.query(
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
);
|
|
1862
|
+
const rows = await runner.query("SELECT pg_try_advisory_lock($1::bigint) AS got", [
|
|
1863
|
+
lockId
|
|
1864
|
+
]);
|
|
1869
1865
|
const got = rows[0]?.got === true;
|
|
1870
1866
|
if (!got) return null;
|
|
1871
1867
|
return {
|
|
@@ -1881,6 +1877,7 @@ function makePostgresSingletonLock(runner) {
|
|
|
1881
1877
|
}
|
|
1882
1878
|
};
|
|
1883
1879
|
}
|
|
1880
|
+
__name(makePostgresSingletonLock, "makePostgresSingletonLock");
|
|
1884
1881
|
function hashKeyToInt64(key) {
|
|
1885
1882
|
const FNV_OFFSET = 0xcbf29ce484222325n;
|
|
1886
1883
|
const FNV_PRIME = 0x100000001b3n;
|
|
@@ -1895,18 +1892,22 @@ function hashKeyToInt64(key) {
|
|
|
1895
1892
|
const signed = hash > SIGNED_MAX ? hash - TWO64 : hash;
|
|
1896
1893
|
return signed.toString();
|
|
1897
1894
|
}
|
|
1895
|
+
__name(hashKeyToInt64, "hashKeyToInt64");
|
|
1898
1896
|
|
|
1899
1897
|
// src/api/handlers.ts
|
|
1900
1898
|
var import_viem6 = require("viem");
|
|
1901
1899
|
var import_core6 = require("@pafi-dev/core");
|
|
1902
1900
|
var IssuerApiHandlers = class _IssuerApiHandlers {
|
|
1901
|
+
static {
|
|
1902
|
+
__name(this, "IssuerApiHandlers");
|
|
1903
|
+
}
|
|
1903
1904
|
authService;
|
|
1904
1905
|
ledger;
|
|
1905
1906
|
provider;
|
|
1906
1907
|
/**
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1908
|
+
* Set of supported PointToken addresses (checksum-normalized). Handlers
|
|
1909
|
+
* validate the request's `pointTokenAddress` against this set.
|
|
1910
|
+
*/
|
|
1910
1911
|
supportedTokens;
|
|
1911
1912
|
chainId;
|
|
1912
1913
|
contracts;
|
|
@@ -1917,10 +1918,10 @@ var IssuerApiHandlers = class _IssuerApiHandlers {
|
|
|
1917
1918
|
rateLimiter;
|
|
1918
1919
|
mintFeeWrapperAddress;
|
|
1919
1920
|
/**
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1921
|
+
* Per-token feeBps cache. Refreshed on /config when stale. feeBps
|
|
1922
|
+
* changes only when ops calls `wrapper.setRecipients`, so 5-min TTL
|
|
1923
|
+
* is more than safe for FE display purposes.
|
|
1924
|
+
*/
|
|
1924
1925
|
feeBpsCache = /* @__PURE__ */ new Map();
|
|
1925
1926
|
static FEE_BPS_TTL_MS = 5 * 60 * 1e3;
|
|
1926
1927
|
constructor(config) {
|
|
@@ -1928,11 +1929,11 @@ var IssuerApiHandlers = class _IssuerApiHandlers {
|
|
|
1928
1929
|
this.ledger = config.ledger;
|
|
1929
1930
|
this.provider = config.provider;
|
|
1930
1931
|
this.rateLimiter = config.rateLimiter ?? new NoopRateLimiter();
|
|
1931
|
-
const raw = config.pointTokenAddresses && config.pointTokenAddresses.length > 0 ? config.pointTokenAddresses : config.pointTokenAddress ? [
|
|
1932
|
+
const raw = config.pointTokenAddresses && config.pointTokenAddresses.length > 0 ? config.pointTokenAddresses : config.pointTokenAddress ? [
|
|
1933
|
+
config.pointTokenAddress
|
|
1934
|
+
] : [];
|
|
1932
1935
|
if (raw.length === 0) {
|
|
1933
|
-
throw new Error(
|
|
1934
|
-
"IssuerApiHandlers: pointTokenAddress or pointTokenAddresses required"
|
|
1935
|
-
);
|
|
1936
|
+
throw new Error("IssuerApiHandlers: pointTokenAddress or pointTokenAddresses required");
|
|
1936
1937
|
}
|
|
1937
1938
|
const normalized = raw.map((a) => (0, import_viem6.getAddress)(a));
|
|
1938
1939
|
this.supportedTokens = new Set(normalized);
|
|
@@ -1950,63 +1951,48 @@ var IssuerApiHandlers = class _IssuerApiHandlers {
|
|
|
1950
1951
|
// Public handlers (no auth required)
|
|
1951
1952
|
// =========================================================================
|
|
1952
1953
|
/**
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1954
|
+
* `GET /auth/nonce`
|
|
1955
|
+
*
|
|
1956
|
+
* @param rateLimitKey Caller-side rate-limit key (typically client IP).
|
|
1957
|
+
* The HTTP layer (controller/middleware) extracts
|
|
1958
|
+
* this from the request and passes it through.
|
|
1959
|
+
* When omitted, no rate limit applies — production
|
|
1960
|
+
* callers SHOULD always pass a key.
|
|
1961
|
+
*/
|
|
1961
1962
|
async handleGetNonce(rateLimitKey) {
|
|
1962
1963
|
if (rateLimitKey) {
|
|
1963
|
-
const result = await this.rateLimiter.consume(
|
|
1964
|
-
rateLimitKey,
|
|
1965
|
-
"auth_nonce"
|
|
1966
|
-
);
|
|
1964
|
+
const result = await this.rateLimiter.consume(rateLimitKey, "auth_nonce");
|
|
1967
1965
|
if (!result.allowed) {
|
|
1968
|
-
throw new import_core3.ValidationError(
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
retryAfterMs: result.retryAfterMs ?? 0,
|
|
1973
|
-
action: "auth_nonce"
|
|
1974
|
-
}
|
|
1975
|
-
);
|
|
1966
|
+
throw new import_core3.ValidationError("RATE_LIMIT_EXCEEDED", "handleGetNonce: too many requests", {
|
|
1967
|
+
retryAfterMs: result.retryAfterMs ?? 0,
|
|
1968
|
+
action: "auth_nonce"
|
|
1969
|
+
});
|
|
1976
1970
|
}
|
|
1977
1971
|
}
|
|
1978
1972
|
const nonce = await this.authService.getNonce();
|
|
1979
|
-
return {
|
|
1973
|
+
return {
|
|
1974
|
+
nonce
|
|
1975
|
+
};
|
|
1980
1976
|
}
|
|
1981
1977
|
/**
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1978
|
+
* `POST /auth/login`
|
|
1979
|
+
*
|
|
1980
|
+
* @param body Login message + signature.
|
|
1981
|
+
* @param rateLimitKey Caller-side rate-limit key (typically client IP
|
|
1982
|
+
* or `body.userAddress` if known). See `handleGetNonce`.
|
|
1983
|
+
*/
|
|
1988
1984
|
async handleLogin(body, rateLimitKey) {
|
|
1989
1985
|
if (rateLimitKey) {
|
|
1990
|
-
const result2 = await this.rateLimiter.consume(
|
|
1991
|
-
rateLimitKey,
|
|
1992
|
-
"auth_login"
|
|
1993
|
-
);
|
|
1986
|
+
const result2 = await this.rateLimiter.consume(rateLimitKey, "auth_login");
|
|
1994
1987
|
if (!result2.allowed) {
|
|
1995
|
-
throw new import_core3.ValidationError(
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
retryAfterMs: result2.retryAfterMs ?? 0,
|
|
2000
|
-
action: "auth_login"
|
|
2001
|
-
}
|
|
2002
|
-
);
|
|
1988
|
+
throw new import_core3.ValidationError("RATE_LIMIT_EXCEEDED", "handleLogin: too many requests", {
|
|
1989
|
+
retryAfterMs: result2.retryAfterMs ?? 0,
|
|
1990
|
+
action: "auth_login"
|
|
1991
|
+
});
|
|
2003
1992
|
}
|
|
2004
1993
|
}
|
|
2005
1994
|
if (!body || typeof body.message !== "string" || body.message.length === 0 || typeof body.signature !== "string" || body.signature.length <= 2) {
|
|
2006
|
-
throw new import_core3.ValidationError(
|
|
2007
|
-
"INVALID_LOGIN_BODY",
|
|
2008
|
-
"handleLogin: message and signature are required"
|
|
2009
|
-
);
|
|
1995
|
+
throw new import_core3.ValidationError("INVALID_LOGIN_BODY", "handleLogin: message and signature are required");
|
|
2010
1996
|
}
|
|
2011
1997
|
if (body.message.length > 4096) {
|
|
2012
1998
|
throw new import_core3.ValidationError("MESSAGE_TOO_LONG", "message too long");
|
|
@@ -2022,11 +2008,11 @@ var IssuerApiHandlers = class _IssuerApiHandlers {
|
|
|
2022
2008
|
};
|
|
2023
2009
|
}
|
|
2024
2010
|
/**
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2011
|
+
* `GET /config?chainId=<id>`
|
|
2012
|
+
*
|
|
2013
|
+
* Returns the contract addresses and chain id that the frontend SDK
|
|
2014
|
+
* needs to build EIP-712 messages and interact with on-chain.
|
|
2015
|
+
*/
|
|
2030
2016
|
async handleConfig(chainId) {
|
|
2031
2017
|
if (!Number.isInteger(chainId) || chainId <= 0) {
|
|
2032
2018
|
throw new import_core3.ValidationError("INVALID_CHAIN_ID", "invalid chainId", {
|
|
@@ -2034,11 +2020,10 @@ var IssuerApiHandlers = class _IssuerApiHandlers {
|
|
|
2034
2020
|
});
|
|
2035
2021
|
}
|
|
2036
2022
|
if (chainId !== this.chainId) {
|
|
2037
|
-
throw new import_core3.ValidationError(
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
);
|
|
2023
|
+
throw new import_core3.ValidationError("UNSUPPORTED_CHAIN_ID", `handleConfig: unsupported chainId ${chainId}`, {
|
|
2024
|
+
requested: chainId,
|
|
2025
|
+
supported: this.chainId
|
|
2026
|
+
});
|
|
2042
2027
|
}
|
|
2043
2028
|
const contracts = {
|
|
2044
2029
|
...this.contracts,
|
|
@@ -2052,60 +2037,55 @@ var IssuerApiHandlers = class _IssuerApiHandlers {
|
|
|
2052
2037
|
contracts
|
|
2053
2038
|
};
|
|
2054
2039
|
if (this.mintFeeWrapperAddress) {
|
|
2055
|
-
const byToken = await this.resolveFeeBpsByToken(
|
|
2056
|
-
this.mintFeeWrapperAddress
|
|
2057
|
-
);
|
|
2040
|
+
const byToken = await this.resolveFeeBpsByToken(this.mintFeeWrapperAddress);
|
|
2058
2041
|
if (byToken) response.mintFeeBpsByToken = byToken;
|
|
2059
2042
|
}
|
|
2060
2043
|
if (this.pafiWebUrl) response.pafiWebUrl = this.pafiWebUrl;
|
|
2061
2044
|
return response;
|
|
2062
2045
|
}
|
|
2063
2046
|
/**
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2047
|
+
* Read `totalFeeBps(pointToken)` for every supported PointToken from
|
|
2048
|
+
* the wrapper. Cached per-token for 5 minutes. Returns `undefined`
|
|
2049
|
+
* (caller drops the field) if every read fails — FE must treat
|
|
2050
|
+
* "field missing" as "fee unknown, do not display".
|
|
2051
|
+
*/
|
|
2069
2052
|
async resolveFeeBpsByToken(wrapper) {
|
|
2070
2053
|
const now = Date.now();
|
|
2071
2054
|
const out = {};
|
|
2072
2055
|
let anyFresh = false;
|
|
2073
|
-
await Promise.all(
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2056
|
+
await Promise.all(Array.from(this.supportedTokens).map(async (token) => {
|
|
2057
|
+
const cached = this.feeBpsCache.get(token);
|
|
2058
|
+
if (cached && cached.expiresAt > now) {
|
|
2059
|
+
out[token.toLowerCase()] = cached.value;
|
|
2060
|
+
anyFresh = true;
|
|
2061
|
+
return;
|
|
2062
|
+
}
|
|
2063
|
+
try {
|
|
2064
|
+
const bps = await (0, import_core6.getMintFeeBps)(this.provider, wrapper, token);
|
|
2065
|
+
this.feeBpsCache.set(token, {
|
|
2066
|
+
value: bps,
|
|
2067
|
+
expiresAt: now + _IssuerApiHandlers.FEE_BPS_TTL_MS
|
|
2068
|
+
});
|
|
2069
|
+
out[token.toLowerCase()] = bps;
|
|
2070
|
+
anyFresh = true;
|
|
2071
|
+
} catch {
|
|
2072
|
+
if (cached) {
|
|
2077
2073
|
out[token.toLowerCase()] = cached.value;
|
|
2078
2074
|
anyFresh = true;
|
|
2079
|
-
return;
|
|
2080
|
-
}
|
|
2081
|
-
try {
|
|
2082
|
-
const bps = await (0, import_core6.getMintFeeBps)(this.provider, wrapper, token);
|
|
2083
|
-
this.feeBpsCache.set(token, {
|
|
2084
|
-
value: bps,
|
|
2085
|
-
expiresAt: now + _IssuerApiHandlers.FEE_BPS_TTL_MS
|
|
2086
|
-
});
|
|
2087
|
-
out[token.toLowerCase()] = bps;
|
|
2088
|
-
anyFresh = true;
|
|
2089
|
-
} catch {
|
|
2090
|
-
if (cached) {
|
|
2091
|
-
out[token.toLowerCase()] = cached.value;
|
|
2092
|
-
anyFresh = true;
|
|
2093
|
-
}
|
|
2094
2075
|
}
|
|
2095
|
-
}
|
|
2096
|
-
);
|
|
2076
|
+
}
|
|
2077
|
+
}));
|
|
2097
2078
|
return anyFresh ? out : void 0;
|
|
2098
2079
|
}
|
|
2099
2080
|
/** `GET /gas-fee` — quoted in USDT (6-decimal base units). */
|
|
2100
2081
|
async handleGasFee() {
|
|
2101
2082
|
if (!this.feeManager) {
|
|
2102
|
-
throw new ConfigurationError(
|
|
2103
|
-
"FEE_MANAGER_NOT_CONFIGURED",
|
|
2104
|
-
"handleGasFee: feeManager is not configured on this issuer"
|
|
2105
|
-
);
|
|
2083
|
+
throw new ConfigurationError("FEE_MANAGER_NOT_CONFIGURED", "handleGasFee: feeManager is not configured on this issuer");
|
|
2106
2084
|
}
|
|
2107
2085
|
const gasFeeUsdt = await this.feeManager.estimateGasFee();
|
|
2108
|
-
return {
|
|
2086
|
+
return {
|
|
2087
|
+
gasFeeUsdt
|
|
2088
|
+
};
|
|
2109
2089
|
}
|
|
2110
2090
|
// =========================================================================
|
|
2111
2091
|
// Protected handlers (JWT required — userAddress extracted by middleware)
|
|
@@ -2115,57 +2095,49 @@ var IssuerApiHandlers = class _IssuerApiHandlers {
|
|
|
2115
2095
|
await this.authService.logout(token);
|
|
2116
2096
|
}
|
|
2117
2097
|
/**
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2098
|
+
* `GET /pools?chainId=<id>&pointToken=<addr>`
|
|
2099
|
+
*
|
|
2100
|
+
* Delegates to the injected `PoolsProvider`. The handler itself does
|
|
2101
|
+
* not know where pools come from — that's an issuer decision.
|
|
2102
|
+
*/
|
|
2123
2103
|
async handlePools(_userAddress, request) {
|
|
2124
2104
|
if (!this.poolsProvider) {
|
|
2125
|
-
throw new ConfigurationError(
|
|
2126
|
-
"POOLS_PROVIDER_NOT_CONFIGURED",
|
|
2127
|
-
"handlePools: poolsProvider is not configured on this issuer"
|
|
2128
|
-
);
|
|
2105
|
+
throw new ConfigurationError("POOLS_PROVIDER_NOT_CONFIGURED", "handlePools: poolsProvider is not configured on this issuer");
|
|
2129
2106
|
}
|
|
2130
2107
|
if (request.chainId !== this.chainId) {
|
|
2131
|
-
throw new import_core3.ValidationError(
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
);
|
|
2108
|
+
throw new import_core3.ValidationError("UNSUPPORTED_CHAIN_ID", `handlePools: unsupported chainId ${request.chainId}`, {
|
|
2109
|
+
requested: request.chainId,
|
|
2110
|
+
supported: this.chainId
|
|
2111
|
+
});
|
|
2136
2112
|
}
|
|
2137
2113
|
return this.poolsProvider(request);
|
|
2138
2114
|
}
|
|
2139
2115
|
/**
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2116
|
+
* `GET /user?chainId=<id>&user=<addr>&pointToken=<addr>`
|
|
2117
|
+
*
|
|
2118
|
+
* Returns per-user state the frontend needs to build a fresh mint:
|
|
2119
|
+
* on-chain nonces + minter status + off-chain balance.
|
|
2120
|
+
*/
|
|
2145
2121
|
async handleUser(userAddress, request) {
|
|
2146
2122
|
if (request.chainId !== this.chainId) {
|
|
2147
|
-
throw new import_core3.ValidationError(
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
);
|
|
2123
|
+
throw new import_core3.ValidationError("UNSUPPORTED_CHAIN_ID", `handleUser: unsupported chainId ${request.chainId}`, {
|
|
2124
|
+
requested: request.chainId,
|
|
2125
|
+
supported: this.chainId
|
|
2126
|
+
});
|
|
2152
2127
|
}
|
|
2153
2128
|
const normalizedAuthed = (0, import_viem6.getAddress)(userAddress);
|
|
2154
2129
|
const normalizedRequest = (0, import_viem6.getAddress)(request.userAddress);
|
|
2155
2130
|
if (normalizedAuthed !== normalizedRequest) {
|
|
2156
|
-
throw new import_core3.ValidationError(
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
);
|
|
2131
|
+
throw new import_core3.ValidationError("USER_ADDRESS_MISMATCH", "handleUser: request userAddress must match authenticated user", {
|
|
2132
|
+
authenticated: normalizedAuthed,
|
|
2133
|
+
requested: normalizedRequest
|
|
2134
|
+
});
|
|
2161
2135
|
}
|
|
2162
2136
|
const pointToken = (0, import_viem6.getAddress)(request.pointTokenAddress);
|
|
2163
2137
|
if (!this.supportedTokens.has(pointToken)) {
|
|
2164
|
-
throw new import_core3.ValidationError(
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
{ requested: pointToken }
|
|
2168
|
-
);
|
|
2138
|
+
throw new import_core3.ValidationError("UNSUPPORTED_POINT_TOKEN", `handleUser: unsupported pointToken ${pointToken}`, {
|
|
2139
|
+
requested: pointToken
|
|
2140
|
+
});
|
|
2169
2141
|
}
|
|
2170
2142
|
const [offChainBalance, onChainBalance, minter] = await Promise.all([
|
|
2171
2143
|
this.ledger.getBalance(normalizedAuthed, pointToken),
|
|
@@ -2177,7 +2149,6 @@ var IssuerApiHandlers = class _IssuerApiHandlers {
|
|
|
2177
2149
|
onChainBalance,
|
|
2178
2150
|
totalBalance: offChainBalance + onChainBalance,
|
|
2179
2151
|
balance: offChainBalance,
|
|
2180
|
-
// deprecated alias
|
|
2181
2152
|
isMinter: minter
|
|
2182
2153
|
};
|
|
2183
2154
|
}
|
|
@@ -2185,56 +2156,41 @@ var IssuerApiHandlers = class _IssuerApiHandlers {
|
|
|
2185
2156
|
// removed in 0.5.43 — callers should use `PTClaimHandler` directly or
|
|
2186
2157
|
// wire `IssuerApiAdapter.claim()` which composes the full flow.
|
|
2187
2158
|
/**
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2159
|
+
* `GET /redemption/preview?pointToken=<addr>`
|
|
2160
|
+
*
|
|
2161
|
+
* Returns the headroom currently available to `userAddress` under the
|
|
2162
|
+
* configured RedemptionPolicy. Pure read — does not record anything.
|
|
2163
|
+
* Use this for UI to render "X PT redeemable now / next available at …".
|
|
2164
|
+
*/
|
|
2194
2165
|
async handleRedemptionPreview(userAddress, request) {
|
|
2195
2166
|
if (!this.redemption) {
|
|
2196
|
-
throw new ConfigurationError(
|
|
2197
|
-
"REDEMPTION_NOT_CONFIGURED",
|
|
2198
|
-
"handleRedemptionPreview: redemption is not configured on this issuer"
|
|
2199
|
-
);
|
|
2167
|
+
throw new ConfigurationError("REDEMPTION_NOT_CONFIGURED", "handleRedemptionPreview: redemption is not configured on this issuer");
|
|
2200
2168
|
}
|
|
2201
2169
|
const tokenAddress = request.pointTokenAddress ? this.requireSupportedToken((0, import_viem6.getAddress)(request.pointTokenAddress), "handleRedemptionPreview") : void 0;
|
|
2202
|
-
const preview = await this.redemption.preview(
|
|
2203
|
-
(0, import_viem6.getAddress)(userAddress),
|
|
2204
|
-
tokenAddress
|
|
2205
|
-
);
|
|
2170
|
+
const preview = await this.redemption.preview((0, import_viem6.getAddress)(userAddress), tokenAddress);
|
|
2206
2171
|
return preview;
|
|
2207
2172
|
}
|
|
2208
2173
|
/**
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2174
|
+
* `POST /redemption/evaluate`
|
|
2175
|
+
*
|
|
2176
|
+
* Pre-flight check before the issuer signs a BurnRequest. Returns
|
|
2177
|
+
* { allowed, denial?, preview }. Caller (the burn-orchestrator) MUST
|
|
2178
|
+
* re-check on the actual initiate path — evaluate is read-only and a
|
|
2179
|
+
* caller could race two requests under the same headroom. The intended
|
|
2180
|
+
* write path is: evaluate → sign BurnRequest → reserve pending credit
|
|
2181
|
+
* → call `service.redemption.recordSuccessfulInitiate()`.
|
|
2182
|
+
*/
|
|
2218
2183
|
async handleRedemptionEvaluate(userAddress, request) {
|
|
2219
2184
|
if (!this.redemption) {
|
|
2220
|
-
throw new ConfigurationError(
|
|
2221
|
-
"REDEMPTION_NOT_CONFIGURED",
|
|
2222
|
-
"handleRedemptionEvaluate: redemption is not configured on this issuer"
|
|
2223
|
-
);
|
|
2185
|
+
throw new ConfigurationError("REDEMPTION_NOT_CONFIGURED", "handleRedemptionEvaluate: redemption is not configured on this issuer");
|
|
2224
2186
|
}
|
|
2225
2187
|
if (request.amountPt <= 0n) {
|
|
2226
|
-
throw new import_core3.ValidationError(
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
{ amountPt: request.amountPt.toString() }
|
|
2230
|
-
);
|
|
2188
|
+
throw new import_core3.ValidationError("INVALID_AMOUNT", "handleRedemptionEvaluate: amountPt must be positive", {
|
|
2189
|
+
amountPt: request.amountPt.toString()
|
|
2190
|
+
});
|
|
2231
2191
|
}
|
|
2232
2192
|
const tokenAddress = request.pointTokenAddress ? this.requireSupportedToken((0, import_viem6.getAddress)(request.pointTokenAddress), "handleRedemptionEvaluate") : void 0;
|
|
2233
|
-
const decision = await this.redemption.evaluate(
|
|
2234
|
-
(0, import_viem6.getAddress)(userAddress),
|
|
2235
|
-
request.amountPt,
|
|
2236
|
-
tokenAddress
|
|
2237
|
-
);
|
|
2193
|
+
const decision = await this.redemption.evaluate((0, import_viem6.getAddress)(userAddress), request.amountPt, tokenAddress);
|
|
2238
2194
|
const response = {
|
|
2239
2195
|
allowed: decision.allowed,
|
|
2240
2196
|
preview: decision.preview
|
|
@@ -2244,11 +2200,9 @@ var IssuerApiHandlers = class _IssuerApiHandlers {
|
|
|
2244
2200
|
}
|
|
2245
2201
|
requireSupportedToken(pointToken, handler) {
|
|
2246
2202
|
if (!this.supportedTokens.has(pointToken)) {
|
|
2247
|
-
throw new import_core3.ValidationError(
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
{ requested: pointToken }
|
|
2251
|
-
);
|
|
2203
|
+
throw new import_core3.ValidationError("UNSUPPORTED_POINT_TOKEN", `${handler}: unsupported pointToken ${pointToken}`, {
|
|
2204
|
+
requested: pointToken
|
|
2205
|
+
});
|
|
2252
2206
|
}
|
|
2253
2207
|
return pointToken;
|
|
2254
2208
|
}
|
|
@@ -2262,10 +2216,17 @@ var NAME_ABI = [
|
|
|
2262
2216
|
name: "name",
|
|
2263
2217
|
stateMutability: "view",
|
|
2264
2218
|
inputs: [],
|
|
2265
|
-
outputs: [
|
|
2219
|
+
outputs: [
|
|
2220
|
+
{
|
|
2221
|
+
type: "string"
|
|
2222
|
+
}
|
|
2223
|
+
]
|
|
2266
2224
|
}
|
|
2267
2225
|
];
|
|
2268
2226
|
var PointTokenDomainResolver = class {
|
|
2227
|
+
static {
|
|
2228
|
+
__name(this, "PointTokenDomainResolver");
|
|
2229
|
+
}
|
|
2269
2230
|
provider;
|
|
2270
2231
|
overrides;
|
|
2271
2232
|
cache = /* @__PURE__ */ new Map();
|
|
@@ -2312,6 +2273,9 @@ var DEFAULT_REDEEM_LOCK_MS = 15 * 60 * 1e3;
|
|
|
2312
2273
|
var M11_SAFETY_MARGIN_MS = 30 * 1e3;
|
|
2313
2274
|
var DEFAULT_SIG_DEADLINE_SEC = (DEFAULT_REDEEM_LOCK_MS - M11_SAFETY_MARGIN_MS) / 1e3;
|
|
2314
2275
|
var PTRedeemError = class extends import_core.PafiSdkError {
|
|
2276
|
+
static {
|
|
2277
|
+
__name(this, "PTRedeemError");
|
|
2278
|
+
}
|
|
2315
2279
|
httpStatus = "unprocessable";
|
|
2316
2280
|
code;
|
|
2317
2281
|
policyDenialCode;
|
|
@@ -2324,6 +2288,9 @@ var PTRedeemError = class extends import_core.PafiSdkError {
|
|
|
2324
2288
|
}
|
|
2325
2289
|
};
|
|
2326
2290
|
var PTRedeemHandler = class {
|
|
2291
|
+
static {
|
|
2292
|
+
__name(this, "PTRedeemHandler");
|
|
2293
|
+
}
|
|
2327
2294
|
ledger;
|
|
2328
2295
|
relayService;
|
|
2329
2296
|
provider;
|
|
@@ -2338,31 +2305,25 @@ var PTRedeemHandler = class {
|
|
|
2338
2305
|
now;
|
|
2339
2306
|
redemptionService;
|
|
2340
2307
|
/**
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2308
|
+
* Per-user in-flight nonce guard (single-process only).
|
|
2309
|
+
*
|
|
2310
|
+
* Prevents two concurrent requests from reading the same on-chain
|
|
2311
|
+
* burnRequestNonce before either has completed, which would produce two
|
|
2312
|
+
* signed UserOps with the same nonce — only one succeeds on-chain; the
|
|
2313
|
+
* other leaves an orphaned pending credit and a wasted signer call.
|
|
2314
|
+
*
|
|
2315
|
+
* NOTE: This guard is effective only within a single Node.js process. For
|
|
2316
|
+
* multi-instance deployments (k8s, PM2 cluster), enforce mutual exclusion
|
|
2317
|
+
* via a distributed lock (Redis SETNX / Postgres advisory lock) keyed on
|
|
2318
|
+
* `(userAddress, pointTokenAddress)` BEFORE calling `handle()`.
|
|
2319
|
+
*/
|
|
2353
2320
|
inFlightNonces = /* @__PURE__ */ new Map();
|
|
2354
2321
|
constructor(config) {
|
|
2355
2322
|
if (!config.ledger.reservePendingCredit) {
|
|
2356
|
-
throw new PTRedeemError(
|
|
2357
|
-
"LEDGER_NOT_SUPPORTED",
|
|
2358
|
-
"PTRedeemHandler requires a ledger that implements reservePendingCredit() (v0.3.0+)"
|
|
2359
|
-
);
|
|
2323
|
+
throw new PTRedeemError("LEDGER_NOT_SUPPORTED", "PTRedeemHandler requires a ledger that implements reservePendingCredit() (v0.3.0+)");
|
|
2360
2324
|
}
|
|
2361
2325
|
if (!config.burnerSignerWallet) {
|
|
2362
|
-
throw new PTRedeemError(
|
|
2363
|
-
"SIGNING_FAILED",
|
|
2364
|
-
"PTRedeemHandler requires burnerSignerWallet (issuer burner signer)"
|
|
2365
|
-
);
|
|
2326
|
+
throw new PTRedeemError("SIGNING_FAILED", "PTRedeemHandler requires burnerSignerWallet (issuer burner signer)");
|
|
2366
2327
|
}
|
|
2367
2328
|
this.ledger = config.ledger;
|
|
2368
2329
|
this.relayService = config.relayService;
|
|
@@ -2373,10 +2334,7 @@ var PTRedeemHandler = class {
|
|
|
2373
2334
|
this.domainResolver = config.domainResolver;
|
|
2374
2335
|
this.burnerSignerWallet = config.burnerSignerWallet;
|
|
2375
2336
|
if (!config.supportedTokens) {
|
|
2376
|
-
throw new PTRedeemError(
|
|
2377
|
-
"UNSUPPORTED_POINT_TOKEN",
|
|
2378
|
-
"PTRedeemHandler requires `supportedTokens` (issuer's allow-listed PointToken contracts)."
|
|
2379
|
-
);
|
|
2337
|
+
throw new PTRedeemError("UNSUPPORTED_POINT_TOKEN", "PTRedeemHandler requires `supportedTokens` (issuer's allow-listed PointToken contracts).");
|
|
2380
2338
|
}
|
|
2381
2339
|
this.supportedTokens = config.supportedTokens;
|
|
2382
2340
|
if (this.burnerSignerWallet?.account?.type === "local") {
|
|
@@ -2387,10 +2345,7 @@ var PTRedeemHandler = class {
|
|
|
2387
2345
|
this.now = config.now ?? (() => Date.now());
|
|
2388
2346
|
const maxAllowedSignatureMs = this.redeemLockDurationMs - M11_SAFETY_MARGIN_MS;
|
|
2389
2347
|
if (this.signatureDeadlineSeconds * 1e3 > maxAllowedSignatureMs) {
|
|
2390
|
-
throw new PTRedeemError(
|
|
2391
|
-
"INVALID_AMOUNT",
|
|
2392
|
-
`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).`
|
|
2393
|
-
);
|
|
2348
|
+
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).`);
|
|
2394
2349
|
}
|
|
2395
2350
|
if (config.redemptionService) {
|
|
2396
2351
|
this.redemptionService = config.redemptionService;
|
|
@@ -2398,46 +2353,31 @@ var PTRedeemHandler = class {
|
|
|
2398
2353
|
}
|
|
2399
2354
|
async handle(request) {
|
|
2400
2355
|
if ((0, import_viem8.getAddress)(request.authenticatedAddress) !== (0, import_viem8.getAddress)(request.userAddress)) {
|
|
2401
|
-
throw new PTRedeemError(
|
|
2402
|
-
"UNAUTHORIZED",
|
|
2403
|
-
`userAddress (${request.userAddress}) does not match authenticated session (${request.authenticatedAddress})`
|
|
2404
|
-
);
|
|
2356
|
+
throw new PTRedeemError("UNAUTHORIZED", `userAddress (${request.userAddress}) does not match authenticated session (${request.authenticatedAddress})`);
|
|
2405
2357
|
}
|
|
2406
2358
|
if (request.amount <= 0n) {
|
|
2407
2359
|
throw new PTRedeemError("INVALID_AMOUNT", "redeem amount must be positive");
|
|
2408
2360
|
}
|
|
2409
2361
|
const pointTokenAddress = (0, import_viem8.getAddress)(request.pointTokenAddress);
|
|
2410
2362
|
if (!this.supportedTokens.has(pointTokenAddress)) {
|
|
2411
|
-
throw new PTRedeemError(
|
|
2412
|
-
"UNSUPPORTED_POINT_TOKEN",
|
|
2413
|
-
`redeem: pointTokenAddress ${pointTokenAddress} is not in the issuer's supported-token allowlist. Check IssuerApiHandlers.supportedTokens and PTRedeemHandler.config.supportedTokens point at the same set.`
|
|
2414
|
-
);
|
|
2363
|
+
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.`);
|
|
2415
2364
|
}
|
|
2416
2365
|
if (this.redemptionService) {
|
|
2417
2366
|
let decision;
|
|
2418
2367
|
try {
|
|
2419
|
-
decision = await this.redemptionService.evaluate(
|
|
2420
|
-
request.userAddress,
|
|
2421
|
-
request.amount,
|
|
2422
|
-
pointTokenAddress
|
|
2423
|
-
);
|
|
2368
|
+
decision = await this.redemptionService.evaluate(request.userAddress, request.amount, pointTokenAddress);
|
|
2424
2369
|
} catch (err) {
|
|
2425
2370
|
const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
|
|
2426
2371
|
if (code === "POLICY_PROVIDER_UNAVAILABLE") {
|
|
2427
|
-
throw new PTRedeemError(
|
|
2428
|
-
"REDEMPTION_POLICY_UNAVAILABLE",
|
|
2429
|
-
"Redemption policy temporarily unavailable \u2014 please try again shortly."
|
|
2430
|
-
);
|
|
2372
|
+
throw new PTRedeemError("REDEMPTION_POLICY_UNAVAILABLE", "Redemption policy temporarily unavailable \u2014 please try again shortly.");
|
|
2431
2373
|
}
|
|
2432
2374
|
throw err;
|
|
2433
2375
|
}
|
|
2434
2376
|
if (!decision.allowed) {
|
|
2435
2377
|
const denial = decision.denial;
|
|
2436
|
-
throw new PTRedeemError(
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
{ policyDenialCode: denial.code }
|
|
2440
|
-
);
|
|
2378
|
+
throw new PTRedeemError("REDEMPTION_POLICY_DENIED", `redemption denied: ${denial.message}`, {
|
|
2379
|
+
policyDenialCode: denial.code
|
|
2380
|
+
});
|
|
2441
2381
|
}
|
|
2442
2382
|
}
|
|
2443
2383
|
let burnNonce;
|
|
@@ -2446,13 +2386,12 @@ var PTRedeemHandler = class {
|
|
|
2446
2386
|
address: pointTokenAddress,
|
|
2447
2387
|
abi: import_core7.POINT_TOKEN_ABI,
|
|
2448
2388
|
functionName: "burnRequestNonces",
|
|
2449
|
-
args: [
|
|
2389
|
+
args: [
|
|
2390
|
+
request.userAddress
|
|
2391
|
+
]
|
|
2450
2392
|
});
|
|
2451
2393
|
} catch (err) {
|
|
2452
|
-
throw new PTRedeemError(
|
|
2453
|
-
"NONCE_READ_FAILED",
|
|
2454
|
-
`failed to read burnRequestNonces(${request.userAddress}): ${err instanceof Error ? err.message : String(err)}`
|
|
2455
|
-
);
|
|
2394
|
+
throw new PTRedeemError("NONCE_READ_FAILED", `failed to read burnRequestNonces(${request.userAddress}): ${err instanceof Error ? err.message : String(err)}`);
|
|
2456
2395
|
}
|
|
2457
2396
|
const nonceKey = `${(0, import_viem8.getAddress)(request.userAddress).toLowerCase()}:${pointTokenAddress.toLowerCase()}`;
|
|
2458
2397
|
let userNonces = this.inFlightNonces.get(nonceKey);
|
|
@@ -2461,10 +2400,7 @@ var PTRedeemHandler = class {
|
|
|
2461
2400
|
this.inFlightNonces.set(nonceKey, userNonces);
|
|
2462
2401
|
}
|
|
2463
2402
|
if (userNonces.has(burnNonce)) {
|
|
2464
|
-
throw new PTRedeemError(
|
|
2465
|
-
"NONCE_IN_FLIGHT",
|
|
2466
|
-
`A burn request for nonce ${burnNonce} is already in progress for ${request.userAddress} on ${pointTokenAddress}. Retry after the current request completes.`
|
|
2467
|
-
);
|
|
2403
|
+
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.`);
|
|
2468
2404
|
}
|
|
2469
2405
|
userNonces.add(burnNonce);
|
|
2470
2406
|
try {
|
|
@@ -2478,12 +2414,8 @@ var PTRedeemHandler = class {
|
|
|
2478
2414
|
const referenceMs = this.now();
|
|
2479
2415
|
const projectedLockExpiresAtMs = referenceMs + this.redeemLockDurationMs;
|
|
2480
2416
|
const requestedDeadlineSec = Math.floor(referenceMs / 1e3) + this.signatureDeadlineSeconds;
|
|
2481
|
-
const lockBoundedDeadlineSec = Math.floor(
|
|
2482
|
-
|
|
2483
|
-
);
|
|
2484
|
-
const previewDeadline = BigInt(
|
|
2485
|
-
Math.min(requestedDeadlineSec, lockBoundedDeadlineSec)
|
|
2486
|
-
);
|
|
2417
|
+
const lockBoundedDeadlineSec = Math.floor((projectedLockExpiresAtMs - M11_SAFETY_MARGIN_MS) / 1e3);
|
|
2418
|
+
const previewDeadline = BigInt(Math.min(requestedDeadlineSec, lockBoundedDeadlineSec));
|
|
2487
2419
|
let fee;
|
|
2488
2420
|
if (request.feeAmount !== void 0) {
|
|
2489
2421
|
fee = request.feeAmount > 0n ? request.feeAmount : 0n;
|
|
@@ -2509,21 +2441,11 @@ var PTRedeemHandler = class {
|
|
|
2509
2441
|
}
|
|
2510
2442
|
const feeRecipient = request.feeRecipient ?? (request.chainId !== void 0 ? (0, import_core7.getContractAddresses)(request.chainId).pafiFeeRecipient : (0, import_core7.getContractAddresses)(this.chainId).pafiFeeRecipient);
|
|
2511
2443
|
if (fee > 0n && fee >= request.amount) {
|
|
2512
|
-
throw new PTRedeemError(
|
|
2513
|
-
"INVALID_AMOUNT",
|
|
2514
|
-
`fee (${fee}) must be strictly less than redeem amount (${request.amount})`
|
|
2515
|
-
);
|
|
2444
|
+
throw new PTRedeemError("INVALID_AMOUNT", `fee (${fee}) must be strictly less than redeem amount (${request.amount})`);
|
|
2516
2445
|
}
|
|
2517
|
-
const onChainBalance = await (0, import_core7.getPointTokenBalance)(
|
|
2518
|
-
this.provider,
|
|
2519
|
-
pointTokenAddress,
|
|
2520
|
-
request.userAddress
|
|
2521
|
-
);
|
|
2446
|
+
const onChainBalance = await (0, import_core7.getPointTokenBalance)(this.provider, pointTokenAddress, request.userAddress);
|
|
2522
2447
|
if (onChainBalance < request.amount) {
|
|
2523
|
-
throw new PTRedeemError(
|
|
2524
|
-
"INVALID_AMOUNT",
|
|
2525
|
-
`insufficient on-chain PT balance: have ${onChainBalance}, need ${request.amount}`
|
|
2526
|
-
);
|
|
2448
|
+
throw new PTRedeemError("INVALID_AMOUNT", `insufficient on-chain PT balance: have ${onChainBalance}, need ${request.amount}`);
|
|
2527
2449
|
}
|
|
2528
2450
|
const deadline = previewDeadline;
|
|
2529
2451
|
const domainName = await this.domainResolver.resolve(pointTokenAddress);
|
|
@@ -2545,17 +2467,9 @@ var PTRedeemHandler = class {
|
|
|
2545
2467
|
try {
|
|
2546
2468
|
sponsoredSig = (await (0, import_core7.signBurnRequest)(this.burnerSignerWallet, domain, sponsoredBurnRequest)).serialized;
|
|
2547
2469
|
} catch (err) {
|
|
2548
|
-
throw new PTRedeemError(
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
);
|
|
2552
|
-
}
|
|
2553
|
-
const sponsoredLockId = await this.ledger.reservePendingCredit(
|
|
2554
|
-
request.userAddress,
|
|
2555
|
-
sponsoredBurnAmount,
|
|
2556
|
-
this.redeemLockDurationMs,
|
|
2557
|
-
pointTokenAddress
|
|
2558
|
-
);
|
|
2470
|
+
throw new PTRedeemError("SIGNING_FAILED", `failed to sign sponsored BurnRequest: ${err instanceof Error ? err.message : String(err)}`);
|
|
2471
|
+
}
|
|
2472
|
+
const sponsoredLockId = await this.ledger.reservePendingCredit(request.userAddress, sponsoredBurnAmount, this.redeemLockDurationMs, pointTokenAddress);
|
|
2559
2473
|
try {
|
|
2560
2474
|
const sponsoredUserOp = await this.relayService.prepareBurn({
|
|
2561
2475
|
mode: "burnWithSig",
|
|
@@ -2581,17 +2495,9 @@ var PTRedeemHandler = class {
|
|
|
2581
2495
|
try {
|
|
2582
2496
|
fallbackSig = (await (0, import_core7.signBurnRequest)(this.burnerSignerWallet, domain, fallbackBurnRequest)).serialized;
|
|
2583
2497
|
} catch (err) {
|
|
2584
|
-
throw new PTRedeemError(
|
|
2585
|
-
"SIGNING_FAILED",
|
|
2586
|
-
`failed to sign fallback BurnRequest: ${err instanceof Error ? err.message : String(err)}`
|
|
2587
|
-
);
|
|
2498
|
+
throw new PTRedeemError("SIGNING_FAILED", `failed to sign fallback BurnRequest: ${err instanceof Error ? err.message : String(err)}`);
|
|
2588
2499
|
}
|
|
2589
|
-
const fallbackLockId = await this.ledger.reservePendingCredit(
|
|
2590
|
-
request.userAddress,
|
|
2591
|
-
request.amount,
|
|
2592
|
-
this.redeemLockDurationMs,
|
|
2593
|
-
pointTokenAddress
|
|
2594
|
-
);
|
|
2500
|
+
const fallbackLockId = await this.ledger.reservePendingCredit(request.userAddress, request.amount, this.redeemLockDurationMs, pointTokenAddress);
|
|
2595
2501
|
let fallbackUserOp;
|
|
2596
2502
|
try {
|
|
2597
2503
|
fallbackUserOp = await this.relayService.prepareBurn({
|
|
@@ -2649,15 +2555,11 @@ var PTRedeemHandler = class {
|
|
|
2649
2555
|
var DEFAULT_STATUS_CONFIRMATIONS = 3;
|
|
2650
2556
|
async function isReceiptPastConfirmations(receipt, provider, confirmations, onWarning, handlerName) {
|
|
2651
2557
|
if (!provider) {
|
|
2652
|
-
onWarning?.(
|
|
2653
|
-
`${handlerName}: provider missing \u2014 cannot enforce confirmation depth; deferring receipt fallback to on-chain indexer.`
|
|
2654
|
-
);
|
|
2558
|
+
onWarning?.(`${handlerName}: provider missing \u2014 cannot enforce confirmation depth; deferring receipt fallback to on-chain indexer.`);
|
|
2655
2559
|
return false;
|
|
2656
2560
|
}
|
|
2657
2561
|
if (!receipt.blockNumber) {
|
|
2658
|
-
onWarning?.(
|
|
2659
|
-
`${handlerName}: receipt has no blockNumber \u2014 cannot enforce confirmation depth; deferring to indexer.`
|
|
2660
|
-
);
|
|
2562
|
+
onWarning?.(`${handlerName}: receipt has no blockNumber \u2014 cannot enforce confirmation depth; deferring to indexer.`);
|
|
2661
2563
|
return false;
|
|
2662
2564
|
}
|
|
2663
2565
|
const requiredConfs = BigInt(confirmations ?? DEFAULT_STATUS_CONFIRMATIONS);
|
|
@@ -2665,18 +2567,14 @@ async function isReceiptPastConfirmations(receipt, provider, confirmations, onWa
|
|
|
2665
2567
|
try {
|
|
2666
2568
|
receiptBlock = BigInt(receipt.blockNumber);
|
|
2667
2569
|
} catch {
|
|
2668
|
-
onWarning?.(
|
|
2669
|
-
`${handlerName}: malformed receipt blockNumber (${receipt.blockNumber}) \u2014 deferring to indexer.`
|
|
2670
|
-
);
|
|
2570
|
+
onWarning?.(`${handlerName}: malformed receipt blockNumber (${receipt.blockNumber}) \u2014 deferring to indexer.`);
|
|
2671
2571
|
return false;
|
|
2672
2572
|
}
|
|
2673
2573
|
let head;
|
|
2674
2574
|
try {
|
|
2675
2575
|
head = await provider.getBlockNumber();
|
|
2676
2576
|
} catch (err) {
|
|
2677
|
-
onWarning?.(
|
|
2678
|
-
`${handlerName}: getBlockNumber failed (${err instanceof Error ? err.message : String(err)}) \u2014 deferring to indexer.`
|
|
2679
|
-
);
|
|
2577
|
+
onWarning?.(`${handlerName}: getBlockNumber failed (${err instanceof Error ? err.message : String(err)}) \u2014 deferring to indexer.`);
|
|
2680
2578
|
return false;
|
|
2681
2579
|
}
|
|
2682
2580
|
const depth = head - receiptBlock;
|
|
@@ -2685,7 +2583,11 @@ async function isReceiptPastConfirmations(receipt, provider, confirmations, onWa
|
|
|
2685
2583
|
}
|
|
2686
2584
|
return true;
|
|
2687
2585
|
}
|
|
2688
|
-
|
|
2586
|
+
__name(isReceiptPastConfirmations, "isReceiptPastConfirmations");
|
|
2587
|
+
var LockNotFoundError = class LockNotFoundError2 extends import_core.PafiSdkError {
|
|
2588
|
+
static {
|
|
2589
|
+
__name(this, "LockNotFoundError");
|
|
2590
|
+
}
|
|
2689
2591
|
code = "LOCK_NOT_FOUND";
|
|
2690
2592
|
httpStatus = "not_found";
|
|
2691
2593
|
constructor() {
|
|
@@ -2694,14 +2596,9 @@ var LockNotFoundError = class extends import_core.PafiSdkError {
|
|
|
2694
2596
|
};
|
|
2695
2597
|
async function handleClaimStatus(params) {
|
|
2696
2598
|
if (!params.ledger.getMintLock) {
|
|
2697
|
-
throw new Error(
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
}
|
|
2701
|
-
const lock = await params.ledger.getMintLock(
|
|
2702
|
-
params.lockId,
|
|
2703
|
-
params.userAddress
|
|
2704
|
-
);
|
|
2599
|
+
throw new Error("handleClaimStatus: ledger does not implement `getMintLock` \u2014 implement the optional method on `IPointLedger` or write a custom status handler.");
|
|
2600
|
+
}
|
|
2601
|
+
const lock = await params.ledger.getMintLock(params.lockId, params.userAddress);
|
|
2705
2602
|
if (!lock || lock.userAddress.toLowerCase() !== params.userAddress.toLowerCase()) {
|
|
2706
2603
|
throw new LockNotFoundError();
|
|
2707
2604
|
}
|
|
@@ -2709,17 +2606,9 @@ async function handleClaimStatus(params) {
|
|
|
2709
2606
|
let txHash = lock.txHash ?? null;
|
|
2710
2607
|
if (status === "PENDING" && lock.userOpHash && params.pafiBackendClient) {
|
|
2711
2608
|
try {
|
|
2712
|
-
const receipt = await params.pafiBackendClient.getUserOpReceipt(
|
|
2713
|
-
lock.userOpHash
|
|
2714
|
-
);
|
|
2609
|
+
const receipt = await params.pafiBackendClient.getUserOpReceipt(lock.userOpHash);
|
|
2715
2610
|
if (receipt) {
|
|
2716
|
-
const passesConfirmationDepth = await isReceiptPastConfirmations(
|
|
2717
|
-
receipt,
|
|
2718
|
-
params.provider,
|
|
2719
|
-
params.confirmations,
|
|
2720
|
-
params.onWarning,
|
|
2721
|
-
"handleClaimStatus"
|
|
2722
|
-
);
|
|
2611
|
+
const passesConfirmationDepth = await isReceiptPastConfirmations(receipt, params.provider, params.confirmations, params.onWarning, "handleClaimStatus");
|
|
2723
2612
|
if (!passesConfirmationDepth) {
|
|
2724
2613
|
return {
|
|
2725
2614
|
lockId: lock.lockId,
|
|
@@ -2732,46 +2621,31 @@ async function handleClaimStatus(params) {
|
|
|
2732
2621
|
}
|
|
2733
2622
|
if (receipt.success && receipt.txHash) {
|
|
2734
2623
|
if (!lock.tokenAddress) {
|
|
2735
|
-
params.onWarning?.(
|
|
2736
|
-
`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.`
|
|
2737
|
-
);
|
|
2624
|
+
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.`);
|
|
2738
2625
|
await params.ledger.updateMintStatus(lock.lockId, "MINTED", receipt.txHash).catch((err) => {
|
|
2739
|
-
params.onWarning?.(
|
|
2740
|
-
`handleClaimStatus: ledger updateMintStatus failed for lock ${lock.lockId}: ${err}`
|
|
2741
|
-
);
|
|
2626
|
+
params.onWarning?.(`handleClaimStatus: ledger updateMintStatus failed for lock ${lock.lockId}: ${err}`);
|
|
2742
2627
|
});
|
|
2743
2628
|
status = "MINTED";
|
|
2744
2629
|
txHash = receipt.txHash;
|
|
2745
2630
|
} else {
|
|
2746
2631
|
try {
|
|
2747
|
-
await params.ledger.deductBalance(
|
|
2748
|
-
lock.userAddress,
|
|
2749
|
-
lock.amount,
|
|
2750
|
-
receipt.txHash,
|
|
2751
|
-
lock.tokenAddress
|
|
2752
|
-
);
|
|
2632
|
+
await params.ledger.deductBalance(lock.userAddress, lock.amount, receipt.txHash, lock.tokenAddress);
|
|
2753
2633
|
status = "MINTED";
|
|
2754
2634
|
txHash = receipt.txHash;
|
|
2755
2635
|
} catch (deductErr) {
|
|
2756
|
-
params.onWarning?.(
|
|
2757
|
-
`handleClaimStatus: deductBalance failed for lock ${lock.lockId}: ${deductErr}`
|
|
2758
|
-
);
|
|
2636
|
+
params.onWarning?.(`handleClaimStatus: deductBalance failed for lock ${lock.lockId}: ${deductErr}`);
|
|
2759
2637
|
}
|
|
2760
2638
|
}
|
|
2761
2639
|
} else {
|
|
2762
2640
|
await params.ledger.updateMintStatus(lock.lockId, "FAILED", receipt.txHash).catch((err) => {
|
|
2763
|
-
params.onWarning?.(
|
|
2764
|
-
`handleClaimStatus: ledger updateMintStatus failed for lock ${lock.lockId}: ${err}`
|
|
2765
|
-
);
|
|
2641
|
+
params.onWarning?.(`handleClaimStatus: ledger updateMintStatus failed for lock ${lock.lockId}: ${err}`);
|
|
2766
2642
|
});
|
|
2767
2643
|
status = "FAILED";
|
|
2768
2644
|
txHash = receipt.txHash;
|
|
2769
2645
|
}
|
|
2770
2646
|
}
|
|
2771
2647
|
} catch (err) {
|
|
2772
|
-
params.onWarning?.(
|
|
2773
|
-
`handleClaimStatus: bundler-receipt fallback failed for lock ${lock.lockId}: ${err}`
|
|
2774
|
-
);
|
|
2648
|
+
params.onWarning?.(`handleClaimStatus: bundler-receipt fallback failed for lock ${lock.lockId}: ${err}`);
|
|
2775
2649
|
}
|
|
2776
2650
|
}
|
|
2777
2651
|
return {
|
|
@@ -2783,16 +2657,12 @@ async function handleClaimStatus(params) {
|
|
|
2783
2657
|
expiresAt: new Date(lock.expiresAt).toISOString()
|
|
2784
2658
|
};
|
|
2785
2659
|
}
|
|
2660
|
+
__name(handleClaimStatus, "handleClaimStatus");
|
|
2786
2661
|
async function handleRedeemStatus(params) {
|
|
2787
2662
|
if (!params.ledger.getPendingCredit) {
|
|
2788
|
-
throw new Error(
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
}
|
|
2792
|
-
const credit = await params.ledger.getPendingCredit(
|
|
2793
|
-
params.lockId,
|
|
2794
|
-
params.userAddress
|
|
2795
|
-
);
|
|
2663
|
+
throw new Error("handleRedeemStatus: ledger does not implement `getPendingCredit` \u2014 implement the optional method on `IPointLedger` or write a custom status handler.");
|
|
2664
|
+
}
|
|
2665
|
+
const credit = await params.ledger.getPendingCredit(params.lockId, params.userAddress);
|
|
2796
2666
|
if (!credit || credit.userAddress.toLowerCase() !== params.userAddress.toLowerCase()) {
|
|
2797
2667
|
throw new LockNotFoundError();
|
|
2798
2668
|
}
|
|
@@ -2800,33 +2670,21 @@ async function handleRedeemStatus(params) {
|
|
|
2800
2670
|
let txHash = credit.txHash ?? null;
|
|
2801
2671
|
if (status === "PENDING" && credit.userOpHash && params.pafiBackendClient) {
|
|
2802
2672
|
try {
|
|
2803
|
-
const receipt = await params.pafiBackendClient.getUserOpReceipt(
|
|
2804
|
-
credit.userOpHash
|
|
2805
|
-
);
|
|
2673
|
+
const receipt = await params.pafiBackendClient.getUserOpReceipt(credit.userOpHash);
|
|
2806
2674
|
if (receipt && receipt.success) {
|
|
2807
|
-
const passesConfirmationDepth = await isReceiptPastConfirmations(
|
|
2808
|
-
receipt,
|
|
2809
|
-
params.provider,
|
|
2810
|
-
params.confirmations,
|
|
2811
|
-
params.onWarning,
|
|
2812
|
-
"handleRedeemStatus"
|
|
2813
|
-
);
|
|
2675
|
+
const passesConfirmationDepth = await isReceiptPastConfirmations(receipt, params.provider, params.confirmations, params.onWarning, "handleRedeemStatus");
|
|
2814
2676
|
if (passesConfirmationDepth) {
|
|
2815
2677
|
status = "RESOLVED";
|
|
2816
2678
|
txHash = receipt.txHash;
|
|
2817
2679
|
if (params.ledger.resolveCreditByBurnTx) {
|
|
2818
2680
|
await params.ledger.resolveCreditByBurnTx(credit.lockId, receipt.txHash).catch((err) => {
|
|
2819
|
-
params.onWarning?.(
|
|
2820
|
-
`handleRedeemStatus: resolveCreditByBurnTx failed for lock ${credit.lockId}: ${err}`
|
|
2821
|
-
);
|
|
2681
|
+
params.onWarning?.(`handleRedeemStatus: resolveCreditByBurnTx failed for lock ${credit.lockId}: ${err}`);
|
|
2822
2682
|
});
|
|
2823
2683
|
}
|
|
2824
2684
|
}
|
|
2825
2685
|
}
|
|
2826
2686
|
} catch (err) {
|
|
2827
|
-
params.onWarning?.(
|
|
2828
|
-
`handleRedeemStatus: bundler-receipt fallback failed for lock ${credit.lockId}: ${err}`
|
|
2829
|
-
);
|
|
2687
|
+
params.onWarning?.(`handleRedeemStatus: bundler-receipt fallback failed for lock ${credit.lockId}: ${err}`);
|
|
2830
2688
|
}
|
|
2831
2689
|
}
|
|
2832
2690
|
return {
|
|
@@ -2839,6 +2697,7 @@ async function handleRedeemStatus(params) {
|
|
|
2839
2697
|
resolvedAt: credit.resolvedAt ? new Date(credit.resolvedAt).toISOString() : null
|
|
2840
2698
|
};
|
|
2841
2699
|
}
|
|
2700
|
+
__name(handleRedeemStatus, "handleRedeemStatus");
|
|
2842
2701
|
|
|
2843
2702
|
// src/api/mobileHandlers.ts
|
|
2844
2703
|
var import_viem9 = require("viem");
|
|
@@ -2849,46 +2708,41 @@ var import_core8 = require("@pafi-dev/core");
|
|
|
2849
2708
|
function serializeEntryToJsonRpc(entry, signature, variant = "sponsored") {
|
|
2850
2709
|
if (variant === "fallback") {
|
|
2851
2710
|
if (!entry.fallback) {
|
|
2852
|
-
throw new Error(
|
|
2853
|
-
"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."
|
|
2854
|
-
);
|
|
2711
|
+
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.");
|
|
2855
2712
|
}
|
|
2856
|
-
return (0, import_core8.serializeUserOpToJsonRpc)(
|
|
2857
|
-
{
|
|
2858
|
-
sender: entry.sender,
|
|
2859
|
-
nonce: BigInt(entry.nonce),
|
|
2860
|
-
callData: entry.fallback.callData,
|
|
2861
|
-
callGasLimit: BigInt(entry.fallback.callGasLimit),
|
|
2862
|
-
verificationGasLimit: BigInt(entry.fallback.verificationGasLimit),
|
|
2863
|
-
preVerificationGas: BigInt(entry.fallback.preVerificationGas),
|
|
2864
|
-
maxFeePerGas: BigInt(entry.maxFeePerGas),
|
|
2865
|
-
maxPriorityFeePerGas: BigInt(entry.maxPriorityFeePerGas)
|
|
2866
|
-
// intentionally no paymaster — user pays ETH gas
|
|
2867
|
-
},
|
|
2868
|
-
signature
|
|
2869
|
-
);
|
|
2870
|
-
}
|
|
2871
|
-
return (0, import_core8.serializeUserOpToJsonRpc)(
|
|
2872
|
-
{
|
|
2713
|
+
return (0, import_core8.serializeUserOpToJsonRpc)({
|
|
2873
2714
|
sender: entry.sender,
|
|
2874
2715
|
nonce: BigInt(entry.nonce),
|
|
2875
|
-
callData: entry.callData,
|
|
2876
|
-
callGasLimit: BigInt(entry.callGasLimit),
|
|
2877
|
-
verificationGasLimit: BigInt(entry.verificationGasLimit),
|
|
2878
|
-
preVerificationGas: BigInt(entry.preVerificationGas),
|
|
2716
|
+
callData: entry.fallback.callData,
|
|
2717
|
+
callGasLimit: BigInt(entry.fallback.callGasLimit),
|
|
2718
|
+
verificationGasLimit: BigInt(entry.fallback.verificationGasLimit),
|
|
2719
|
+
preVerificationGas: BigInt(entry.fallback.preVerificationGas),
|
|
2879
2720
|
maxFeePerGas: BigInt(entry.maxFeePerGas),
|
|
2880
|
-
maxPriorityFeePerGas: BigInt(entry.maxPriorityFeePerGas)
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2721
|
+
maxPriorityFeePerGas: BigInt(entry.maxPriorityFeePerGas)
|
|
2722
|
+
}, signature);
|
|
2723
|
+
}
|
|
2724
|
+
return (0, import_core8.serializeUserOpToJsonRpc)({
|
|
2725
|
+
sender: entry.sender,
|
|
2726
|
+
nonce: BigInt(entry.nonce),
|
|
2727
|
+
callData: entry.callData,
|
|
2728
|
+
callGasLimit: BigInt(entry.callGasLimit),
|
|
2729
|
+
verificationGasLimit: BigInt(entry.verificationGasLimit),
|
|
2730
|
+
preVerificationGas: BigInt(entry.preVerificationGas),
|
|
2731
|
+
maxFeePerGas: BigInt(entry.maxFeePerGas),
|
|
2732
|
+
maxPriorityFeePerGas: BigInt(entry.maxPriorityFeePerGas),
|
|
2733
|
+
paymaster: entry.paymaster,
|
|
2734
|
+
paymasterVerificationGasLimit: entry.paymasterVerificationGasLimit != null ? BigInt(entry.paymasterVerificationGasLimit) : void 0,
|
|
2735
|
+
paymasterPostOpGasLimit: entry.paymasterPostOpGasLimit != null ? BigInt(entry.paymasterPostOpGasLimit) : void 0,
|
|
2736
|
+
paymasterData: entry.paymasterData
|
|
2737
|
+
}, signature);
|
|
2888
2738
|
}
|
|
2739
|
+
__name(serializeEntryToJsonRpc, "serializeEntryToJsonRpc");
|
|
2889
2740
|
|
|
2890
2741
|
// src/userop-store/memoryStore.ts
|
|
2891
2742
|
var MemoryPendingUserOpStore = class {
|
|
2743
|
+
static {
|
|
2744
|
+
__name(this, "MemoryPendingUserOpStore");
|
|
2745
|
+
}
|
|
2892
2746
|
entries = /* @__PURE__ */ new Map();
|
|
2893
2747
|
now;
|
|
2894
2748
|
constructor(now = () => Date.now()) {
|
|
@@ -2927,14 +2781,13 @@ function serializeUserOpTypedData(td) {
|
|
|
2927
2781
|
initCode: td.message.initCode,
|
|
2928
2782
|
callData: td.message.callData,
|
|
2929
2783
|
accountGasLimits: td.message.accountGasLimits,
|
|
2930
|
-
preVerificationGas: `0x${td.message.preVerificationGas.toString(
|
|
2931
|
-
16
|
|
2932
|
-
)}`,
|
|
2784
|
+
preVerificationGas: `0x${td.message.preVerificationGas.toString(16)}`,
|
|
2933
2785
|
gasFees: td.message.gasFees,
|
|
2934
2786
|
paymasterAndData: td.message.paymasterAndData
|
|
2935
2787
|
}
|
|
2936
2788
|
};
|
|
2937
2789
|
}
|
|
2790
|
+
__name(serializeUserOpTypedData, "serializeUserOpTypedData");
|
|
2938
2791
|
function mergePaymasterFields(userOp, paymasterFields) {
|
|
2939
2792
|
if (!paymasterFields) return userOp;
|
|
2940
2793
|
const merged = {
|
|
@@ -2945,36 +2798,27 @@ function mergePaymasterFields(userOp, paymasterFields) {
|
|
|
2945
2798
|
}
|
|
2946
2799
|
return merged;
|
|
2947
2800
|
}
|
|
2801
|
+
__name(mergePaymasterFields, "mergePaymasterFields");
|
|
2948
2802
|
function applyPaymasterGasEstimates(partialUserOp, paymasterFields, chainId) {
|
|
2949
|
-
const userOp = mergePaymasterFields(
|
|
2950
|
-
partialUserOp,
|
|
2951
|
-
paymasterFields
|
|
2952
|
-
);
|
|
2803
|
+
const userOp = mergePaymasterFields(partialUserOp, paymasterFields);
|
|
2953
2804
|
return {
|
|
2954
2805
|
userOp,
|
|
2955
2806
|
userOpHash: (0, import_core9.computeUserOpHash)(userOp, chainId),
|
|
2956
2807
|
typedData: serializeUserOpTypedData((0, import_core9.buildUserOpTypedData)(userOp, chainId))
|
|
2957
2808
|
};
|
|
2958
2809
|
}
|
|
2810
|
+
__name(applyPaymasterGasEstimates, "applyPaymasterGasEstimates");
|
|
2959
2811
|
async function prepareMobileUserOp(params) {
|
|
2960
|
-
const sponsored = applyPaymasterGasEstimates(
|
|
2961
|
-
params.partialUserOp,
|
|
2962
|
-
params.paymasterFields,
|
|
2963
|
-
params.chainId
|
|
2964
|
-
);
|
|
2812
|
+
const sponsored = applyPaymasterGasEstimates(params.partialUserOp, params.paymasterFields, params.chainId);
|
|
2965
2813
|
const { userOp, userOpHash } = sponsored;
|
|
2966
2814
|
let fallback;
|
|
2967
2815
|
let fallbackEntry;
|
|
2968
2816
|
if (params.partialUserOpFallback) {
|
|
2969
|
-
fallback = applyPaymasterGasEstimates(
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
},
|
|
2975
|
-
void 0,
|
|
2976
|
-
params.chainId
|
|
2977
|
-
);
|
|
2817
|
+
fallback = applyPaymasterGasEstimates({
|
|
2818
|
+
...params.partialUserOpFallback,
|
|
2819
|
+
maxFeePerGas: userOp.maxFeePerGas,
|
|
2820
|
+
maxPriorityFeePerGas: userOp.maxPriorityFeePerGas
|
|
2821
|
+
}, void 0, params.chainId);
|
|
2978
2822
|
fallbackEntry = {
|
|
2979
2823
|
callData: fallback.userOp.callData,
|
|
2980
2824
|
callGasLimit: fallback.userOp.callGasLimit.toString(),
|
|
@@ -3011,23 +2855,24 @@ async function prepareMobileUserOp(params) {
|
|
|
3011
2855
|
entry
|
|
3012
2856
|
};
|
|
3013
2857
|
}
|
|
2858
|
+
__name(prepareMobileUserOp, "prepareMobileUserOp");
|
|
3014
2859
|
|
|
3015
2860
|
// src/pafi-backend/types.ts
|
|
3016
2861
|
var PafiBackendError = class extends Error {
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
this.code = code;
|
|
3020
|
-
this.httpStatus = httpStatus;
|
|
3021
|
-
this.details = details;
|
|
3022
|
-
this.name = "PafiBackendError";
|
|
3023
|
-
if (opts?.retryAfter !== void 0) this.retryAfter = opts.retryAfter;
|
|
3024
|
-
if (opts?.safeToRetry !== void 0) this.serverSafeToRetry = opts.safeToRetry;
|
|
2862
|
+
static {
|
|
2863
|
+
__name(this, "PafiBackendError");
|
|
3025
2864
|
}
|
|
3026
2865
|
code;
|
|
3027
2866
|
httpStatus;
|
|
3028
2867
|
details;
|
|
3029
2868
|
retryAfter;
|
|
3030
2869
|
serverSafeToRetry;
|
|
2870
|
+
constructor(code, message, httpStatus, details, opts) {
|
|
2871
|
+
super(message), this.code = code, this.httpStatus = httpStatus, this.details = details;
|
|
2872
|
+
this.name = "PafiBackendError";
|
|
2873
|
+
if (opts?.retryAfter !== void 0) this.retryAfter = opts.retryAfter;
|
|
2874
|
+
if (opts?.safeToRetry !== void 0) this.serverSafeToRetry = opts.safeToRetry;
|
|
2875
|
+
}
|
|
3031
2876
|
get safeToRetry() {
|
|
3032
2877
|
if (this.serverSafeToRetry !== void 0) return this.serverSafeToRetry;
|
|
3033
2878
|
switch (this.code) {
|
|
@@ -3049,15 +2894,19 @@ var PafiBackendError = class extends Error {
|
|
|
3049
2894
|
|
|
3050
2895
|
// src/pafi-backend/helpers.ts
|
|
3051
2896
|
var BundlerNotConfiguredError = class extends import_core.PafiSdkError {
|
|
2897
|
+
static {
|
|
2898
|
+
__name(this, "BundlerNotConfiguredError");
|
|
2899
|
+
}
|
|
3052
2900
|
code = "BUNDLER_NOT_CONFIGURED";
|
|
3053
2901
|
httpStatus = "service_unavailable";
|
|
3054
2902
|
constructor() {
|
|
3055
|
-
super(
|
|
3056
|
-
"PAFI backend client not configured \u2014 set PAFI_BACKEND_URL, PAFI_ISSUER_ID, PAFI_API_KEY to enable mobile submit."
|
|
3057
|
-
);
|
|
2903
|
+
super("PAFI backend client not configured \u2014 set PAFI_BACKEND_URL, PAFI_ISSUER_ID, PAFI_API_KEY to enable mobile submit.");
|
|
3058
2904
|
}
|
|
3059
2905
|
};
|
|
3060
2906
|
var BundlerRejectedError = class extends import_core.PafiSdkError {
|
|
2907
|
+
static {
|
|
2908
|
+
__name(this, "BundlerRejectedError");
|
|
2909
|
+
}
|
|
3061
2910
|
code = "BUNDLER_REJECTED";
|
|
3062
2911
|
httpStatus = "unprocessable";
|
|
3063
2912
|
cause;
|
|
@@ -3079,7 +2928,9 @@ async function requestPaymaster(params) {
|
|
|
3079
2928
|
function: fn,
|
|
3080
2929
|
pointToken: params.pointTokenAddress
|
|
3081
2930
|
},
|
|
3082
|
-
...params.eip7702Auth ? {
|
|
2931
|
+
...params.eip7702Auth ? {
|
|
2932
|
+
eip7702Auth: params.eip7702Auth
|
|
2933
|
+
} : {}
|
|
3083
2934
|
});
|
|
3084
2935
|
} catch (err) {
|
|
3085
2936
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -3090,6 +2941,7 @@ async function requestPaymaster(params) {
|
|
|
3090
2941
|
throw err;
|
|
3091
2942
|
}
|
|
3092
2943
|
}
|
|
2944
|
+
__name(requestPaymaster, "requestPaymaster");
|
|
3093
2945
|
function isTransientPaymasterError(code) {
|
|
3094
2946
|
switch (code) {
|
|
3095
2947
|
case "NETWORK_ERROR":
|
|
@@ -3102,6 +2954,7 @@ function isTransientPaymasterError(code) {
|
|
|
3102
2954
|
return false;
|
|
3103
2955
|
}
|
|
3104
2956
|
}
|
|
2957
|
+
__name(isTransientPaymasterError, "isTransientPaymasterError");
|
|
3105
2958
|
function defaultFunctionForScenario(scenario) {
|
|
3106
2959
|
switch (scenario) {
|
|
3107
2960
|
case "mint":
|
|
@@ -3116,6 +2969,7 @@ function defaultFunctionForScenario(scenario) {
|
|
|
3116
2969
|
return scenario;
|
|
3117
2970
|
}
|
|
3118
2971
|
}
|
|
2972
|
+
__name(defaultFunctionForScenario, "defaultFunctionForScenario");
|
|
3119
2973
|
async function relayUserOp(params) {
|
|
3120
2974
|
if (!params.client) {
|
|
3121
2975
|
throw new BundlerNotConfiguredError();
|
|
@@ -3126,36 +2980,43 @@ async function relayUserOp(params) {
|
|
|
3126
2980
|
entryPoint: params.entryPoint,
|
|
3127
2981
|
eip7702Auth: params.eip7702Auth
|
|
3128
2982
|
});
|
|
3129
|
-
return {
|
|
2983
|
+
return {
|
|
2984
|
+
userOpHash: result.userOpHash
|
|
2985
|
+
};
|
|
3130
2986
|
} catch (err) {
|
|
3131
2987
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3132
2988
|
throw new BundlerRejectedError(msg, err);
|
|
3133
2989
|
}
|
|
3134
2990
|
}
|
|
2991
|
+
__name(relayUserOp, "relayUserOp");
|
|
3135
2992
|
|
|
3136
2993
|
// src/api/mobileHandlers.ts
|
|
3137
2994
|
var PendingUserOpNotFoundError = class extends import_core.PafiSdkError {
|
|
2995
|
+
static {
|
|
2996
|
+
__name(this, "PendingUserOpNotFoundError");
|
|
2997
|
+
}
|
|
3138
2998
|
code = "PENDING_USEROP_NOT_FOUND";
|
|
3139
2999
|
httpStatus = "not_found";
|
|
3140
3000
|
constructor(lockId) {
|
|
3141
|
-
super(
|
|
3142
|
-
`No pending UserOp found for lockId ${lockId} \u2014 it may have expired or already been submitted.`
|
|
3143
|
-
);
|
|
3001
|
+
super(`No pending UserOp found for lockId ${lockId} \u2014 it may have expired or already been submitted.`);
|
|
3144
3002
|
}
|
|
3145
3003
|
};
|
|
3146
3004
|
var PendingUserOpForbiddenError = class extends import_core.PafiSdkError {
|
|
3005
|
+
static {
|
|
3006
|
+
__name(this, "PendingUserOpForbiddenError");
|
|
3007
|
+
}
|
|
3147
3008
|
code = "PENDING_USEROP_FORBIDDEN";
|
|
3148
3009
|
httpStatus = "forbidden";
|
|
3149
3010
|
constructor(lockId) {
|
|
3150
|
-
super(
|
|
3151
|
-
`Pending UserOp ${lockId} does not belong to the authenticated user.`
|
|
3152
|
-
);
|
|
3011
|
+
super(`Pending UserOp ${lockId} does not belong to the authenticated user.`);
|
|
3153
3012
|
}
|
|
3154
3013
|
};
|
|
3155
3014
|
async function handleMobilePrepare(params) {
|
|
3156
3015
|
const [fees, userCode] = await Promise.all([
|
|
3157
3016
|
params.provider.estimateFeesPerGas(),
|
|
3158
|
-
params.provider.getCode({
|
|
3017
|
+
params.provider.getCode({
|
|
3018
|
+
address: params.userAddress
|
|
3019
|
+
})
|
|
3159
3020
|
]);
|
|
3160
3021
|
const needsDelegation = !params.eip7702Auth && (0, import_core10.parseEip7702DelegatedAddress)(userCode) === null;
|
|
3161
3022
|
const sponsoredOp = {
|
|
@@ -3189,6 +3050,7 @@ async function handleMobilePrepare(params) {
|
|
|
3189
3050
|
needsDelegation
|
|
3190
3051
|
};
|
|
3191
3052
|
}
|
|
3053
|
+
__name(handleMobilePrepare, "handleMobilePrepare");
|
|
3192
3054
|
async function handleMobileSubmit(params) {
|
|
3193
3055
|
const entry = await params.store.get(params.lockId);
|
|
3194
3056
|
if (!entry) {
|
|
@@ -3208,8 +3070,11 @@ async function handleMobileSubmit(params) {
|
|
|
3208
3070
|
const targetLockId = variant === "fallback" && entry.fallback?.lockId ? entry.fallback.lockId : params.lockId;
|
|
3209
3071
|
await params.bindUserOpHash(targetLockId, result.userOpHash);
|
|
3210
3072
|
await params.store.delete(params.lockId);
|
|
3211
|
-
return {
|
|
3073
|
+
return {
|
|
3074
|
+
userOpHash: result.userOpHash
|
|
3075
|
+
};
|
|
3212
3076
|
}
|
|
3077
|
+
__name(handleMobileSubmit, "handleMobileSubmit");
|
|
3213
3078
|
|
|
3214
3079
|
// src/api/handlers/ptClaimHandler.ts
|
|
3215
3080
|
var import_viem10 = require("viem");
|
|
@@ -3217,6 +3082,9 @@ var import_core11 = require("@pafi-dev/core");
|
|
|
3217
3082
|
|
|
3218
3083
|
// src/issuer-state/types.ts
|
|
3219
3084
|
var IssuerStateError = class extends import_core.PafiSdkError {
|
|
3085
|
+
static {
|
|
3086
|
+
__name(this, "IssuerStateError");
|
|
3087
|
+
}
|
|
3220
3088
|
httpStatus = "unprocessable";
|
|
3221
3089
|
code;
|
|
3222
3090
|
details;
|
|
@@ -3231,6 +3099,9 @@ var IssuerStateError = class extends import_core.PafiSdkError {
|
|
|
3231
3099
|
|
|
3232
3100
|
// src/api/handlers/ptClaimHandler.ts
|
|
3233
3101
|
var PTClaimError = class extends import_core.PafiSdkError {
|
|
3102
|
+
static {
|
|
3103
|
+
__name(this, "PTClaimError");
|
|
3104
|
+
}
|
|
3234
3105
|
httpStatus = "unprocessable";
|
|
3235
3106
|
code;
|
|
3236
3107
|
details;
|
|
@@ -3245,32 +3116,29 @@ function isNoWrapper2(address) {
|
|
|
3245
3116
|
const lower = address.toLowerCase();
|
|
3246
3117
|
return lower === "0x0000000000000000000000000000000000000000" || lower === "0x000000000000000000000000000000000000dead";
|
|
3247
3118
|
}
|
|
3119
|
+
__name(isNoWrapper2, "isNoWrapper");
|
|
3248
3120
|
var DEFAULT_LOCK_MS = 15 * 60 * 1e3;
|
|
3249
3121
|
var M11_SAFETY_MARGIN_MS2 = 30 * 1e3;
|
|
3250
3122
|
var DEFAULT_SIG_DEADLINE_SEC2 = (DEFAULT_LOCK_MS - M11_SAFETY_MARGIN_MS2) / 1e3;
|
|
3251
3123
|
var PTClaimHandler = class {
|
|
3124
|
+
static {
|
|
3125
|
+
__name(this, "PTClaimHandler");
|
|
3126
|
+
}
|
|
3252
3127
|
cfg;
|
|
3253
3128
|
inFlightNonces = /* @__PURE__ */ new Map();
|
|
3254
3129
|
constructor(config) {
|
|
3255
3130
|
if (!config.supportedTokens) {
|
|
3256
|
-
throw new PTClaimError(
|
|
3257
|
-
"UNSUPPORTED_POINT_TOKEN",
|
|
3258
|
-
"PTClaimHandler requires `supportedTokens` (issuer's allow-listed PointToken contracts)."
|
|
3259
|
-
);
|
|
3131
|
+
throw new PTClaimError("UNSUPPORTED_POINT_TOKEN", "PTClaimHandler requires `supportedTokens` (issuer's allow-listed PointToken contracts).");
|
|
3260
3132
|
}
|
|
3261
3133
|
const lockDurationMs = config.lockDurationMs ?? DEFAULT_LOCK_MS;
|
|
3262
3134
|
const signatureDeadlineSeconds = config.signatureDeadlineSeconds ?? DEFAULT_SIG_DEADLINE_SEC2;
|
|
3263
3135
|
const maxAllowedSignatureMs = lockDurationMs - M11_SAFETY_MARGIN_MS2;
|
|
3264
3136
|
if (signatureDeadlineSeconds * 1e3 > maxAllowedSignatureMs) {
|
|
3265
|
-
throw new PTClaimError(
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
signatureDeadlineSeconds,
|
|
3271
|
-
maxAllowedSignatureSec: maxAllowedSignatureMs / 1e3
|
|
3272
|
-
}
|
|
3273
|
-
);
|
|
3137
|
+
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).`, {
|
|
3138
|
+
lockDurationMs,
|
|
3139
|
+
signatureDeadlineSeconds,
|
|
3140
|
+
maxAllowedSignatureSec: maxAllowedSignatureMs / 1e3
|
|
3141
|
+
});
|
|
3274
3142
|
}
|
|
3275
3143
|
this.cfg = {
|
|
3276
3144
|
...config,
|
|
@@ -3281,34 +3149,23 @@ var PTClaimHandler = class {
|
|
|
3281
3149
|
}
|
|
3282
3150
|
async handle(request) {
|
|
3283
3151
|
if ((0, import_viem10.getAddress)(request.authenticatedAddress) !== (0, import_viem10.getAddress)(request.userAddress)) {
|
|
3284
|
-
throw new PTClaimError(
|
|
3285
|
-
"VALIDATION_FAILED",
|
|
3286
|
-
`userAddress (${request.userAddress}) does not match authenticated session (${request.authenticatedAddress})`
|
|
3287
|
-
);
|
|
3152
|
+
throw new PTClaimError("VALIDATION_FAILED", `userAddress (${request.userAddress}) does not match authenticated session (${request.authenticatedAddress})`);
|
|
3288
3153
|
}
|
|
3289
3154
|
if (request.amount <= 0n) {
|
|
3290
3155
|
throw new PTClaimError("INVALID_AMOUNT", "claim amount must be positive");
|
|
3291
3156
|
}
|
|
3292
3157
|
const pointTokenAddress = (0, import_viem10.getAddress)(request.pointTokenAddress);
|
|
3293
3158
|
if (!this.cfg.supportedTokens.has(pointTokenAddress)) {
|
|
3294
|
-
throw new PTClaimError(
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
{ requested: pointTokenAddress }
|
|
3298
|
-
);
|
|
3159
|
+
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.`, {
|
|
3160
|
+
requested: pointTokenAddress
|
|
3161
|
+
});
|
|
3299
3162
|
}
|
|
3300
3163
|
if (this.cfg.issuerStateValidator) {
|
|
3301
3164
|
try {
|
|
3302
|
-
await this.cfg.issuerStateValidator.preValidateMint(
|
|
3303
|
-
request.pointTokenAddress,
|
|
3304
|
-
request.amount
|
|
3305
|
-
);
|
|
3165
|
+
await this.cfg.issuerStateValidator.preValidateMint(request.pointTokenAddress, request.amount);
|
|
3306
3166
|
} catch (err) {
|
|
3307
3167
|
if (err instanceof IssuerStateError) throw err;
|
|
3308
|
-
throw new PTClaimError(
|
|
3309
|
-
"VALIDATION_FAILED",
|
|
3310
|
-
`issuer-state pre-validate failed: ${err instanceof Error ? err.message : String(err)}`
|
|
3311
|
-
);
|
|
3168
|
+
throw new PTClaimError("VALIDATION_FAILED", `issuer-state pre-validate failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
3312
3169
|
}
|
|
3313
3170
|
}
|
|
3314
3171
|
const chainAddresses = (0, import_core11.getContractAddresses)(request.chainId);
|
|
@@ -3319,13 +3176,12 @@ var PTClaimHandler = class {
|
|
|
3319
3176
|
address: request.pointTokenAddress,
|
|
3320
3177
|
abi: import_core11.POINT_TOKEN_ABI,
|
|
3321
3178
|
functionName: "mintRequestNonces",
|
|
3322
|
-
args: [
|
|
3179
|
+
args: [
|
|
3180
|
+
request.userAddress
|
|
3181
|
+
]
|
|
3323
3182
|
});
|
|
3324
3183
|
} catch (err) {
|
|
3325
|
-
throw new PTClaimError(
|
|
3326
|
-
"NONCE_READ_FAILED",
|
|
3327
|
-
`failed to read mintRequestNonces(${request.userAddress}): ${err instanceof Error ? err.message : String(err)}`
|
|
3328
|
-
);
|
|
3184
|
+
throw new PTClaimError("NONCE_READ_FAILED", `failed to read mintRequestNonces(${request.userAddress}): ${err instanceof Error ? err.message : String(err)}`);
|
|
3329
3185
|
}
|
|
3330
3186
|
const nonceKey = `${(0, import_viem10.getAddress)(request.userAddress).toLowerCase()}:${request.pointTokenAddress.toLowerCase()}`;
|
|
3331
3187
|
let userNonces = this.inFlightNonces.get(nonceKey);
|
|
@@ -3334,11 +3190,11 @@ var PTClaimHandler = class {
|
|
|
3334
3190
|
this.inFlightNonces.set(nonceKey, userNonces);
|
|
3335
3191
|
}
|
|
3336
3192
|
if (userNonces.has(mintRequestNonce)) {
|
|
3337
|
-
throw new PTClaimError(
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
);
|
|
3193
|
+
throw new PTClaimError("NONCE_IN_FLIGHT", `concurrent claim for nonce ${mintRequestNonce} in progress; retry after the prior request completes`, {
|
|
3194
|
+
userAddress: request.userAddress,
|
|
3195
|
+
pointToken: request.pointTokenAddress,
|
|
3196
|
+
nonce: mintRequestNonce.toString()
|
|
3197
|
+
});
|
|
3342
3198
|
}
|
|
3343
3199
|
userNonces.add(mintRequestNonce);
|
|
3344
3200
|
const wrapperOverride = this.cfg.mintFeeWrapperAddress;
|
|
@@ -3347,20 +3203,11 @@ var PTClaimHandler = class {
|
|
|
3347
3203
|
try {
|
|
3348
3204
|
const lockCreatedAtMs = this.cfg.now();
|
|
3349
3205
|
const lockExpiresAtMs = lockCreatedAtMs + this.cfg.lockDurationMs;
|
|
3350
|
-
const lockId = await this.cfg.ledger.lockForMinting(
|
|
3351
|
-
request.userAddress,
|
|
3352
|
-
request.amount,
|
|
3353
|
-
this.cfg.lockDurationMs,
|
|
3354
|
-
request.pointTokenAddress
|
|
3355
|
-
);
|
|
3206
|
+
const lockId = await this.cfg.ledger.lockForMinting(request.userAddress, request.amount, this.cfg.lockDurationMs, request.pointTokenAddress);
|
|
3356
3207
|
try {
|
|
3357
3208
|
const requestedDeadlineSec = Math.floor(lockCreatedAtMs / 1e3) + this.cfg.signatureDeadlineSeconds;
|
|
3358
|
-
const lockBoundedDeadlineSec = Math.floor(
|
|
3359
|
-
|
|
3360
|
-
);
|
|
3361
|
-
const signatureDeadline = BigInt(
|
|
3362
|
-
Math.min(requestedDeadlineSec, lockBoundedDeadlineSec)
|
|
3363
|
-
);
|
|
3209
|
+
const lockBoundedDeadlineSec = Math.floor((lockExpiresAtMs - M11_SAFETY_MARGIN_MS2) / 1e3);
|
|
3210
|
+
const signatureDeadline = BigInt(Math.min(requestedDeadlineSec, lockBoundedDeadlineSec));
|
|
3364
3211
|
const previewUserOp = this.cfg.relayService.previewMintUserOp({
|
|
3365
3212
|
userAddress: request.userAddress,
|
|
3366
3213
|
aaNonce: request.aaNonce,
|
|
@@ -3379,15 +3226,12 @@ var PTClaimHandler = class {
|
|
|
3379
3226
|
}
|
|
3380
3227
|
}) : 0n;
|
|
3381
3228
|
if (feeAmount > 0n && feeAmount >= request.amount) {
|
|
3382
|
-
throw new PTClaimError(
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
);
|
|
3229
|
+
throw new PTClaimError("INVALID_AMOUNT", `fee (${feeAmount}) must be strictly less than claim amount (${request.amount})`, {
|
|
3230
|
+
feeAmount: feeAmount.toString(),
|
|
3231
|
+
amount: request.amount.toString()
|
|
3232
|
+
});
|
|
3387
3233
|
}
|
|
3388
|
-
const domainName = await this.cfg.domainResolver.resolve(
|
|
3389
|
-
request.pointTokenAddress
|
|
3390
|
-
);
|
|
3234
|
+
const domainName = await this.cfg.domainResolver.resolve(request.pointTokenAddress);
|
|
3391
3235
|
const domain = {
|
|
3392
3236
|
name: domainName,
|
|
3393
3237
|
chainId: request.chainId,
|
|
@@ -3417,10 +3261,7 @@ var PTClaimHandler = class {
|
|
|
3417
3261
|
feeAmount
|
|
3418
3262
|
});
|
|
3419
3263
|
} catch (err) {
|
|
3420
|
-
throw new PTClaimError(
|
|
3421
|
-
"BUILD_FAILED",
|
|
3422
|
-
`prepareMint failed: ${err instanceof Error ? err.message : String(err)}`
|
|
3423
|
-
);
|
|
3264
|
+
throw new PTClaimError("BUILD_FAILED", `prepareMint failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
3424
3265
|
}
|
|
3425
3266
|
let fallback;
|
|
3426
3267
|
if (feeAmount > 0n) {
|
|
@@ -3439,10 +3280,7 @@ var PTClaimHandler = class {
|
|
|
3439
3280
|
mintFeeWrapperAddress: resolvedWrapper
|
|
3440
3281
|
});
|
|
3441
3282
|
} catch (err) {
|
|
3442
|
-
throw new PTClaimError(
|
|
3443
|
-
"BUILD_FAILED",
|
|
3444
|
-
`prepareMint (fallback) failed: ${err instanceof Error ? err.message : String(err)}`
|
|
3445
|
-
);
|
|
3283
|
+
throw new PTClaimError("BUILD_FAILED", `prepareMint (fallback) failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
3446
3284
|
}
|
|
3447
3285
|
}
|
|
3448
3286
|
const calls = (0, import_core11.decodeBatchExecuteCalls)(userOp.callData);
|
|
@@ -3472,6 +3310,9 @@ var PTClaimHandler = class {
|
|
|
3472
3310
|
// src/api/handlers/perpDepositHandler.ts
|
|
3473
3311
|
var import_core12 = require("@pafi-dev/core");
|
|
3474
3312
|
var PerpDepositError = class extends import_core.PafiSdkError {
|
|
3313
|
+
static {
|
|
3314
|
+
__name(this, "PerpDepositError");
|
|
3315
|
+
}
|
|
3475
3316
|
httpStatus = "unprocessable";
|
|
3476
3317
|
code;
|
|
3477
3318
|
safeToRetry;
|
|
@@ -3483,6 +3324,9 @@ var PerpDepositError = class extends import_core.PafiSdkError {
|
|
|
3483
3324
|
};
|
|
3484
3325
|
var DEFAULT_MAX_FEE_PREMIUM_BPS = 5e3;
|
|
3485
3326
|
var PerpDepositHandler = class {
|
|
3327
|
+
static {
|
|
3328
|
+
__name(this, "PerpDepositHandler");
|
|
3329
|
+
}
|
|
3486
3330
|
cfg;
|
|
3487
3331
|
constructor(config) {
|
|
3488
3332
|
this.cfg = {
|
|
@@ -3498,10 +3342,7 @@ var PerpDepositHandler = class {
|
|
|
3498
3342
|
const tokenHash = import_core12.TOKEN_HASHES.USDC;
|
|
3499
3343
|
const vault = import_core12.ORDERLY_VAULT_ADDRESSES[request.chainId];
|
|
3500
3344
|
if (!vault) {
|
|
3501
|
-
throw new PerpDepositError(
|
|
3502
|
-
"PERP_DEPOSIT_UNAVAILABLE",
|
|
3503
|
-
`no Orderly Vault for chainId ${request.chainId}`
|
|
3504
|
-
);
|
|
3345
|
+
throw new PerpDepositError("PERP_DEPOSIT_UNAVAILABLE", `no Orderly Vault for chainId ${request.chainId}`);
|
|
3505
3346
|
}
|
|
3506
3347
|
const { orderlyRelay: relayAddress, pafiFeeRecipient } = (0, import_core12.getContractAddresses)(request.chainId);
|
|
3507
3348
|
const [usdcAddress, brokerAllowed] = await Promise.all([
|
|
@@ -3509,20 +3350,21 @@ var PerpDepositHandler = class {
|
|
|
3509
3350
|
address: vault,
|
|
3510
3351
|
abi: import_core12.ORDERLY_VAULT_ABI,
|
|
3511
3352
|
functionName: "getAllowedToken",
|
|
3512
|
-
args: [
|
|
3353
|
+
args: [
|
|
3354
|
+
tokenHash
|
|
3355
|
+
]
|
|
3513
3356
|
}),
|
|
3514
3357
|
this.cfg.provider.readContract({
|
|
3515
3358
|
address: vault,
|
|
3516
3359
|
abi: import_core12.ORDERLY_VAULT_ABI,
|
|
3517
3360
|
functionName: "getAllowedBroker",
|
|
3518
|
-
args: [
|
|
3361
|
+
args: [
|
|
3362
|
+
brokerHash
|
|
3363
|
+
]
|
|
3519
3364
|
})
|
|
3520
3365
|
]);
|
|
3521
3366
|
if (!brokerAllowed) {
|
|
3522
|
-
throw new PerpDepositError(
|
|
3523
|
-
"BROKER_NOT_WHITELISTED",
|
|
3524
|
-
`broker "${request.brokerId}" is not whitelisted on Orderly Vault`
|
|
3525
|
-
);
|
|
3367
|
+
throw new PerpDepositError("BROKER_NOT_WHITELISTED", `broker "${request.brokerId}" is not whitelisted on Orderly Vault`);
|
|
3526
3368
|
}
|
|
3527
3369
|
const accountId = (0, import_core12.computeAccountId)(request.userAddress, brokerHash);
|
|
3528
3370
|
const requestForQuote = {
|
|
@@ -3537,7 +3379,9 @@ var PerpDepositHandler = class {
|
|
|
3537
3379
|
address: relayAddress,
|
|
3538
3380
|
abi: import_core12.ORDERLY_RELAY_ABI,
|
|
3539
3381
|
functionName: "quoteTokenFee",
|
|
3540
|
-
args: [
|
|
3382
|
+
args: [
|
|
3383
|
+
requestForQuote
|
|
3384
|
+
]
|
|
3541
3385
|
}),
|
|
3542
3386
|
(0, import_core12.quoteOperatorFeeUsdt)({
|
|
3543
3387
|
provider: this.cfg.provider,
|
|
@@ -3547,16 +3391,10 @@ var PerpDepositHandler = class {
|
|
|
3547
3391
|
})
|
|
3548
3392
|
]);
|
|
3549
3393
|
if (relayTokenFee >= request.amount) {
|
|
3550
|
-
throw new PerpDepositError(
|
|
3551
|
-
"RELAY_FEE_EXCEEDS_AMOUNT",
|
|
3552
|
-
`Relay quoted fee ${relayTokenFee} >= deposit amount ${request.amount}`
|
|
3553
|
-
);
|
|
3394
|
+
throw new PerpDepositError("RELAY_FEE_EXCEEDS_AMOUNT", `Relay quoted fee ${relayTokenFee} >= deposit amount ${request.amount}`);
|
|
3554
3395
|
}
|
|
3555
3396
|
if (usdcGasFee > 0n && usdcGasFee >= request.amount) {
|
|
3556
|
-
throw new PerpDepositError(
|
|
3557
|
-
"FEE_EXCEEDS_AMOUNT",
|
|
3558
|
-
`USDC gas fee ${usdcGasFee} >= deposit amount ${request.amount}`
|
|
3559
|
-
);
|
|
3397
|
+
throw new PerpDepositError("FEE_EXCEEDS_AMOUNT", `USDC gas fee ${usdcGasFee} >= deposit amount ${request.amount}`);
|
|
3560
3398
|
}
|
|
3561
3399
|
const maxFee = relayTokenFee * BigInt(1e4 + this.cfg.maxFeePremiumBps) / 10000n;
|
|
3562
3400
|
const depositReq = {
|
|
@@ -3641,38 +3479,34 @@ async function handleDelegatePrepare(params) {
|
|
|
3641
3479
|
eip7702Auth: authorization,
|
|
3642
3480
|
onWarning: params.onWarning
|
|
3643
3481
|
});
|
|
3644
|
-
const prepared = applyPaymasterGasEstimates(
|
|
3645
|
-
userOp,
|
|
3646
|
-
paymasterFields,
|
|
3647
|
-
params.chainId
|
|
3648
|
-
);
|
|
3482
|
+
const prepared = applyPaymasterGasEstimates(userOp, paymasterFields, params.chainId);
|
|
3649
3483
|
const merged = prepared.userOp;
|
|
3650
3484
|
const userOpHash = prepared.userOpHash;
|
|
3651
|
-
await params.store.save(
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
);
|
|
3485
|
+
await params.store.save(params.lockId, {
|
|
3486
|
+
sender: merged.sender,
|
|
3487
|
+
nonce: merged.nonce.toString(10),
|
|
3488
|
+
callData: merged.callData,
|
|
3489
|
+
callGasLimit: merged.callGasLimit.toString(10),
|
|
3490
|
+
verificationGasLimit: merged.verificationGasLimit.toString(10),
|
|
3491
|
+
preVerificationGas: merged.preVerificationGas.toString(10),
|
|
3492
|
+
maxFeePerGas: merged.maxFeePerGas.toString(10),
|
|
3493
|
+
maxPriorityFeePerGas: merged.maxPriorityFeePerGas.toString(10),
|
|
3494
|
+
...merged.paymaster ? {
|
|
3495
|
+
paymaster: merged.paymaster
|
|
3496
|
+
} : {},
|
|
3497
|
+
...merged.paymasterVerificationGasLimit ? {
|
|
3498
|
+
paymasterVerificationGasLimit: merged.paymasterVerificationGasLimit.toString(10)
|
|
3499
|
+
} : {},
|
|
3500
|
+
...merged.paymasterPostOpGasLimit ? {
|
|
3501
|
+
paymasterPostOpGasLimit: merged.paymasterPostOpGasLimit.toString(10)
|
|
3502
|
+
} : {},
|
|
3503
|
+
...merged.paymasterData ? {
|
|
3504
|
+
paymasterData: merged.paymasterData
|
|
3505
|
+
} : {},
|
|
3506
|
+
chainId: params.chainId,
|
|
3507
|
+
userOpHash,
|
|
3508
|
+
eip7702Auth: authorization
|
|
3509
|
+
}, params.ttlSeconds);
|
|
3676
3510
|
return {
|
|
3677
3511
|
lockId: params.lockId,
|
|
3678
3512
|
userOpHash,
|
|
@@ -3681,6 +3515,7 @@ async function handleDelegatePrepare(params) {
|
|
|
3681
3515
|
isSponsored: !!paymasterFields
|
|
3682
3516
|
};
|
|
3683
3517
|
}
|
|
3518
|
+
__name(handleDelegatePrepare, "handleDelegatePrepare");
|
|
3684
3519
|
async function handleDelegateSubmit(params) {
|
|
3685
3520
|
const entry = await params.store.get(params.lockId);
|
|
3686
3521
|
if (!entry) {
|
|
@@ -3690,9 +3525,7 @@ async function handleDelegateSubmit(params) {
|
|
|
3690
3525
|
throw new PendingUserOpForbiddenError(params.lockId);
|
|
3691
3526
|
}
|
|
3692
3527
|
if (!entry.eip7702Auth) {
|
|
3693
|
-
throw new Error(
|
|
3694
|
-
`delegate entry ${params.lockId} missing eip7702Auth \u2014 prepare step did not run correctly`
|
|
3695
|
-
);
|
|
3528
|
+
throw new Error(`delegate entry ${params.lockId} missing eip7702Auth \u2014 prepare step did not run correctly`);
|
|
3696
3529
|
}
|
|
3697
3530
|
const userOpJson = serializeEntryToJsonRpc(entry, params.userOpSig, "sponsored");
|
|
3698
3531
|
const result = await relayUserOp({
|
|
@@ -3702,14 +3535,20 @@ async function handleDelegateSubmit(params) {
|
|
|
3702
3535
|
eip7702Auth: entry.eip7702Auth
|
|
3703
3536
|
});
|
|
3704
3537
|
await params.store.delete(params.lockId);
|
|
3705
|
-
return {
|
|
3538
|
+
return {
|
|
3539
|
+
userOpHash: result.userOpHash
|
|
3540
|
+
};
|
|
3706
3541
|
}
|
|
3542
|
+
__name(handleDelegateSubmit, "handleDelegateSubmit");
|
|
3707
3543
|
|
|
3708
3544
|
// src/api/issuerApiAdapter.ts
|
|
3709
3545
|
var import_node_crypto3 = require("crypto");
|
|
3710
3546
|
var import_viem12 = require("viem");
|
|
3711
3547
|
var import_core14 = require("@pafi-dev/core");
|
|
3712
3548
|
var AdapterMisconfiguredError = class extends Error {
|
|
3549
|
+
static {
|
|
3550
|
+
__name(this, "AdapterMisconfiguredError");
|
|
3551
|
+
}
|
|
3713
3552
|
code = "ADAPTER_MISCONFIGURED";
|
|
3714
3553
|
constructor(message) {
|
|
3715
3554
|
super(message);
|
|
@@ -3717,35 +3556,28 @@ var AdapterMisconfiguredError = class extends Error {
|
|
|
3717
3556
|
}
|
|
3718
3557
|
};
|
|
3719
3558
|
var IssuerApiAdapter = class {
|
|
3559
|
+
static {
|
|
3560
|
+
__name(this, "IssuerApiAdapter");
|
|
3561
|
+
}
|
|
3720
3562
|
cfg;
|
|
3721
3563
|
constructor(config) {
|
|
3722
3564
|
if (config.ptClaimHandler) {
|
|
3723
3565
|
if (typeof config.ledger.bindMintUserOpHash !== "function") {
|
|
3724
|
-
throw new AdapterMisconfiguredError(
|
|
3725
|
-
"ledger.bindMintUserOpHash is required when ptClaimHandler is wired (mobile claim flow). Implement it on your IPointLedger or omit ptClaimHandler from IssuerApiAdapter config."
|
|
3726
|
-
);
|
|
3566
|
+
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.");
|
|
3727
3567
|
}
|
|
3728
3568
|
if (typeof config.ledger.getMintLock !== "function") {
|
|
3729
|
-
throw new AdapterMisconfiguredError(
|
|
3730
|
-
"ledger.getMintLock is required when ptClaimHandler is wired \u2014 claimStatus uses it to look up the lock."
|
|
3731
|
-
);
|
|
3569
|
+
throw new AdapterMisconfiguredError("ledger.getMintLock is required when ptClaimHandler is wired \u2014 claimStatus uses it to look up the lock.");
|
|
3732
3570
|
}
|
|
3733
3571
|
}
|
|
3734
3572
|
if (config.ptRedeemHandler) {
|
|
3735
3573
|
if (typeof config.ledger.reservePendingCredit !== "function") {
|
|
3736
|
-
throw new AdapterMisconfiguredError(
|
|
3737
|
-
"ledger.reservePendingCredit is required when ptRedeemHandler is wired (burn/redeem reverse flow). PTRedeemHandler also enforces this at construction; see ledger/types.ts comments."
|
|
3738
|
-
);
|
|
3574
|
+
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.");
|
|
3739
3575
|
}
|
|
3740
3576
|
if (typeof config.ledger.bindCreditUserOpHash !== "function") {
|
|
3741
|
-
throw new AdapterMisconfiguredError(
|
|
3742
|
-
"ledger.bindCreditUserOpHash is required when ptRedeemHandler is wired (mobile redeem flow)."
|
|
3743
|
-
);
|
|
3577
|
+
throw new AdapterMisconfiguredError("ledger.bindCreditUserOpHash is required when ptRedeemHandler is wired (mobile redeem flow).");
|
|
3744
3578
|
}
|
|
3745
3579
|
if (typeof config.ledger.getPendingCredit !== "function") {
|
|
3746
|
-
throw new AdapterMisconfiguredError(
|
|
3747
|
-
"ledger.getPendingCredit is required when ptRedeemHandler is wired \u2014 redeemStatus uses it to look up the credit."
|
|
3748
|
-
);
|
|
3580
|
+
throw new AdapterMisconfiguredError("ledger.getPendingCredit is required when ptRedeemHandler is wired \u2014 redeemStatus uses it to look up the credit.");
|
|
3749
3581
|
}
|
|
3750
3582
|
}
|
|
3751
3583
|
this.cfg = config;
|
|
@@ -3760,24 +3592,25 @@ var IssuerApiAdapter = class {
|
|
|
3760
3592
|
}
|
|
3761
3593
|
async gasFee() {
|
|
3762
3594
|
const result = await this.cfg.issuerService.api.handleGasFee();
|
|
3763
|
-
return {
|
|
3595
|
+
return {
|
|
3596
|
+
gasFeeUsdt: result.gasFeeUsdt.toString()
|
|
3597
|
+
};
|
|
3764
3598
|
}
|
|
3765
3599
|
async pools(authenticatedAddress, chainId, pointTokenAddress) {
|
|
3766
|
-
const result = await this.cfg.issuerService.api.handlePools(
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
);
|
|
3770
|
-
return {
|
|
3600
|
+
const result = await this.cfg.issuerService.api.handlePools(authenticatedAddress, {
|
|
3601
|
+
chainId,
|
|
3602
|
+
pointTokenAddress: (0, import_viem12.getAddress)(pointTokenAddress)
|
|
3603
|
+
});
|
|
3604
|
+
return {
|
|
3605
|
+
pools: result.pools
|
|
3606
|
+
};
|
|
3771
3607
|
}
|
|
3772
3608
|
async user(authenticatedAddress, chainId, userAddress, pointTokenAddress) {
|
|
3773
|
-
const result = await this.cfg.issuerService.api.handleUser(
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
pointTokenAddress: (0, import_viem12.getAddress)(pointTokenAddress)
|
|
3779
|
-
}
|
|
3780
|
-
);
|
|
3609
|
+
const result = await this.cfg.issuerService.api.handleUser(authenticatedAddress, {
|
|
3610
|
+
chainId,
|
|
3611
|
+
userAddress: (0, import_viem12.getAddress)(userAddress),
|
|
3612
|
+
pointTokenAddress: (0, import_viem12.getAddress)(pointTokenAddress)
|
|
3613
|
+
});
|
|
3781
3614
|
return {
|
|
3782
3615
|
offChainBalance: result.offChainBalance.toString(),
|
|
3783
3616
|
onChainBalance: result.onChainBalance.toString(),
|
|
@@ -3790,11 +3623,7 @@ var IssuerApiAdapter = class {
|
|
|
3790
3623
|
// directly. Issuer SDK doesn't ship swap/quote anymore.
|
|
3791
3624
|
// ------------------------------ Action endpoints -------------------------
|
|
3792
3625
|
async claim(input) {
|
|
3793
|
-
const ptClaimHandler = this.assertHandler(
|
|
3794
|
-
this.cfg.ptClaimHandler,
|
|
3795
|
-
"ptClaimHandler",
|
|
3796
|
-
"claim"
|
|
3797
|
-
);
|
|
3626
|
+
const ptClaimHandler = this.assertHandler(this.cfg.ptClaimHandler, "ptClaimHandler", "claim");
|
|
3798
3627
|
const pointTokenAddress = (0, import_viem12.getAddress)(input.pointTokenAddress);
|
|
3799
3628
|
const result = await ptClaimHandler.handle({
|
|
3800
3629
|
authenticatedAddress: input.authenticatedAddress,
|
|
@@ -3804,12 +3633,7 @@ var IssuerApiAdapter = class {
|
|
|
3804
3633
|
chainId: input.chainId,
|
|
3805
3634
|
aaNonce: input.aaNonce
|
|
3806
3635
|
});
|
|
3807
|
-
const sponsorAuth = await this.buildSponsorAuth(
|
|
3808
|
-
input.authenticatedAddress,
|
|
3809
|
-
result.userOp.callData,
|
|
3810
|
-
input.chainId,
|
|
3811
|
-
"mint"
|
|
3812
|
-
);
|
|
3636
|
+
const sponsorAuth = await this.buildSponsorAuth(input.authenticatedAddress, result.userOp.callData, input.chainId, "mint");
|
|
3813
3637
|
return {
|
|
3814
3638
|
calls: result.calls,
|
|
3815
3639
|
callsFallback: result.callsFallback,
|
|
@@ -3830,12 +3654,7 @@ var IssuerApiAdapter = class {
|
|
|
3830
3654
|
aaNonce: input.aaNonce,
|
|
3831
3655
|
chainId: input.chainId
|
|
3832
3656
|
});
|
|
3833
|
-
const sponsorAuth = await this.buildSponsorAuth(
|
|
3834
|
-
input.authenticatedAddress,
|
|
3835
|
-
response.userOp.callData,
|
|
3836
|
-
input.chainId,
|
|
3837
|
-
"burn"
|
|
3838
|
-
);
|
|
3657
|
+
const sponsorAuth = await this.buildSponsorAuth(input.authenticatedAddress, response.userOp.callData, input.chainId, "burn");
|
|
3839
3658
|
return {
|
|
3840
3659
|
calls: (0, import_core14.decodeBatchExecuteCalls)(response.userOp.callData),
|
|
3841
3660
|
callsFallback: response.fallback ? (0, import_core14.decodeBatchExecuteCalls)(response.fallback.userOp.callData) : void 0,
|
|
@@ -3852,11 +3671,7 @@ var IssuerApiAdapter = class {
|
|
|
3852
3671
|
// swap() removed (2026-04-27) — moved to @pafi-dev/trading.
|
|
3853
3672
|
// PAFI's web FE calls TradingHandlers.handleSwap directly.
|
|
3854
3673
|
async perpDeposit(input) {
|
|
3855
|
-
const perpHandler = this.assertHandler(
|
|
3856
|
-
this.cfg.perpHandler,
|
|
3857
|
-
"perpHandler",
|
|
3858
|
-
"perpDeposit"
|
|
3859
|
-
);
|
|
3674
|
+
const perpHandler = this.assertHandler(this.cfg.perpHandler, "perpHandler", "perpDeposit");
|
|
3860
3675
|
const result = await perpHandler.handle({
|
|
3861
3676
|
userAddress: input.authenticatedAddress,
|
|
3862
3677
|
chainId: input.chainId,
|
|
@@ -3864,12 +3679,7 @@ var IssuerApiAdapter = class {
|
|
|
3864
3679
|
brokerId: input.brokerId,
|
|
3865
3680
|
aaNonce: input.aaNonce
|
|
3866
3681
|
});
|
|
3867
|
-
const sponsorAuth = await this.buildSponsorAuth(
|
|
3868
|
-
input.authenticatedAddress,
|
|
3869
|
-
result.userOp.callData,
|
|
3870
|
-
input.chainId,
|
|
3871
|
-
"perp-deposit"
|
|
3872
|
-
);
|
|
3682
|
+
const sponsorAuth = await this.buildSponsorAuth(input.authenticatedAddress, result.userOp.callData, input.chainId, "perp-deposit");
|
|
3873
3683
|
return {
|
|
3874
3684
|
calls: result.calls,
|
|
3875
3685
|
callsFallback: result.callsFallback,
|
|
@@ -3886,11 +3696,7 @@ var IssuerApiAdapter = class {
|
|
|
3886
3696
|
}
|
|
3887
3697
|
// ------------------------------ Mobile endpoints -------------------------
|
|
3888
3698
|
async claimPrepare(input) {
|
|
3889
|
-
const ptClaimHandler = this.assertHandler(
|
|
3890
|
-
this.cfg.ptClaimHandler,
|
|
3891
|
-
"ptClaimHandler",
|
|
3892
|
-
"claimPrepare"
|
|
3893
|
-
);
|
|
3699
|
+
const ptClaimHandler = this.assertHandler(this.cfg.ptClaimHandler, "ptClaimHandler", "claimPrepare");
|
|
3894
3700
|
const pointTokenAddress = (0, import_viem12.getAddress)(input.pointTokenAddress);
|
|
3895
3701
|
const claimResult = await ptClaimHandler.handle({
|
|
3896
3702
|
authenticatedAddress: input.authenticatedAddress,
|
|
@@ -3900,17 +3706,7 @@ var IssuerApiAdapter = class {
|
|
|
3900
3706
|
chainId: input.chainId,
|
|
3901
3707
|
aaNonce: input.aaNonce
|
|
3902
3708
|
});
|
|
3903
|
-
const prepared = await this.runMobilePrepare(
|
|
3904
|
-
input.authenticatedAddress,
|
|
3905
|
-
input.chainId,
|
|
3906
|
-
claimResult.lockId,
|
|
3907
|
-
claimResult.userOp,
|
|
3908
|
-
claimResult.fallback,
|
|
3909
|
-
"mint",
|
|
3910
|
-
pointTokenAddress,
|
|
3911
|
-
claimResult.expiresInSeconds,
|
|
3912
|
-
input.eip7702Auth
|
|
3913
|
-
);
|
|
3709
|
+
const prepared = await this.runMobilePrepare(input.authenticatedAddress, input.chainId, claimResult.lockId, claimResult.userOp, claimResult.fallback, "mint", pointTokenAddress, claimResult.expiresInSeconds, input.eip7702Auth);
|
|
3914
3710
|
return {
|
|
3915
3711
|
lockId: claimResult.lockId,
|
|
3916
3712
|
userOpHash: prepared.sponsored.userOpHash,
|
|
@@ -3931,7 +3727,7 @@ var IssuerApiAdapter = class {
|
|
|
3931
3727
|
signature: input.signature,
|
|
3932
3728
|
variant: input.variant,
|
|
3933
3729
|
store: this.cfg.pendingUserOpStore,
|
|
3934
|
-
bindUserOpHash: (lockId, hash) => this.cfg.ledger.bindMintUserOpHash(lockId, hash),
|
|
3730
|
+
bindUserOpHash: /* @__PURE__ */ __name((lockId, hash) => this.cfg.ledger.bindMintUserOpHash(lockId, hash), "bindUserOpHash"),
|
|
3935
3731
|
pafiBackendClient: this.cfg.pafiBackendClient
|
|
3936
3732
|
});
|
|
3937
3733
|
}
|
|
@@ -3946,18 +3742,7 @@ var IssuerApiAdapter = class {
|
|
|
3946
3742
|
aaNonce: input.aaNonce,
|
|
3947
3743
|
chainId: input.chainId
|
|
3948
3744
|
});
|
|
3949
|
-
const prepared = await this.runMobilePrepare(
|
|
3950
|
-
input.authenticatedAddress,
|
|
3951
|
-
input.chainId,
|
|
3952
|
-
redeemResponse.lockId,
|
|
3953
|
-
redeemResponse.userOp,
|
|
3954
|
-
redeemResponse.fallback?.userOp,
|
|
3955
|
-
"burn",
|
|
3956
|
-
pointTokenAddress,
|
|
3957
|
-
redeemResponse.expiresInSeconds,
|
|
3958
|
-
input.eip7702Auth,
|
|
3959
|
-
redeemResponse.fallback?.lockId
|
|
3960
|
-
);
|
|
3745
|
+
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);
|
|
3961
3746
|
return {
|
|
3962
3747
|
lockId: redeemResponse.lockId,
|
|
3963
3748
|
lockIdFallback: redeemResponse.fallback?.lockId,
|
|
@@ -3981,7 +3766,7 @@ var IssuerApiAdapter = class {
|
|
|
3981
3766
|
signature: input.signature,
|
|
3982
3767
|
variant: input.variant,
|
|
3983
3768
|
store: this.cfg.pendingUserOpStore,
|
|
3984
|
-
bindUserOpHash: (lockId, hash) => this.cfg.ledger.bindCreditUserOpHash(lockId, hash),
|
|
3769
|
+
bindUserOpHash: /* @__PURE__ */ __name((lockId, hash) => this.cfg.ledger.bindCreditUserOpHash(lockId, hash), "bindUserOpHash"),
|
|
3985
3770
|
pafiBackendClient: this.cfg.pafiBackendClient
|
|
3986
3771
|
});
|
|
3987
3772
|
}
|
|
@@ -4009,7 +3794,9 @@ var IssuerApiAdapter = class {
|
|
|
4009
3794
|
async delegateStatus(authenticatedAddress, chainId) {
|
|
4010
3795
|
const { batchExecutor } = (0, import_core14.getContractAddresses)(chainId);
|
|
4011
3796
|
const [code, nonce] = await Promise.all([
|
|
4012
|
-
this.cfg.provider.getCode({
|
|
3797
|
+
this.cfg.provider.getCode({
|
|
3798
|
+
address: authenticatedAddress
|
|
3799
|
+
}),
|
|
4013
3800
|
this.cfg.provider.getTransactionCount({
|
|
4014
3801
|
address: authenticatedAddress,
|
|
4015
3802
|
blockTag: "pending"
|
|
@@ -4023,21 +3810,21 @@ var IssuerApiAdapter = class {
|
|
|
4023
3810
|
};
|
|
4024
3811
|
}
|
|
4025
3812
|
/**
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
3813
|
+
* Build the delegation-anchor UserOp + obtain paymaster sponsorship
|
|
3814
|
+
* + persist as a pending entry. Mobile must:
|
|
3815
|
+
*
|
|
3816
|
+
* 1. Sign EIP-7702 authorization LOCALLY (Privy `signAuthorization`
|
|
3817
|
+
* with `{contractAddress: batchExecutorAddress, chainId,
|
|
3818
|
+
* nonce: delegationNonce}`) → 65-byte authSig hex.
|
|
3819
|
+
* 2. POST `/delegate/prepare` with `{ chainId, delegationNonce,
|
|
3820
|
+
* authSig }` → this method.
|
|
3821
|
+
* 3. Sign returned `userOpHash` LOCALLY (`signTypedData(typedData)`).
|
|
3822
|
+
* 4. POST `/delegate/submit` with `{ lockId, userOpSig }`.
|
|
3823
|
+
*
|
|
3824
|
+
* v0.7.7 — replaces single-shot delegateSubmit that tried to relay
|
|
3825
|
+
* a UserOp with empty `signature: "0x"` (Simple7702Account's
|
|
3826
|
+
* validateUserOp reverts `ECDSAInvalidSignatureLength` 0xfce698f7).
|
|
3827
|
+
*/
|
|
4041
3828
|
async delegatePrepare(authenticatedAddress, input) {
|
|
4042
3829
|
const { batchExecutor } = (0, import_core14.getContractAddresses)(input.chainId);
|
|
4043
3830
|
const fees = await this.cfg.provider.estimateFeesPerGas();
|
|
@@ -4052,7 +3839,6 @@ var IssuerApiAdapter = class {
|
|
|
4052
3839
|
lockId,
|
|
4053
3840
|
store: this.cfg.pendingUserOpStore,
|
|
4054
3841
|
ttlSeconds: 15 * 60,
|
|
4055
|
-
// 15min — match claim/redeem mobile lock duration
|
|
4056
3842
|
pafiBackendClient: this.cfg.pafiBackendClient,
|
|
4057
3843
|
onWarning: this.cfg.onWarning
|
|
4058
3844
|
});
|
|
@@ -4075,13 +3861,15 @@ var IssuerApiAdapter = class {
|
|
|
4075
3861
|
store: this.cfg.pendingUserOpStore,
|
|
4076
3862
|
pafiBackendClient: this.cfg.pafiBackendClient
|
|
4077
3863
|
});
|
|
4078
|
-
return {
|
|
3864
|
+
return {
|
|
3865
|
+
userOpHash: result.userOpHash
|
|
3866
|
+
};
|
|
4079
3867
|
}
|
|
4080
3868
|
// ------------------------------ Internal helpers -------------------------
|
|
4081
3869
|
/**
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
3870
|
+
* Build + sign a SponsorAuth payload. Returns `undefined` when no
|
|
3871
|
+
* issuer id is configured, so the controller can skip the field.
|
|
3872
|
+
*/
|
|
4085
3873
|
async buildSponsorAuth(authenticatedAddress, callData, chainId, scenario) {
|
|
4086
3874
|
if (!this.cfg.pafiIssuerId) return void 0;
|
|
4087
3875
|
return (0, import_core14.buildAndSignSponsorAuth)({
|
|
@@ -4113,22 +3901,18 @@ var IssuerApiAdapter = class {
|
|
|
4113
3901
|
}
|
|
4114
3902
|
assertRedeemHandler() {
|
|
4115
3903
|
if (!this.cfg.ptRedeemHandler) {
|
|
4116
|
-
throw new Error(
|
|
4117
|
-
"PTRedeemHandler not wired \u2014 IssuerApiAdapter.redeem* require a configured ptRedeemHandler."
|
|
4118
|
-
);
|
|
3904
|
+
throw new Error("PTRedeemHandler not wired \u2014 IssuerApiAdapter.redeem* require a configured ptRedeemHandler.");
|
|
4119
3905
|
}
|
|
4120
3906
|
}
|
|
4121
3907
|
/**
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
3908
|
+
* Narrow an optional handler to non-null and throw a clear error when
|
|
3909
|
+
* the issuer wired the adapter without it. Lets issuers opt out of
|
|
3910
|
+
* flows they don't expose (gg56 ships only mobile claim/redeem, so
|
|
3911
|
+
* `swapHandler` + `perpHandler` aren't constructed).
|
|
3912
|
+
*/
|
|
4127
3913
|
assertHandler(handler, fieldName, methodName) {
|
|
4128
3914
|
if (handler === null || handler === void 0) {
|
|
4129
|
-
throw new Error(
|
|
4130
|
-
`${fieldName} not wired \u2014 IssuerApiAdapter.${methodName}() requires a configured ${fieldName}.`
|
|
4131
|
-
);
|
|
3915
|
+
throw new Error(`${fieldName} not wired \u2014 IssuerApiAdapter.${methodName}() requires a configured ${fieldName}.`);
|
|
4132
3916
|
}
|
|
4133
3917
|
return handler;
|
|
4134
3918
|
}
|
|
@@ -4161,9 +3945,7 @@ function createSubgraphPoolsProvider(config = {}) {
|
|
|
4161
3945
|
}
|
|
4162
3946
|
} catch (err) {
|
|
4163
3947
|
if (err instanceof TypeError) {
|
|
4164
|
-
throw new Error(
|
|
4165
|
-
`subgraphPoolsProvider: invalid subgraphUrl: ${subgraphUrl}`
|
|
4166
|
-
);
|
|
3948
|
+
throw new Error(`subgraphPoolsProvider: invalid subgraphUrl: ${subgraphUrl}`);
|
|
4167
3949
|
}
|
|
4168
3950
|
throw err;
|
|
4169
3951
|
}
|
|
@@ -4173,64 +3955,61 @@ function createSubgraphPoolsProvider(config = {}) {
|
|
|
4173
3955
|
const onError = config.onError;
|
|
4174
3956
|
const cache = /* @__PURE__ */ new Map();
|
|
4175
3957
|
if (!fetchImpl) {
|
|
4176
|
-
throw new Error(
|
|
4177
|
-
"createSubgraphPoolsProvider: no fetch implementation available \u2014 pass `fetchImpl` or run on Node 18+"
|
|
4178
|
-
);
|
|
3958
|
+
throw new Error("createSubgraphPoolsProvider: no fetch implementation available \u2014 pass `fetchImpl` or run on Node 18+");
|
|
4179
3959
|
}
|
|
4180
|
-
const reportError = (err) => {
|
|
3960
|
+
const reportError = /* @__PURE__ */ __name((err) => {
|
|
4181
3961
|
if (!onError) return;
|
|
4182
3962
|
try {
|
|
4183
3963
|
onError(err);
|
|
4184
3964
|
} catch {
|
|
4185
3965
|
}
|
|
4186
|
-
};
|
|
3966
|
+
}, "reportError");
|
|
4187
3967
|
return async (request) => {
|
|
4188
3968
|
const cacheKey = `${request.chainId}:${request.pointTokenAddress.toLowerCase()}`;
|
|
4189
3969
|
if (cacheTtl > 0) {
|
|
4190
3970
|
const cached = cache.get(cacheKey);
|
|
4191
3971
|
if (cached && cached.expiresAt > now()) {
|
|
4192
|
-
return {
|
|
3972
|
+
return {
|
|
3973
|
+
pools: cached.pools
|
|
3974
|
+
};
|
|
4193
3975
|
}
|
|
4194
3976
|
}
|
|
4195
|
-
const pools = await fetchPoolsFromSubgraph(
|
|
4196
|
-
fetchImpl,
|
|
4197
|
-
subgraphUrl,
|
|
4198
|
-
request.pointTokenAddress,
|
|
4199
|
-
reportError
|
|
4200
|
-
);
|
|
3977
|
+
const pools = await fetchPoolsFromSubgraph(fetchImpl, subgraphUrl, request.pointTokenAddress, reportError);
|
|
4201
3978
|
if (cacheTtl > 0) {
|
|
4202
3979
|
cache.set(cacheKey, {
|
|
4203
3980
|
expiresAt: now() + cacheTtl,
|
|
4204
3981
|
pools
|
|
4205
3982
|
});
|
|
4206
3983
|
}
|
|
4207
|
-
return {
|
|
3984
|
+
return {
|
|
3985
|
+
pools
|
|
3986
|
+
};
|
|
4208
3987
|
};
|
|
4209
3988
|
}
|
|
3989
|
+
__name(createSubgraphPoolsProvider, "createSubgraphPoolsProvider");
|
|
4210
3990
|
async function fetchPoolsFromSubgraph(fetchImpl, subgraphUrl, pointTokenAddress, reportError) {
|
|
4211
3991
|
let response;
|
|
4212
3992
|
try {
|
|
4213
3993
|
response = await fetchImpl(subgraphUrl, {
|
|
4214
3994
|
method: "POST",
|
|
4215
|
-
headers: {
|
|
3995
|
+
headers: {
|
|
3996
|
+
"Content-Type": "application/json"
|
|
3997
|
+
},
|
|
4216
3998
|
body: JSON.stringify({
|
|
4217
3999
|
query: POOL_QUERY,
|
|
4218
|
-
variables: {
|
|
4000
|
+
variables: {
|
|
4001
|
+
id: pointTokenAddress.toLowerCase()
|
|
4002
|
+
}
|
|
4219
4003
|
})
|
|
4220
4004
|
});
|
|
4221
4005
|
} catch (err) {
|
|
4222
4006
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
4223
|
-
console.warn(
|
|
4224
|
-
"[subgraphPoolsProvider] subgraph unreachable:",
|
|
4225
|
-
error.message
|
|
4226
|
-
);
|
|
4007
|
+
console.warn("[subgraphPoolsProvider] subgraph unreachable:", error.message);
|
|
4227
4008
|
reportError(error);
|
|
4228
4009
|
return [];
|
|
4229
4010
|
}
|
|
4230
4011
|
if (!response.ok) {
|
|
4231
|
-
const error = new Error(
|
|
4232
|
-
`subgraph returned HTTP ${response.status}`
|
|
4233
|
-
);
|
|
4012
|
+
const error = new Error(`subgraph returned HTTP ${response.status}`);
|
|
4234
4013
|
console.warn(`[subgraphPoolsProvider] ${error.message}`);
|
|
4235
4014
|
reportError(error);
|
|
4236
4015
|
return [];
|
|
@@ -4240,10 +4019,7 @@ async function fetchPoolsFromSubgraph(fetchImpl, subgraphUrl, pointTokenAddress,
|
|
|
4240
4019
|
json = await response.json();
|
|
4241
4020
|
} catch (err) {
|
|
4242
4021
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
4243
|
-
console.warn(
|
|
4244
|
-
"[subgraphPoolsProvider] subgraph returned non-JSON:",
|
|
4245
|
-
error.message
|
|
4246
|
-
);
|
|
4022
|
+
console.warn("[subgraphPoolsProvider] subgraph returned non-JSON:", error.message);
|
|
4247
4023
|
reportError(error);
|
|
4248
4024
|
return [];
|
|
4249
4025
|
}
|
|
@@ -4259,36 +4035,39 @@ async function fetchPoolsFromSubgraph(fetchImpl, subgraphUrl, pointTokenAddress,
|
|
|
4259
4035
|
}
|
|
4260
4036
|
const { pool } = token;
|
|
4261
4037
|
if (!(0, import_viem13.isAddress)(pool.token0.id) || !(0, import_viem13.isAddress)(pool.token1.id)) {
|
|
4262
|
-
const error = new Error(
|
|
4263
|
-
"[PAFI] SubgraphPoolsProvider: invalid token address in response"
|
|
4264
|
-
);
|
|
4038
|
+
const error = new Error("[PAFI] SubgraphPoolsProvider: invalid token address in response");
|
|
4265
4039
|
console.error(error.message, "\u2014 skipping pool");
|
|
4266
4040
|
reportError(error);
|
|
4267
4041
|
return [];
|
|
4268
4042
|
}
|
|
4269
4043
|
const feeNum = Number(pool.feeTier);
|
|
4270
4044
|
if (!Number.isInteger(feeNum) || feeNum < 0 || feeNum >= MAX_REASONABLE_FEE_TIER) {
|
|
4271
|
-
const error = new Error(
|
|
4272
|
-
`[PAFI] SubgraphPoolsProvider: invalid feeTier value: ${pool.feeTier}`
|
|
4273
|
-
);
|
|
4045
|
+
const error = new Error(`[PAFI] SubgraphPoolsProvider: invalid feeTier value: ${pool.feeTier}`);
|
|
4274
4046
|
console.error(error.message, "\u2014 skipping pool");
|
|
4275
4047
|
reportError(error);
|
|
4276
4048
|
return [];
|
|
4277
4049
|
}
|
|
4278
|
-
const [token0, token1] = sortTokens(
|
|
4279
|
-
pool.token0.id,
|
|
4280
|
-
pool.token1.id
|
|
4281
|
-
);
|
|
4050
|
+
const [token0, token1] = sortTokens(pool.token0.id, pool.token1.id);
|
|
4282
4051
|
const poolKey = {
|
|
4283
4052
|
token0,
|
|
4284
4053
|
token1,
|
|
4285
4054
|
fee: feeNum
|
|
4286
4055
|
};
|
|
4287
|
-
return [
|
|
4056
|
+
return [
|
|
4057
|
+
poolKey
|
|
4058
|
+
];
|
|
4288
4059
|
}
|
|
4060
|
+
__name(fetchPoolsFromSubgraph, "fetchPoolsFromSubgraph");
|
|
4289
4061
|
function sortTokens(a, b) {
|
|
4290
|
-
return a.toLowerCase() < b.toLowerCase() ? [
|
|
4062
|
+
return a.toLowerCase() < b.toLowerCase() ? [
|
|
4063
|
+
a,
|
|
4064
|
+
b
|
|
4065
|
+
] : [
|
|
4066
|
+
b,
|
|
4067
|
+
a
|
|
4068
|
+
];
|
|
4291
4069
|
}
|
|
4070
|
+
__name(sortTokens, "sortTokens");
|
|
4292
4071
|
|
|
4293
4072
|
// src/pools/subgraphNativeUsdtQuoter.ts
|
|
4294
4073
|
var DEFAULT_CACHE_TTL_MS2 = 3e4;
|
|
@@ -4311,9 +4090,7 @@ function createSubgraphNativeUsdtQuoter(config = {}) {
|
|
|
4311
4090
|
}
|
|
4312
4091
|
} catch (err) {
|
|
4313
4092
|
if (err instanceof TypeError) {
|
|
4314
|
-
throw new Error(
|
|
4315
|
-
`createSubgraphNativeUsdtQuoter: invalid subgraphUrl: ${subgraphUrl}`
|
|
4316
|
-
);
|
|
4093
|
+
throw new Error(`createSubgraphNativeUsdtQuoter: invalid subgraphUrl: ${subgraphUrl}`);
|
|
4317
4094
|
}
|
|
4318
4095
|
throw err;
|
|
4319
4096
|
}
|
|
@@ -4324,23 +4101,15 @@ function createSubgraphNativeUsdtQuoter(config = {}) {
|
|
|
4324
4101
|
const fetchImpl = config.fetchImpl ?? globalThis.fetch;
|
|
4325
4102
|
const now = config.now ?? (() => Date.now());
|
|
4326
4103
|
if (!fetchImpl) {
|
|
4327
|
-
throw new Error(
|
|
4328
|
-
"createSubgraphNativeUsdtQuoter: no fetch implementation available \u2014 pass `fetchImpl` or run on Node 18+"
|
|
4329
|
-
);
|
|
4104
|
+
throw new Error("createSubgraphNativeUsdtQuoter: no fetch implementation available \u2014 pass `fetchImpl` or run on Node 18+");
|
|
4330
4105
|
}
|
|
4331
4106
|
let cached;
|
|
4332
4107
|
async function getUsdtPerNative() {
|
|
4333
4108
|
if (cacheTtl > 0 && cached && cached.expiresAt > now()) {
|
|
4334
4109
|
return cached.usdtPerNative;
|
|
4335
4110
|
}
|
|
4336
|
-
const price = await fetchEthPriceFromSubgraph(
|
|
4337
|
-
|
|
4338
|
-
subgraphUrl
|
|
4339
|
-
);
|
|
4340
|
-
const usdtPerNative = toUsdtPerNative(
|
|
4341
|
-
price ?? fallbackPrice,
|
|
4342
|
-
usdtDecimals
|
|
4343
|
-
);
|
|
4111
|
+
const price = await fetchEthPriceFromSubgraph(fetchImpl, subgraphUrl);
|
|
4112
|
+
const usdtPerNative = toUsdtPerNative(price ?? fallbackPrice, usdtDecimals);
|
|
4344
4113
|
if (cacheTtl > 0) {
|
|
4345
4114
|
cached = {
|
|
4346
4115
|
usdtPerNative,
|
|
@@ -4349,66 +4118,62 @@ function createSubgraphNativeUsdtQuoter(config = {}) {
|
|
|
4349
4118
|
}
|
|
4350
4119
|
return usdtPerNative;
|
|
4351
4120
|
}
|
|
4121
|
+
__name(getUsdtPerNative, "getUsdtPerNative");
|
|
4352
4122
|
return async (amountNative) => {
|
|
4353
4123
|
if (amountNative === 0n) return 0n;
|
|
4354
4124
|
const usdtPerNative = await getUsdtPerNative();
|
|
4355
4125
|
return amountNative * usdtPerNative / 10n ** BigInt(nativeDecimals);
|
|
4356
4126
|
};
|
|
4357
4127
|
}
|
|
4128
|
+
__name(createSubgraphNativeUsdtQuoter, "createSubgraphNativeUsdtQuoter");
|
|
4358
4129
|
async function fetchEthPriceFromSubgraph(fetchImpl, subgraphUrl) {
|
|
4359
4130
|
let response;
|
|
4360
4131
|
try {
|
|
4361
4132
|
response = await fetchImpl(subgraphUrl, {
|
|
4362
4133
|
method: "POST",
|
|
4363
|
-
headers: {
|
|
4364
|
-
|
|
4134
|
+
headers: {
|
|
4135
|
+
"Content-Type": "application/json"
|
|
4136
|
+
},
|
|
4137
|
+
body: JSON.stringify({
|
|
4138
|
+
query: PRICE_QUERY
|
|
4139
|
+
})
|
|
4365
4140
|
});
|
|
4366
4141
|
} catch (err) {
|
|
4367
|
-
console.warn(
|
|
4368
|
-
"[subgraphNativeUsdtQuoter] subgraph unreachable:",
|
|
4369
|
-
err.message
|
|
4370
|
-
);
|
|
4142
|
+
console.warn("[subgraphNativeUsdtQuoter] subgraph unreachable:", err.message);
|
|
4371
4143
|
return null;
|
|
4372
4144
|
}
|
|
4373
4145
|
if (!response.ok) {
|
|
4374
|
-
console.warn(
|
|
4375
|
-
`[subgraphNativeUsdtQuoter] subgraph returned ${response.status}`
|
|
4376
|
-
);
|
|
4146
|
+
console.warn(`[subgraphNativeUsdtQuoter] subgraph returned ${response.status}`);
|
|
4377
4147
|
return null;
|
|
4378
4148
|
}
|
|
4379
4149
|
const json = await response.json();
|
|
4380
4150
|
if (json.errors && json.errors.length > 0) {
|
|
4381
|
-
console.warn(
|
|
4382
|
-
"[subgraphNativeUsdtQuoter] subgraph errors:",
|
|
4383
|
-
json.errors.map((e) => e.message).join("; ")
|
|
4384
|
-
);
|
|
4151
|
+
console.warn("[subgraphNativeUsdtQuoter] subgraph errors:", json.errors.map((e) => e.message).join("; "));
|
|
4385
4152
|
return null;
|
|
4386
4153
|
}
|
|
4387
4154
|
const raw = json.data?.bundle?.ethPriceUSD;
|
|
4388
4155
|
if (!raw) return null;
|
|
4389
4156
|
const parsed = Number(raw);
|
|
4390
4157
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
4391
|
-
console.warn(
|
|
4392
|
-
`[subgraphNativeUsdtQuoter] invalid ethPriceUSD from subgraph: ${raw}`
|
|
4393
|
-
);
|
|
4158
|
+
console.warn(`[subgraphNativeUsdtQuoter] invalid ethPriceUSD from subgraph: ${raw}`);
|
|
4394
4159
|
return null;
|
|
4395
4160
|
}
|
|
4396
4161
|
const MIN_REASONABLE_ETH_PRICE = 100;
|
|
4397
4162
|
const MAX_REASONABLE_ETH_PRICE = 1e5;
|
|
4398
4163
|
if (parsed < MIN_REASONABLE_ETH_PRICE || parsed > MAX_REASONABLE_ETH_PRICE) {
|
|
4399
|
-
console.warn(
|
|
4400
|
-
`[PAFI] SubgraphNativeUsdtQuoter: ETH/USD price ${parsed} is outside reasonable range. Using fallback.`
|
|
4401
|
-
);
|
|
4164
|
+
console.warn(`[PAFI] SubgraphNativeUsdtQuoter: ETH/USD price ${parsed} is outside reasonable range. Using fallback.`);
|
|
4402
4165
|
return null;
|
|
4403
4166
|
}
|
|
4404
4167
|
return parsed;
|
|
4405
4168
|
}
|
|
4169
|
+
__name(fetchEthPriceFromSubgraph, "fetchEthPriceFromSubgraph");
|
|
4406
4170
|
function toUsdtPerNative(priceFloat, usdtDecimals) {
|
|
4407
4171
|
const fixed = priceFloat.toFixed(usdtDecimals);
|
|
4408
4172
|
const [whole, fraction = ""] = fixed.split(".");
|
|
4409
4173
|
const padded = (fraction + "0".repeat(usdtDecimals)).slice(0, usdtDecimals);
|
|
4410
4174
|
return BigInt(whole + padded);
|
|
4411
4175
|
}
|
|
4176
|
+
__name(toUsdtPerNative, "toUsdtPerNative");
|
|
4412
4177
|
|
|
4413
4178
|
// src/pools/nativePtQuoter.ts
|
|
4414
4179
|
var import_viem14 = require("viem");
|
|
@@ -4429,18 +4194,7 @@ var POOL_PRICE_QUERY = `
|
|
|
4429
4194
|
}
|
|
4430
4195
|
`;
|
|
4431
4196
|
function createNativePtQuoter(config) {
|
|
4432
|
-
const {
|
|
4433
|
-
provider,
|
|
4434
|
-
pointTokenAddress,
|
|
4435
|
-
chainlinkFeedAddress = "0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70",
|
|
4436
|
-
subgraphUrl = import_core15.PAFI_SUBGRAPH_URL,
|
|
4437
|
-
cacheTtlMs = 3e4,
|
|
4438
|
-
fallbackEthPriceUsd = 3e3,
|
|
4439
|
-
fallbackPtPriceUsdt = 0.1,
|
|
4440
|
-
failClosed = false,
|
|
4441
|
-
fetchImpl = globalThis.fetch,
|
|
4442
|
-
now = () => Date.now()
|
|
4443
|
-
} = config;
|
|
4197
|
+
const { provider, pointTokenAddress, chainlinkFeedAddress = "0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70", subgraphUrl = import_core15.PAFI_SUBGRAPH_URL, cacheTtlMs = 3e4, fallbackEthPriceUsd = 3e3, fallbackPtPriceUsdt = 0.1, failClosed = false, fetchImpl = globalThis.fetch, now = /* @__PURE__ */ __name(() => Date.now(), "now") } = config;
|
|
4444
4198
|
let ethPriceCache;
|
|
4445
4199
|
let ptPriceCache;
|
|
4446
4200
|
async function getEthPrice8dec() {
|
|
@@ -4459,28 +4213,34 @@ function createNativePtQuoter(config) {
|
|
|
4459
4213
|
if (ageS > CHAINLINK_MAX_AGE_S) {
|
|
4460
4214
|
throw new Error(`Chainlink: price stale by ${ageS}s`);
|
|
4461
4215
|
}
|
|
4462
|
-
ethPriceCache = {
|
|
4216
|
+
ethPriceCache = {
|
|
4217
|
+
value: answer,
|
|
4218
|
+
expiresAt: ts + cacheTtlMs
|
|
4219
|
+
};
|
|
4463
4220
|
return answer;
|
|
4464
4221
|
} catch (err) {
|
|
4465
4222
|
if (failClosed) {
|
|
4466
|
-
throw new Error(
|
|
4467
|
-
`[nativePtQuoter] Chainlink unavailable in fail-closed mode: ${err.message}`
|
|
4468
|
-
);
|
|
4223
|
+
throw new Error(`[nativePtQuoter] Chainlink unavailable in fail-closed mode: ${err.message}`);
|
|
4469
4224
|
}
|
|
4470
4225
|
console.warn("[nativePtQuoter] Chainlink unavailable, using fallback:", err.message);
|
|
4471
4226
|
return BigInt(Math.round(fallbackEthPriceUsd * 1e8));
|
|
4472
4227
|
}
|
|
4473
4228
|
}
|
|
4229
|
+
__name(getEthPrice8dec, "getEthPrice8dec");
|
|
4474
4230
|
async function getPtPerUsdt18dec() {
|
|
4475
4231
|
const ts = now();
|
|
4476
4232
|
if (ptPriceCache && ptPriceCache.expiresAt > ts) return ptPriceCache.value;
|
|
4477
4233
|
try {
|
|
4478
4234
|
const response = await fetchImpl(subgraphUrl, {
|
|
4479
4235
|
method: "POST",
|
|
4480
|
-
headers: {
|
|
4236
|
+
headers: {
|
|
4237
|
+
"Content-Type": "application/json"
|
|
4238
|
+
},
|
|
4481
4239
|
body: JSON.stringify({
|
|
4482
4240
|
query: POOL_PRICE_QUERY,
|
|
4483
|
-
variables: {
|
|
4241
|
+
variables: {
|
|
4242
|
+
id: pointTokenAddress.toLowerCase()
|
|
4243
|
+
}
|
|
4484
4244
|
})
|
|
4485
4245
|
});
|
|
4486
4246
|
if (!response.ok) throw new Error(`subgraph HTTP ${response.status}`);
|
|
@@ -4496,19 +4256,21 @@ function createNativePtQuoter(config) {
|
|
|
4496
4256
|
const raw = parseBigDecimalTo18(ptPerUsdtStr);
|
|
4497
4257
|
if (raw === 0n) throw new Error(`pool price parsed to zero: ${ptPerUsdtStr}`);
|
|
4498
4258
|
const value = 10n ** 24n / raw;
|
|
4499
|
-
ptPriceCache = {
|
|
4259
|
+
ptPriceCache = {
|
|
4260
|
+
value,
|
|
4261
|
+
expiresAt: ts + cacheTtlMs
|
|
4262
|
+
};
|
|
4500
4263
|
return value;
|
|
4501
4264
|
} catch (err) {
|
|
4502
4265
|
if (failClosed) {
|
|
4503
|
-
throw new Error(
|
|
4504
|
-
`[nativePtQuoter] subgraph miss for ${pointTokenAddress} in fail-closed mode: ${err.message}`
|
|
4505
|
-
);
|
|
4266
|
+
throw new Error(`[nativePtQuoter] subgraph miss for ${pointTokenAddress} in fail-closed mode: ${err.message}`);
|
|
4506
4267
|
}
|
|
4507
4268
|
console.warn("[nativePtQuoter] subgraph unavailable, using fallback:", err.message);
|
|
4508
4269
|
const ptPerUsdtHuman = 1 / fallbackPtPriceUsdt;
|
|
4509
4270
|
return parseBigDecimalTo18(ptPerUsdtHuman.toFixed(18));
|
|
4510
4271
|
}
|
|
4511
4272
|
}
|
|
4273
|
+
__name(getPtPerUsdt18dec, "getPtPerUsdt18dec");
|
|
4512
4274
|
return async (amountNative) => {
|
|
4513
4275
|
if (amountNative === 0n) return 0n;
|
|
4514
4276
|
const [ethPrice8dec, ptPerUsdt18dec] = await Promise.all([
|
|
@@ -4518,12 +4280,14 @@ function createNativePtQuoter(config) {
|
|
|
4518
4280
|
return amountNative * ethPrice8dec * ptPerUsdt18dec / 10n ** 26n;
|
|
4519
4281
|
};
|
|
4520
4282
|
}
|
|
4283
|
+
__name(createNativePtQuoter, "createNativePtQuoter");
|
|
4521
4284
|
function parseBigDecimalTo18(s) {
|
|
4522
4285
|
const SCALE = 18;
|
|
4523
4286
|
const [whole = "0", frac = ""] = s.split(".");
|
|
4524
4287
|
const padded = (frac + "0".repeat(SCALE)).slice(0, SCALE);
|
|
4525
4288
|
return BigInt(whole + padded);
|
|
4526
4289
|
}
|
|
4290
|
+
__name(parseBigDecimalTo18, "parseBigDecimalTo18");
|
|
4527
4291
|
|
|
4528
4292
|
// src/pafi-backend/client.ts
|
|
4529
4293
|
var import_core16 = require("@pafi-dev/core");
|
|
@@ -4531,15 +4295,24 @@ function extractPafiErrorFields(json, status) {
|
|
|
4531
4295
|
const inner = typeof json.error === "object" && json.error !== null ? json.error : null;
|
|
4532
4296
|
const code = inner?.code ?? json.code ?? "INTERNAL_ERROR";
|
|
4533
4297
|
const message = inner?.message ?? json.message ?? `HTTP ${status}`;
|
|
4534
|
-
return {
|
|
4298
|
+
return {
|
|
4299
|
+
code,
|
|
4300
|
+
message
|
|
4301
|
+
};
|
|
4535
4302
|
}
|
|
4303
|
+
__name(extractPafiErrorFields, "extractPafiErrorFields");
|
|
4536
4304
|
function serializeBigInt(_key, value) {
|
|
4537
4305
|
return typeof value === "bigint" ? value.toString(10) : value;
|
|
4538
4306
|
}
|
|
4307
|
+
__name(serializeBigInt, "serializeBigInt");
|
|
4539
4308
|
function sleep(ms) {
|
|
4540
4309
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
4541
4310
|
}
|
|
4311
|
+
__name(sleep, "sleep");
|
|
4542
4312
|
var PafiBackendClient = class {
|
|
4313
|
+
static {
|
|
4314
|
+
__name(this, "PafiBackendClient");
|
|
4315
|
+
}
|
|
4543
4316
|
config;
|
|
4544
4317
|
baseUrl;
|
|
4545
4318
|
constructor(config) {
|
|
@@ -4577,11 +4350,11 @@ var PafiBackendClient = class {
|
|
|
4577
4350
|
throw lastError;
|
|
4578
4351
|
}
|
|
4579
4352
|
/**
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4353
|
+
* Fetch ERC-4337 UserOp receipt via PAFI's authenticated bundler proxy.
|
|
4354
|
+
* Returns `null` when the bundler hasn't seen the userOp yet — caller
|
|
4355
|
+
* should keep polling. Used by status endpoints to short-circuit the
|
|
4356
|
+
* on-chain indexer when several PENDING locks share the same amount.
|
|
4357
|
+
*/
|
|
4585
4358
|
async getUserOpReceipt(userOpHash) {
|
|
4586
4359
|
const fetchFn = this.config.fetchImpl ?? fetch;
|
|
4587
4360
|
const url = `${this.baseUrl}/bundler/receipt`;
|
|
@@ -4594,14 +4367,12 @@ var PafiBackendClient = class {
|
|
|
4594
4367
|
Authorization: `Bearer ${this.config.apiKey}`,
|
|
4595
4368
|
"X-Issuer-Id": this.config.issuerId
|
|
4596
4369
|
},
|
|
4597
|
-
body: JSON.stringify({
|
|
4370
|
+
body: JSON.stringify({
|
|
4371
|
+
userOpHash
|
|
4372
|
+
})
|
|
4598
4373
|
});
|
|
4599
4374
|
} catch (err) {
|
|
4600
|
-
throw new PafiBackendError(
|
|
4601
|
-
"NETWORK_ERROR",
|
|
4602
|
-
`Network error: ${err instanceof Error ? err.message : String(err)}`,
|
|
4603
|
-
0
|
|
4604
|
-
);
|
|
4375
|
+
throw new PafiBackendError("NETWORK_ERROR", `Network error: ${err instanceof Error ? err.message : String(err)}`, 0);
|
|
4605
4376
|
}
|
|
4606
4377
|
const text = await response.text();
|
|
4607
4378
|
let json = {};
|
|
@@ -4635,11 +4406,7 @@ var PafiBackendClient = class {
|
|
|
4635
4406
|
body: JSON.stringify(request)
|
|
4636
4407
|
});
|
|
4637
4408
|
} catch (err) {
|
|
4638
|
-
throw new PafiBackendError(
|
|
4639
|
-
"NETWORK_ERROR",
|
|
4640
|
-
`Network error: ${err instanceof Error ? err.message : String(err)}`,
|
|
4641
|
-
0
|
|
4642
|
-
);
|
|
4409
|
+
throw new PafiBackendError("NETWORK_ERROR", `Network error: ${err instanceof Error ? err.message : String(err)}`, 0);
|
|
4643
4410
|
}
|
|
4644
4411
|
const text = await response.text();
|
|
4645
4412
|
let json = {};
|
|
@@ -4651,7 +4418,9 @@ var PafiBackendClient = class {
|
|
|
4651
4418
|
const { code, message } = extractPafiErrorFields(json, response.status);
|
|
4652
4419
|
throw new PafiBackendError(code, message, response.status, json);
|
|
4653
4420
|
}
|
|
4654
|
-
return {
|
|
4421
|
+
return {
|
|
4422
|
+
userOpHash: json.userOpHash
|
|
4423
|
+
};
|
|
4655
4424
|
}
|
|
4656
4425
|
async _doRequest(request) {
|
|
4657
4426
|
const fetchFn = this.config.fetchImpl ?? fetch;
|
|
@@ -4669,11 +4438,7 @@ var PafiBackendClient = class {
|
|
|
4669
4438
|
body
|
|
4670
4439
|
});
|
|
4671
4440
|
} catch (err) {
|
|
4672
|
-
throw new PafiBackendError(
|
|
4673
|
-
"NETWORK_ERROR",
|
|
4674
|
-
`Network error: ${err instanceof Error ? err.message : String(err)}`,
|
|
4675
|
-
0
|
|
4676
|
-
);
|
|
4441
|
+
throw new PafiBackendError("NETWORK_ERROR", `Network error: ${err instanceof Error ? err.message : String(err)}`, 0);
|
|
4677
4442
|
}
|
|
4678
4443
|
const text = await response.text();
|
|
4679
4444
|
let json = {};
|
|
@@ -4694,9 +4459,7 @@ var PafiBackendClient = class {
|
|
|
4694
4459
|
return {
|
|
4695
4460
|
paymaster: json.paymaster,
|
|
4696
4461
|
paymasterData: json.paymasterData,
|
|
4697
|
-
paymasterVerificationGasLimit: BigInt(
|
|
4698
|
-
json.paymasterVerificationGasLimit
|
|
4699
|
-
),
|
|
4462
|
+
paymasterVerificationGasLimit: BigInt(json.paymasterVerificationGasLimit),
|
|
4700
4463
|
paymasterPostOpGasLimit: BigInt(json.paymasterPostOpGasLimit),
|
|
4701
4464
|
callGasLimit: json.callGasLimit != null ? BigInt(json.callGasLimit) : void 0,
|
|
4702
4465
|
verificationGasLimit: json.verificationGasLimit != null ? BigInt(json.verificationGasLimit) : void 0,
|
|
@@ -4720,10 +4483,7 @@ function evaluateRedemption(input) {
|
|
|
4720
4483
|
const cooldownUntilUnixSec = history.lastRedeemedAtUnixSec !== null ? history.lastRedeemedAtUnixSec + policy.cooldownSec : null;
|
|
4721
4484
|
const inCooldown = cooldownUntilUnixSec !== null && cooldownUntilUnixSec > nowUnixSec;
|
|
4722
4485
|
const activeBlackout = findActiveBlackout(policy.blackoutWindows, nowUnixSec);
|
|
4723
|
-
const nextBlackoutEnd = nextBlackoutEndAfter(
|
|
4724
|
-
policy.blackoutWindows,
|
|
4725
|
-
nowUnixSec
|
|
4726
|
-
);
|
|
4486
|
+
const nextBlackoutEnd = nextBlackoutEndAfter(policy.blackoutWindows, nowUnixSec);
|
|
4727
4487
|
let availableAmountPt = 0n;
|
|
4728
4488
|
if (!inCooldown && !activeBlackout) {
|
|
4729
4489
|
const headroom = dailyRemaining < policy.perTxMaxPt ? dailyRemaining : policy.perTxMaxPt;
|
|
@@ -4740,7 +4500,11 @@ function evaluateRedemption(input) {
|
|
|
4740
4500
|
policySource
|
|
4741
4501
|
};
|
|
4742
4502
|
if (amountPt <= 0n) {
|
|
4743
|
-
return {
|
|
4503
|
+
return {
|
|
4504
|
+
allowed: false,
|
|
4505
|
+
preview,
|
|
4506
|
+
denial: rejectAmountBelowMin(policy)
|
|
4507
|
+
};
|
|
4744
4508
|
}
|
|
4745
4509
|
const denial = firstDenial({
|
|
4746
4510
|
amountPt,
|
|
@@ -4750,25 +4514,29 @@ function evaluateRedemption(input) {
|
|
|
4750
4514
|
cooldownUntilUnixSec,
|
|
4751
4515
|
activeBlackout
|
|
4752
4516
|
});
|
|
4753
|
-
if (denial) return {
|
|
4754
|
-
|
|
4517
|
+
if (denial) return {
|
|
4518
|
+
allowed: false,
|
|
4519
|
+
denial,
|
|
4520
|
+
preview
|
|
4521
|
+
};
|
|
4522
|
+
return {
|
|
4523
|
+
allowed: true,
|
|
4524
|
+
preview
|
|
4525
|
+
};
|
|
4755
4526
|
}
|
|
4527
|
+
__name(evaluateRedemption, "evaluateRedemption");
|
|
4756
4528
|
function firstDenial(args) {
|
|
4757
4529
|
const { amountPt, policy, dailyRemaining, inCooldown, cooldownUntilUnixSec, activeBlackout } = args;
|
|
4758
4530
|
if (activeBlackout) {
|
|
4759
4531
|
return {
|
|
4760
4532
|
code: "BLACKOUT_WINDOW",
|
|
4761
|
-
message: `Redemption is blocked until ${new Date(
|
|
4762
|
-
activeBlackout.endUnixSec * 1e3
|
|
4763
|
-
).toISOString()}${activeBlackout.reason ? ` (${activeBlackout.reason})` : ""}`
|
|
4533
|
+
message: `Redemption is blocked until ${new Date(activeBlackout.endUnixSec * 1e3).toISOString()}${activeBlackout.reason ? ` (${activeBlackout.reason})` : ""}`
|
|
4764
4534
|
};
|
|
4765
4535
|
}
|
|
4766
4536
|
if (inCooldown && cooldownUntilUnixSec !== null) {
|
|
4767
4537
|
return {
|
|
4768
4538
|
code: "COOLDOWN_ACTIVE",
|
|
4769
|
-
message: `Cooldown active until ${new Date(
|
|
4770
|
-
cooldownUntilUnixSec * 1e3
|
|
4771
|
-
).toISOString()}`
|
|
4539
|
+
message: `Cooldown active until ${new Date(cooldownUntilUnixSec * 1e3).toISOString()}`
|
|
4772
4540
|
};
|
|
4773
4541
|
}
|
|
4774
4542
|
if (amountPt < policy.perTxMinPt) {
|
|
@@ -4791,18 +4559,21 @@ function firstDenial(args) {
|
|
|
4791
4559
|
}
|
|
4792
4560
|
return null;
|
|
4793
4561
|
}
|
|
4562
|
+
__name(firstDenial, "firstDenial");
|
|
4794
4563
|
function rejectAmountBelowMin(policy) {
|
|
4795
4564
|
return {
|
|
4796
4565
|
code: "AMOUNT_BELOW_MIN",
|
|
4797
4566
|
message: `amount must be >= ${policy.perTxMinPt}`
|
|
4798
4567
|
};
|
|
4799
4568
|
}
|
|
4569
|
+
__name(rejectAmountBelowMin, "rejectAmountBelowMin");
|
|
4800
4570
|
function findActiveBlackout(windows, nowUnixSec) {
|
|
4801
4571
|
for (const w of windows) {
|
|
4802
4572
|
if (w.startUnixSec <= nowUnixSec && nowUnixSec < w.endUnixSec) return w;
|
|
4803
4573
|
}
|
|
4804
4574
|
return null;
|
|
4805
4575
|
}
|
|
4576
|
+
__name(findActiveBlackout, "findActiveBlackout");
|
|
4806
4577
|
function nextBlackoutEndAfter(windows, nowUnixSec) {
|
|
4807
4578
|
let earliest = null;
|
|
4808
4579
|
for (const w of windows) {
|
|
@@ -4812,6 +4583,7 @@ function nextBlackoutEndAfter(windows, nowUnixSec) {
|
|
|
4812
4583
|
}
|
|
4813
4584
|
return earliest;
|
|
4814
4585
|
}
|
|
4586
|
+
__name(nextBlackoutEndAfter, "nextBlackoutEndAfter");
|
|
4815
4587
|
var REDEMPTION_HISTORY_WINDOW_SEC = SECONDS_PER_DAY;
|
|
4816
4588
|
|
|
4817
4589
|
// src/redemption/policyProvider.ts
|
|
@@ -4821,6 +4593,9 @@ var import_core18 = require("@pafi-dev/core");
|
|
|
4821
4593
|
var import_core17 = require("@pafi-dev/core");
|
|
4822
4594
|
var DEFAULT_TIMEOUT_MS = 1e3;
|
|
4823
4595
|
var SettlementClient = class {
|
|
4596
|
+
static {
|
|
4597
|
+
__name(this, "SettlementClient");
|
|
4598
|
+
}
|
|
4824
4599
|
config;
|
|
4825
4600
|
constructor(config) {
|
|
4826
4601
|
if (!config.chainId) throw new Error("SettlementClient: chainId is required");
|
|
@@ -4840,10 +4615,7 @@ var SettlementClient = class {
|
|
|
4840
4615
|
const fetchFn = this.config.fetchImpl ?? fetch;
|
|
4841
4616
|
const url = `${this.config.baseUrl}/issuers/${encodeURIComponent(this.config.issuerId)}/redemption-policy`;
|
|
4842
4617
|
const controller = new AbortController();
|
|
4843
|
-
const timer = setTimeout(
|
|
4844
|
-
() => controller.abort(),
|
|
4845
|
-
this.config.fetchTimeoutMs
|
|
4846
|
-
);
|
|
4618
|
+
const timer = setTimeout(() => controller.abort(), this.config.fetchTimeoutMs);
|
|
4847
4619
|
let response;
|
|
4848
4620
|
try {
|
|
4849
4621
|
response = await fetchFn(url, {
|
|
@@ -4857,30 +4629,56 @@ var SettlementClient = class {
|
|
|
4857
4629
|
});
|
|
4858
4630
|
} catch (err) {
|
|
4859
4631
|
const isAbort = err instanceof Error && (err.name === "AbortError" || /aborted|timeout/i.test(err.message ?? ""));
|
|
4860
|
-
return {
|
|
4632
|
+
return {
|
|
4633
|
+
ok: false,
|
|
4634
|
+
reason: isAbort ? "TIMEOUT" : "NETWORK"
|
|
4635
|
+
};
|
|
4861
4636
|
} finally {
|
|
4862
4637
|
clearTimeout(timer);
|
|
4863
4638
|
}
|
|
4864
4639
|
if (response.status === 404) {
|
|
4865
|
-
return {
|
|
4640
|
+
return {
|
|
4641
|
+
ok: false,
|
|
4642
|
+
reason: "NOT_FOUND",
|
|
4643
|
+
status: 404
|
|
4644
|
+
};
|
|
4866
4645
|
}
|
|
4867
4646
|
if (response.status === 401 || response.status === 403) {
|
|
4868
|
-
return {
|
|
4647
|
+
return {
|
|
4648
|
+
ok: false,
|
|
4649
|
+
reason: "UNAUTHORIZED",
|
|
4650
|
+
status: response.status
|
|
4651
|
+
};
|
|
4869
4652
|
}
|
|
4870
4653
|
if (!response.ok) {
|
|
4871
|
-
return {
|
|
4654
|
+
return {
|
|
4655
|
+
ok: false,
|
|
4656
|
+
reason: "SERVER_ERROR",
|
|
4657
|
+
status: response.status
|
|
4658
|
+
};
|
|
4872
4659
|
}
|
|
4873
4660
|
let raw;
|
|
4874
4661
|
try {
|
|
4875
4662
|
raw = await response.json();
|
|
4876
4663
|
} catch {
|
|
4877
|
-
return {
|
|
4664
|
+
return {
|
|
4665
|
+
ok: false,
|
|
4666
|
+
reason: "INVALID_RESPONSE",
|
|
4667
|
+
status: response.status
|
|
4668
|
+
};
|
|
4878
4669
|
}
|
|
4879
4670
|
const parsed = parsePolicyDto(raw);
|
|
4880
4671
|
if (!parsed) {
|
|
4881
|
-
return {
|
|
4672
|
+
return {
|
|
4673
|
+
ok: false,
|
|
4674
|
+
reason: "INVALID_RESPONSE",
|
|
4675
|
+
status: response.status
|
|
4676
|
+
};
|
|
4882
4677
|
}
|
|
4883
|
-
return {
|
|
4678
|
+
return {
|
|
4679
|
+
ok: true,
|
|
4680
|
+
policy: parsed
|
|
4681
|
+
};
|
|
4884
4682
|
}
|
|
4885
4683
|
};
|
|
4886
4684
|
function parsePolicyDto(raw) {
|
|
@@ -4903,6 +4701,7 @@ function parsePolicyDto(raw) {
|
|
|
4903
4701
|
return null;
|
|
4904
4702
|
}
|
|
4905
4703
|
}
|
|
4704
|
+
__name(parsePolicyDto, "parsePolicyDto");
|
|
4906
4705
|
function normalizeBlackout(raw) {
|
|
4907
4706
|
if (!raw || typeof raw !== "object") return null;
|
|
4908
4707
|
const win = raw;
|
|
@@ -4915,6 +4714,7 @@ function normalizeBlackout(raw) {
|
|
|
4915
4714
|
reason: typeof win.reason === "string" ? win.reason : void 0
|
|
4916
4715
|
};
|
|
4917
4716
|
}
|
|
4717
|
+
__name(normalizeBlackout, "normalizeBlackout");
|
|
4918
4718
|
|
|
4919
4719
|
// src/redemption/defaults.ts
|
|
4920
4720
|
var PT_DECIMALS = 10n ** 18n;
|
|
@@ -4928,23 +4728,34 @@ var DEFAULT_REDEMPTION_POLICY = {
|
|
|
4928
4728
|
version: "default-v1"
|
|
4929
4729
|
};
|
|
4930
4730
|
function defaultPolicyFor(issuerId) {
|
|
4931
|
-
return {
|
|
4731
|
+
return {
|
|
4732
|
+
...DEFAULT_REDEMPTION_POLICY,
|
|
4733
|
+
issuerId
|
|
4734
|
+
};
|
|
4932
4735
|
}
|
|
4736
|
+
__name(defaultPolicyFor, "defaultPolicyFor");
|
|
4933
4737
|
|
|
4934
4738
|
// src/redemption/policyProvider.ts
|
|
4935
4739
|
var DEFAULT_CACHE_TTL_MS3 = 5 * 60 * 1e3;
|
|
4936
4740
|
var PolicyProviderUnavailableError = class extends import_core18.PafiSdkError {
|
|
4741
|
+
static {
|
|
4742
|
+
__name(this, "PolicyProviderUnavailableError");
|
|
4743
|
+
}
|
|
4937
4744
|
code = "POLICY_PROVIDER_UNAVAILABLE";
|
|
4938
4745
|
httpStatus = "service_unavailable";
|
|
4939
4746
|
details;
|
|
4940
4747
|
constructor(issuerId, reason) {
|
|
4941
|
-
super(
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4748
|
+
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.`);
|
|
4749
|
+
this.details = {
|
|
4750
|
+
issuerId,
|
|
4751
|
+
reason
|
|
4752
|
+
};
|
|
4945
4753
|
}
|
|
4946
4754
|
};
|
|
4947
4755
|
var PolicyProvider = class {
|
|
4756
|
+
static {
|
|
4757
|
+
__name(this, "PolicyProvider");
|
|
4758
|
+
}
|
|
4948
4759
|
client;
|
|
4949
4760
|
issuerId;
|
|
4950
4761
|
cacheTtlMs;
|
|
@@ -4963,7 +4774,10 @@ var PolicyProvider = class {
|
|
|
4963
4774
|
}
|
|
4964
4775
|
async getPolicy() {
|
|
4965
4776
|
const fresh = this.readCache();
|
|
4966
|
-
if (fresh) return {
|
|
4777
|
+
if (fresh) return {
|
|
4778
|
+
policy: fresh,
|
|
4779
|
+
source: "cache"
|
|
4780
|
+
};
|
|
4967
4781
|
if (this.inflight) return this.inflight;
|
|
4968
4782
|
this.inflight = this.fetchAndStore().finally(() => {
|
|
4969
4783
|
this.inflight = null;
|
|
@@ -4989,19 +4803,22 @@ var PolicyProvider = class {
|
|
|
4989
4803
|
policy: result.policy,
|
|
4990
4804
|
expiresAtMs: this.now() + this.cacheTtlMs
|
|
4991
4805
|
};
|
|
4992
|
-
return {
|
|
4806
|
+
return {
|
|
4807
|
+
policy: result.policy,
|
|
4808
|
+
source: "settlement"
|
|
4809
|
+
};
|
|
4993
4810
|
}
|
|
4994
4811
|
const reason = "reason" in result && typeof result.reason === "string" ? result.reason : "unknown";
|
|
4995
4812
|
if (this.onFetchFailure === "permissive-default") {
|
|
4996
|
-
this.onWarning?.(
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
4813
|
+
this.onWarning?.("PolicyProvider: settlement-api unreachable, falling back to permissive default. Pre-flight redeem limit is DEGRADED until settlement-api recovers.", {
|
|
4814
|
+
event: "policy_provider_fallback",
|
|
4815
|
+
issuerId: this.issuerId,
|
|
4816
|
+
reason
|
|
4817
|
+
});
|
|
4818
|
+
return {
|
|
4819
|
+
policy: defaultPolicyFor(this.issuerId),
|
|
4820
|
+
source: "default"
|
|
4821
|
+
};
|
|
5005
4822
|
}
|
|
5006
4823
|
throw new PolicyProviderUnavailableError(this.issuerId, reason);
|
|
5007
4824
|
}
|
|
@@ -5009,6 +4826,9 @@ var PolicyProvider = class {
|
|
|
5009
4826
|
|
|
5010
4827
|
// src/redemption/service.ts
|
|
5011
4828
|
var RedemptionService = class {
|
|
4829
|
+
static {
|
|
4830
|
+
__name(this, "RedemptionService");
|
|
4831
|
+
}
|
|
5012
4832
|
policyProvider;
|
|
5013
4833
|
historyStore;
|
|
5014
4834
|
nowUnixSec;
|
|
@@ -5025,17 +4845,16 @@ var RedemptionService = class {
|
|
|
5025
4845
|
const { policy, source } = await this.policyProvider.getPolicy();
|
|
5026
4846
|
const now = this.nowUnixSec();
|
|
5027
4847
|
const [redeemedLast24hPt, lastRedeemedAtUnixSec] = await Promise.all([
|
|
5028
|
-
this.historyStore.sumRedeemedSince(
|
|
5029
|
-
user,
|
|
5030
|
-
now - REDEMPTION_HISTORY_WINDOW_SEC,
|
|
5031
|
-
pointTokenAddress
|
|
5032
|
-
),
|
|
4848
|
+
this.historyStore.sumRedeemedSince(user, now - REDEMPTION_HISTORY_WINDOW_SEC, pointTokenAddress),
|
|
5033
4849
|
this.historyStore.getLastRedeemedAtUnixSec(user, pointTokenAddress)
|
|
5034
4850
|
]);
|
|
5035
4851
|
return evaluateRedemption({
|
|
5036
4852
|
policy,
|
|
5037
4853
|
policySource: source,
|
|
5038
|
-
history: {
|
|
4854
|
+
history: {
|
|
4855
|
+
redeemedLast24hPt,
|
|
4856
|
+
lastRedeemedAtUnixSec
|
|
4857
|
+
},
|
|
5039
4858
|
amountPt,
|
|
5040
4859
|
nowUnixSec: now
|
|
5041
4860
|
});
|
|
@@ -5062,16 +4881,18 @@ async function createIssuerService(config) {
|
|
|
5062
4881
|
if (!config.auth?.domain) {
|
|
5063
4882
|
throw new Error("createIssuerService: auth.domain is required");
|
|
5064
4883
|
}
|
|
5065
|
-
const rawAddresses = config.pointTokenAddresses && config.pointTokenAddresses.length > 0 ? config.pointTokenAddresses : config.pointTokenAddress ? [
|
|
4884
|
+
const rawAddresses = config.pointTokenAddresses && config.pointTokenAddresses.length > 0 ? config.pointTokenAddresses : config.pointTokenAddress ? [
|
|
4885
|
+
config.pointTokenAddress
|
|
4886
|
+
] : [];
|
|
5066
4887
|
if (rawAddresses.length === 0) {
|
|
5067
|
-
throw new Error(
|
|
5068
|
-
"createIssuerService: at least one of pointTokenAddress / pointTokenAddresses is required"
|
|
5069
|
-
);
|
|
4888
|
+
throw new Error("createIssuerService: at least one of pointTokenAddress / pointTokenAddresses is required");
|
|
5070
4889
|
}
|
|
5071
4890
|
const tokenAddresses = rawAddresses.map((a) => (0, import_viem15.getAddress)(a));
|
|
5072
4891
|
const ledger = config.ledger;
|
|
5073
4892
|
const sessionStore = config.sessionStore ?? new MemorySessionStore();
|
|
5074
|
-
const policy = config.policy ?? new DefaultPolicyEngine({
|
|
4893
|
+
const policy = config.policy ?? new DefaultPolicyEngine({
|
|
4894
|
+
ledger
|
|
4895
|
+
});
|
|
5075
4896
|
const authServiceConfig = {
|
|
5076
4897
|
sessionStore,
|
|
5077
4898
|
jwtSecret: config.auth.jwtSecret,
|
|
@@ -5099,9 +4920,7 @@ async function createIssuerService(config) {
|
|
|
5099
4920
|
const baseCursorStore = config.indexer?.cursorStore;
|
|
5100
4921
|
const sharedCursorWithMultipleTokens = baseCursorStore !== void 0 && typeof baseCursorStore.forKey !== "function" && tokenAddresses.length > 1;
|
|
5101
4922
|
if (sharedCursorWithMultipleTokens) {
|
|
5102
|
-
console.warn(
|
|
5103
|
-
`[@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.`
|
|
5104
|
-
);
|
|
4923
|
+
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.`);
|
|
5105
4924
|
}
|
|
5106
4925
|
const indexers = /* @__PURE__ */ new Map();
|
|
5107
4926
|
for (const tokenAddress of tokenAddresses) {
|
|
@@ -5117,9 +4936,7 @@ async function createIssuerService(config) {
|
|
|
5117
4936
|
indexerConfig.fromBlock = config.indexer.fromBlock;
|
|
5118
4937
|
}
|
|
5119
4938
|
if (baseCursorStore) {
|
|
5120
|
-
indexerConfig.cursorStore = typeof baseCursorStore.forKey === "function" ? baseCursorStore.forKey(
|
|
5121
|
-
`point-indexer:${tokenAddress.toLowerCase()}`
|
|
5122
|
-
) : baseCursorStore;
|
|
4939
|
+
indexerConfig.cursorStore = typeof baseCursorStore.forKey === "function" ? baseCursorStore.forKey(`point-indexer:${tokenAddress.toLowerCase()}`) : baseCursorStore;
|
|
5123
4940
|
}
|
|
5124
4941
|
if (config.indexer?.confirmations !== void 0) {
|
|
5125
4942
|
indexerConfig.confirmations = config.indexer.confirmations;
|
|
@@ -5190,9 +5007,7 @@ async function createIssuerService(config) {
|
|
|
5190
5007
|
if (config.indexer?.autoStart) {
|
|
5191
5008
|
const lock = config.indexer.singletonLock;
|
|
5192
5009
|
if (!lock) {
|
|
5193
|
-
console.warn(
|
|
5194
|
-
"[@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."
|
|
5195
|
-
);
|
|
5010
|
+
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.");
|
|
5196
5011
|
for (const idx of indexers.values()) {
|
|
5197
5012
|
idx.start();
|
|
5198
5013
|
}
|
|
@@ -5221,35 +5036,39 @@ async function createIssuerService(config) {
|
|
|
5221
5036
|
redemption
|
|
5222
5037
|
};
|
|
5223
5038
|
}
|
|
5039
|
+
__name(createIssuerService, "createIssuerService");
|
|
5224
5040
|
|
|
5225
5041
|
// src/issuer-state/validator.ts
|
|
5226
5042
|
var import_viem16 = require("viem");
|
|
5227
5043
|
var import_core20 = require("@pafi-dev/core");
|
|
5228
5044
|
var ISSUER_RECORD_TTL_MS = 1e4;
|
|
5229
5045
|
var IssuerStateValidator = class _IssuerStateValidator {
|
|
5230
|
-
|
|
5231
|
-
this
|
|
5232
|
-
this.registryAddress = registryAddress;
|
|
5046
|
+
static {
|
|
5047
|
+
__name(this, "IssuerStateValidator");
|
|
5233
5048
|
}
|
|
5234
5049
|
provider;
|
|
5235
5050
|
registryAddress;
|
|
5236
5051
|
pointTokenIssuerCache = /* @__PURE__ */ new Map();
|
|
5237
5052
|
stateCache = /* @__PURE__ */ new Map();
|
|
5238
5053
|
inflight = /* @__PURE__ */ new Map();
|
|
5054
|
+
constructor(provider, registryAddress) {
|
|
5055
|
+
this.provider = provider;
|
|
5056
|
+
this.registryAddress = registryAddress;
|
|
5057
|
+
}
|
|
5239
5058
|
/**
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5059
|
+
* Convenience factory — reads `registryAddress` from the SDK
|
|
5060
|
+
* `CONTRACT_ADDRESSES` map for the given chain.
|
|
5061
|
+
*/
|
|
5243
5062
|
static forChain(provider, chainId) {
|
|
5244
5063
|
const { issuerRegistry } = (0, import_core20.getContractAddresses)(chainId);
|
|
5245
5064
|
return new _IssuerStateValidator(provider, issuerRegistry);
|
|
5246
5065
|
}
|
|
5247
5066
|
/**
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5067
|
+
* Invalidate cached state for one PointToken, or everything if omitted.
|
|
5068
|
+
* Call after admin txs that change registry or cap settings — closes
|
|
5069
|
+
* the split-brain window described
|
|
5070
|
+
* passive TTL. Idempotent: safe to call when no entry exists.
|
|
5071
|
+
*/
|
|
5253
5072
|
invalidate(pointToken) {
|
|
5254
5073
|
if (pointToken) {
|
|
5255
5074
|
const key = (0, import_viem16.getAddress)(pointToken);
|
|
@@ -5263,9 +5082,9 @@ var IssuerStateValidator = class _IssuerStateValidator {
|
|
|
5263
5082
|
}
|
|
5264
5083
|
}
|
|
5265
5084
|
/**
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5085
|
+
* Resolve `PointToken.issuer()` once per token and memoize.
|
|
5086
|
+
* The issuer field is set at `initialize()` and never changes.
|
|
5087
|
+
*/
|
|
5269
5088
|
async getIssuerAddressForPointToken(pointToken) {
|
|
5270
5089
|
const key = (0, import_viem16.getAddress)(pointToken);
|
|
5271
5090
|
const cached = this.pointTokenIssuerCache.get(key);
|
|
@@ -5279,9 +5098,9 @@ var IssuerStateValidator = class _IssuerStateValidator {
|
|
|
5279
5098
|
return (0, import_viem16.getAddress)(issuer);
|
|
5280
5099
|
}
|
|
5281
5100
|
/**
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5101
|
+
* Read registry record + totalSupply, with 30s cache and in-flight
|
|
5102
|
+
* deduplication. Does NOT throw on inactive/missing — returns raw state.
|
|
5103
|
+
*/
|
|
5285
5104
|
async getIssuerState(pointToken) {
|
|
5286
5105
|
const tokenAddr = (0, import_viem16.getAddress)(pointToken);
|
|
5287
5106
|
const now = Date.now();
|
|
@@ -5302,49 +5121,41 @@ var IssuerStateValidator = class _IssuerStateValidator {
|
|
|
5302
5121
|
return promise;
|
|
5303
5122
|
}
|
|
5304
5123
|
/**
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5124
|
+
* Validate that `amount` PT can be minted on `pointToken` right now.
|
|
5125
|
+
*
|
|
5126
|
+
* Throws `IssuerStateError` with:
|
|
5127
|
+
* - `ISSUER_NOT_REGISTERED` — registry has no record for this issuer
|
|
5128
|
+
* - `ISSUER_INACTIVE` — issuer.active is false
|
|
5129
|
+
* - `MINT_CAP_EXCEEDED` — totalSupply + amount would exceed hardCap
|
|
5130
|
+
*
|
|
5131
|
+
* Returns the fetched state on success so callers can log without a
|
|
5132
|
+
* second RPC round-trip.
|
|
5133
|
+
*/
|
|
5315
5134
|
async preValidateMint(pointToken, amount) {
|
|
5316
5135
|
let state;
|
|
5317
5136
|
try {
|
|
5318
5137
|
state = await this.getIssuerState(pointToken);
|
|
5319
5138
|
} catch (err) {
|
|
5320
5139
|
if (err.message.includes("IssuerNotFound")) {
|
|
5321
|
-
throw new IssuerStateError(
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
{ pointToken }
|
|
5325
|
-
);
|
|
5140
|
+
throw new IssuerStateError("ISSUER_NOT_REGISTERED", `IssuerRegistry has no record for PointToken ${pointToken}`, {
|
|
5141
|
+
pointToken
|
|
5142
|
+
});
|
|
5326
5143
|
}
|
|
5327
5144
|
throw err;
|
|
5328
5145
|
}
|
|
5329
5146
|
const { issuer, equityCap, equitySupply, remaining } = state;
|
|
5330
5147
|
if (!issuer.active) {
|
|
5331
|
-
throw new IssuerStateError(
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
{ pointToken }
|
|
5335
|
-
);
|
|
5148
|
+
throw new IssuerStateError("ISSUER_INACTIVE", `Issuer "${issuer.name}" is deactivated on IssuerRegistry`, {
|
|
5149
|
+
pointToken
|
|
5150
|
+
});
|
|
5336
5151
|
}
|
|
5337
5152
|
if (equitySupply + amount > equityCap.hardCap) {
|
|
5338
|
-
throw new IssuerStateError(
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
equityMinted: equitySupply.toString(),
|
|
5345
|
-
remaining: remaining.toString()
|
|
5346
|
-
}
|
|
5347
|
-
);
|
|
5153
|
+
throw new IssuerStateError("MINT_CAP_EXCEEDED", `Requested ${amount} PT would exceed EQUITY mint cap. Cap=${equityCap.hardCap}, equityMinted=${equitySupply}, remaining=${remaining}`, {
|
|
5154
|
+
requested: amount.toString(),
|
|
5155
|
+
cap: equityCap.hardCap.toString(),
|
|
5156
|
+
equityMinted: equitySupply.toString(),
|
|
5157
|
+
remaining: remaining.toString()
|
|
5158
|
+
});
|
|
5348
5159
|
}
|
|
5349
5160
|
return state;
|
|
5350
5161
|
}
|
|
@@ -5354,7 +5165,9 @@ var IssuerStateValidator = class _IssuerStateValidator {
|
|
|
5354
5165
|
address: this.registryAddress,
|
|
5355
5166
|
abi: import_core20.issuerRegistryAbi,
|
|
5356
5167
|
functionName: "getIssuer",
|
|
5357
|
-
args: [
|
|
5168
|
+
args: [
|
|
5169
|
+
issuerAddr
|
|
5170
|
+
]
|
|
5358
5171
|
});
|
|
5359
5172
|
const issuer = {
|
|
5360
5173
|
signerAddress: issuerStruct.signerAddress,
|
|
@@ -5386,6 +5199,9 @@ var IssuerStateValidator = class _IssuerStateValidator {
|
|
|
5386
5199
|
|
|
5387
5200
|
// src/redemption/memoryHistoryStore.ts
|
|
5388
5201
|
var MemoryRedemptionHistoryStore = class {
|
|
5202
|
+
static {
|
|
5203
|
+
__name(this, "MemoryRedemptionHistoryStore");
|
|
5204
|
+
}
|
|
5389
5205
|
entries = [];
|
|
5390
5206
|
async sumRedeemedSince(user, sinceUnixSec, pointTokenAddress) {
|
|
5391
5207
|
const userKey = user.toLowerCase();
|
|
@@ -5421,7 +5237,7 @@ var MemoryRedemptionHistoryStore = class {
|
|
|
5421
5237
|
};
|
|
5422
5238
|
|
|
5423
5239
|
// src/index.ts
|
|
5424
|
-
var PAFI_ISSUER_SDK_VERSION = true ? "0.39.
|
|
5240
|
+
var PAFI_ISSUER_SDK_VERSION = true ? "0.39.3" : "dev";
|
|
5425
5241
|
// Annotate the CommonJS export names for ESM import in node:
|
|
5426
5242
|
0 && (module.exports = {
|
|
5427
5243
|
AdapterMisconfiguredError,
|