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