@pafi-dev/issuer 0.3.0-beta.9 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +164 -292
- package/dist/index.cjs +427 -310
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +136 -122
- package/dist/index.d.ts +136 -122
- package/dist/index.js +402 -286
- package/dist/index.js.map +1 -1
- package/package.json +11 -11
package/dist/index.cjs
CHANGED
|
@@ -28,15 +28,14 @@ __export(index_exports, {
|
|
|
28
28
|
FeeManager: () => FeeManager,
|
|
29
29
|
InMemoryCursorStore: () => InMemoryCursorStore,
|
|
30
30
|
IssuerApiHandlers: () => IssuerApiHandlers,
|
|
31
|
-
MemoryPointLedger: () => MemoryPointLedger,
|
|
32
31
|
MemorySessionStore: () => MemorySessionStore,
|
|
33
32
|
NonceManager: () => NonceManager,
|
|
34
33
|
PAFI_ISSUER_SDK_VERSION: () => PAFI_ISSUER_SDK_VERSION,
|
|
35
34
|
PTRedeemError: () => PTRedeemError,
|
|
36
35
|
PTRedeemHandler: () => PTRedeemHandler,
|
|
36
|
+
PafiBackendClient: () => PafiBackendClient,
|
|
37
37
|
PafiBackendError: () => PafiBackendError,
|
|
38
38
|
PointIndexer: () => PointIndexer,
|
|
39
|
-
PrivateKeySigner: () => PrivateKeySigner,
|
|
40
39
|
RelayError: () => RelayError,
|
|
41
40
|
RelayService: () => RelayService,
|
|
42
41
|
TopUpRedemptionError: () => TopUpRedemptionError,
|
|
@@ -48,204 +47,6 @@ __export(index_exports, {
|
|
|
48
47
|
});
|
|
49
48
|
module.exports = __toCommonJS(index_exports);
|
|
50
49
|
|
|
51
|
-
// src/ledger/memoryLedger.ts
|
|
52
|
-
var import_viem = require("viem");
|
|
53
|
-
var MemoryPointLedger = class {
|
|
54
|
-
balances = /* @__PURE__ */ new Map();
|
|
55
|
-
locks = /* @__PURE__ */ new Map();
|
|
56
|
-
nextLockId = 1;
|
|
57
|
-
now;
|
|
58
|
-
constructor(opts = {}) {
|
|
59
|
-
this.now = opts.now ?? (() => Date.now());
|
|
60
|
-
}
|
|
61
|
-
// -------------------------------------------------------------------------
|
|
62
|
-
// Read
|
|
63
|
-
// -------------------------------------------------------------------------
|
|
64
|
-
async getBalance(userAddress, tokenAddress) {
|
|
65
|
-
const user = (0, import_viem.getAddress)(userAddress);
|
|
66
|
-
const token = normalizeToken(tokenAddress);
|
|
67
|
-
this.purgeExpired();
|
|
68
|
-
const total = this.balances.get(balanceKey(user, token)) ?? 0n;
|
|
69
|
-
const locked = this.lockedTotalFor(user, token);
|
|
70
|
-
return total - locked;
|
|
71
|
-
}
|
|
72
|
-
async getLockedRequests(userAddress, tokenAddress) {
|
|
73
|
-
const user = (0, import_viem.getAddress)(userAddress);
|
|
74
|
-
const token = normalizeToken(tokenAddress);
|
|
75
|
-
this.purgeExpired();
|
|
76
|
-
const out = [];
|
|
77
|
-
for (const lock of this.locks.values()) {
|
|
78
|
-
if (lock.userAddress === user && lock.status === "PENDING" && (lock.tokenAddress ?? DEFAULT_TOKEN_KEY) === token) {
|
|
79
|
-
out.push({ ...lock });
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return out;
|
|
83
|
-
}
|
|
84
|
-
// -------------------------------------------------------------------------
|
|
85
|
-
// Write
|
|
86
|
-
// -------------------------------------------------------------------------
|
|
87
|
-
async creditBalance(userAddress, amount, _reason, tokenAddress) {
|
|
88
|
-
if (amount <= 0n) {
|
|
89
|
-
throw new Error("MemoryPointLedger: credit amount must be positive");
|
|
90
|
-
}
|
|
91
|
-
const user = (0, import_viem.getAddress)(userAddress);
|
|
92
|
-
const token = normalizeToken(tokenAddress);
|
|
93
|
-
const key = balanceKey(user, token);
|
|
94
|
-
const current = this.balances.get(key) ?? 0n;
|
|
95
|
-
this.balances.set(key, current + amount);
|
|
96
|
-
}
|
|
97
|
-
async lockForMinting(userAddress, amount, lockDurationMs, tokenAddress) {
|
|
98
|
-
if (amount <= 0n) {
|
|
99
|
-
throw new Error("MemoryPointLedger: lock amount must be positive");
|
|
100
|
-
}
|
|
101
|
-
if (lockDurationMs <= 0) {
|
|
102
|
-
throw new Error("MemoryPointLedger: lockDurationMs must be positive");
|
|
103
|
-
}
|
|
104
|
-
const user = (0, import_viem.getAddress)(userAddress);
|
|
105
|
-
const token = normalizeToken(tokenAddress);
|
|
106
|
-
this.purgeExpired();
|
|
107
|
-
const total = this.balances.get(balanceKey(user, token)) ?? 0n;
|
|
108
|
-
const alreadyLocked = this.lockedTotalFor(user, token);
|
|
109
|
-
const available = total - alreadyLocked;
|
|
110
|
-
if (available < amount) {
|
|
111
|
-
throw new Error(
|
|
112
|
-
`MemoryPointLedger: insufficient balance \u2014 available=${available}, requested=${amount}`
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
const lockId = `lock-${this.nextLockId++}`;
|
|
116
|
-
const now = this.now();
|
|
117
|
-
const lock = {
|
|
118
|
-
lockId,
|
|
119
|
-
userAddress: user,
|
|
120
|
-
amount,
|
|
121
|
-
status: "PENDING",
|
|
122
|
-
createdAt: now,
|
|
123
|
-
expiresAt: now + lockDurationMs
|
|
124
|
-
};
|
|
125
|
-
if (tokenAddress !== void 0) {
|
|
126
|
-
lock.tokenAddress = (0, import_viem.getAddress)(tokenAddress);
|
|
127
|
-
}
|
|
128
|
-
this.locks.set(lockId, lock);
|
|
129
|
-
return lockId;
|
|
130
|
-
}
|
|
131
|
-
async releaseLock(lockId) {
|
|
132
|
-
const lock = this.locks.get(lockId);
|
|
133
|
-
if (!lock) return;
|
|
134
|
-
if (lock.status === "PENDING") {
|
|
135
|
-
this.locks.delete(lockId);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
async deductBalance(userAddress, amount, txHash, tokenAddress) {
|
|
139
|
-
if (amount <= 0n) {
|
|
140
|
-
throw new Error("MemoryPointLedger: deduct amount must be positive");
|
|
141
|
-
}
|
|
142
|
-
const user = (0, import_viem.getAddress)(userAddress);
|
|
143
|
-
const token = normalizeToken(tokenAddress);
|
|
144
|
-
const key = balanceKey(user, token);
|
|
145
|
-
const current = this.balances.get(key) ?? 0n;
|
|
146
|
-
if (current < amount) {
|
|
147
|
-
throw new Error(
|
|
148
|
-
`MemoryPointLedger: cannot deduct ${amount} from balance ${current}`
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
this.balances.set(key, current - amount);
|
|
152
|
-
for (const lock of this.locks.values()) {
|
|
153
|
-
if (lock.userAddress === user && lock.status === "PENDING" && lock.amount === amount && (lock.tokenAddress ?? DEFAULT_TOKEN_KEY) === token) {
|
|
154
|
-
lock.status = "MINTED";
|
|
155
|
-
lock.txHash = txHash;
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
async updateMintStatus(lockId, status, txHash) {
|
|
161
|
-
const lock = this.locks.get(lockId);
|
|
162
|
-
if (!lock) {
|
|
163
|
-
throw new Error(`MemoryPointLedger: unknown lockId ${lockId}`);
|
|
164
|
-
}
|
|
165
|
-
lock.status = status;
|
|
166
|
-
if (txHash) lock.txHash = txHash;
|
|
167
|
-
}
|
|
168
|
-
// -------------------------------------------------------------------------
|
|
169
|
-
// v1.4 — Reverse flow (PT burn → off-chain credit)
|
|
170
|
-
// -------------------------------------------------------------------------
|
|
171
|
-
pendingCredits = /* @__PURE__ */ new Map();
|
|
172
|
-
nextCreditId = 1;
|
|
173
|
-
async reservePendingCredit(userAddress, amount, durationMs, tokenAddress) {
|
|
174
|
-
if (amount <= 0n) {
|
|
175
|
-
throw new Error(
|
|
176
|
-
"MemoryPointLedger: pending credit amount must be positive"
|
|
177
|
-
);
|
|
178
|
-
}
|
|
179
|
-
if (durationMs <= 0) {
|
|
180
|
-
throw new Error("MemoryPointLedger: durationMs must be positive");
|
|
181
|
-
}
|
|
182
|
-
const user = (0, import_viem.getAddress)(userAddress);
|
|
183
|
-
const lockId = `credit-${this.nextCreditId++}`;
|
|
184
|
-
const now = this.now();
|
|
185
|
-
this.pendingCredits.set(lockId, {
|
|
186
|
-
lockId,
|
|
187
|
-
userAddress: user,
|
|
188
|
-
amount,
|
|
189
|
-
tokenAddress: tokenAddress !== void 0 ? (0, import_viem.getAddress)(tokenAddress) : void 0,
|
|
190
|
-
createdAt: now,
|
|
191
|
-
expiresAt: now + durationMs,
|
|
192
|
-
status: "PENDING"
|
|
193
|
-
});
|
|
194
|
-
return lockId;
|
|
195
|
-
}
|
|
196
|
-
async resolveCreditByBurnTx(lockId, txHash) {
|
|
197
|
-
const credit = this.pendingCredits.get(lockId);
|
|
198
|
-
if (!credit) {
|
|
199
|
-
throw new Error(
|
|
200
|
-
`MemoryPointLedger: unknown pending credit lockId ${lockId}`
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
|
-
if (credit.status === "RESOLVED") {
|
|
204
|
-
if (credit.txHash === txHash) return;
|
|
205
|
-
throw new Error(
|
|
206
|
-
`MemoryPointLedger: credit ${lockId} already resolved with a different txHash`
|
|
207
|
-
);
|
|
208
|
-
}
|
|
209
|
-
const token = normalizeToken(credit.tokenAddress);
|
|
210
|
-
const key = balanceKey(credit.userAddress, token);
|
|
211
|
-
const current = this.balances.get(key) ?? 0n;
|
|
212
|
-
this.balances.set(key, current + credit.amount);
|
|
213
|
-
credit.status = "RESOLVED";
|
|
214
|
-
credit.txHash = txHash;
|
|
215
|
-
}
|
|
216
|
-
// -------------------------------------------------------------------------
|
|
217
|
-
// Internal helpers
|
|
218
|
-
// -------------------------------------------------------------------------
|
|
219
|
-
/**
|
|
220
|
-
* Auto-expire any PENDING lock past its expiry. Called lazily on every
|
|
221
|
-
* read/write so the in-memory state stays self-cleaning without a timer.
|
|
222
|
-
*/
|
|
223
|
-
purgeExpired() {
|
|
224
|
-
const now = this.now();
|
|
225
|
-
for (const lock of this.locks.values()) {
|
|
226
|
-
if (lock.status === "PENDING" && lock.expiresAt <= now) {
|
|
227
|
-
lock.status = "EXPIRED";
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
lockedTotalFor(userAddress, tokenKey) {
|
|
232
|
-
let total = 0n;
|
|
233
|
-
for (const lock of this.locks.values()) {
|
|
234
|
-
if (lock.userAddress === userAddress && lock.status === "PENDING" && (lock.tokenAddress ?? DEFAULT_TOKEN_KEY) === tokenKey) {
|
|
235
|
-
total += lock.amount;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
return total;
|
|
239
|
-
}
|
|
240
|
-
};
|
|
241
|
-
var DEFAULT_TOKEN_KEY = "default";
|
|
242
|
-
function normalizeToken(tokenAddress) {
|
|
243
|
-
return tokenAddress === void 0 ? DEFAULT_TOKEN_KEY : (0, import_viem.getAddress)(tokenAddress);
|
|
244
|
-
}
|
|
245
|
-
function balanceKey(user, tokenKey) {
|
|
246
|
-
return `${user}|${tokenKey}`;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
50
|
// src/policy/defaultPolicy.ts
|
|
250
51
|
var DefaultPolicyEngine = class {
|
|
251
52
|
ledger;
|
|
@@ -261,6 +62,13 @@ var DefaultPolicyEngine = class {
|
|
|
261
62
|
}
|
|
262
63
|
if (opts.verifyMintCap) this.verifyMintCap = opts.verifyMintCap;
|
|
263
64
|
if (opts.resolveIssuer) this.resolveIssuer = opts.resolveIssuer;
|
|
65
|
+
if (!opts.mintingOracleAddress || !opts.provider || !opts.verifyMintCap || !opts.resolveIssuer) {
|
|
66
|
+
if (process.env.NODE_ENV === "production") {
|
|
67
|
+
throw new Error(
|
|
68
|
+
"[PAFI] DefaultPolicyEngine: on-chain MintingOracle cap check is required in production. Configure mintingOracleAddress, provider, verifyMintCap, and resolveIssuer."
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
264
72
|
}
|
|
265
73
|
async evaluate(request) {
|
|
266
74
|
if (request.amount <= 0n) {
|
|
@@ -297,32 +105,9 @@ var DefaultPolicyEngine = class {
|
|
|
297
105
|
}
|
|
298
106
|
};
|
|
299
107
|
|
|
300
|
-
// src/signer/privateKeySigner.ts
|
|
301
|
-
var import_viem2 = require("viem");
|
|
302
|
-
var import_accounts = require("viem/accounts");
|
|
303
|
-
var import_core = require("@pafi-dev/core");
|
|
304
|
-
var PrivateKeySigner = class {
|
|
305
|
-
account;
|
|
306
|
-
walletClient;
|
|
307
|
-
constructor(opts) {
|
|
308
|
-
this.account = (0, import_accounts.privateKeyToAccount)(opts.privateKey);
|
|
309
|
-
this.walletClient = (0, import_viem2.createWalletClient)({
|
|
310
|
-
account: this.account,
|
|
311
|
-
chain: opts.chain,
|
|
312
|
-
transport: (0, import_viem2.http)(opts.rpcUrl)
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
async signMintRequest(domain, message) {
|
|
316
|
-
return (0, import_core.signMintRequest)(this.walletClient, domain, message);
|
|
317
|
-
}
|
|
318
|
-
async getAddress() {
|
|
319
|
-
return this.account.address;
|
|
320
|
-
}
|
|
321
|
-
};
|
|
322
|
-
|
|
323
108
|
// src/auth/memorySessionStore.ts
|
|
324
109
|
var import_node_crypto = require("crypto");
|
|
325
|
-
var
|
|
110
|
+
var import_viem = require("viem");
|
|
326
111
|
var DEFAULT_NONCE_TTL_MS = 5 * 60 * 1e3;
|
|
327
112
|
var MemorySessionStore = class {
|
|
328
113
|
nonces = /* @__PURE__ */ new Map();
|
|
@@ -332,6 +117,11 @@ var MemorySessionStore = class {
|
|
|
332
117
|
nonceTtlMs;
|
|
333
118
|
now;
|
|
334
119
|
constructor(opts = {}) {
|
|
120
|
+
if (process.env.NODE_ENV === "production") {
|
|
121
|
+
console.error(
|
|
122
|
+
"[PAFI] MemorySessionStore is not safe for multi-process/K8s deployments. Session revocations are NOT propagated across pods. Use a Redis-backed session store in production."
|
|
123
|
+
);
|
|
124
|
+
}
|
|
335
125
|
this.nonceTtlMs = opts.nonceTtlMs ?? DEFAULT_NONCE_TTL_MS;
|
|
336
126
|
this.now = opts.now ?? (() => Date.now());
|
|
337
127
|
}
|
|
@@ -358,7 +148,7 @@ var MemorySessionStore = class {
|
|
|
358
148
|
this.purgeExpiredSessions();
|
|
359
149
|
const normalized = {
|
|
360
150
|
...session,
|
|
361
|
-
userAddress: (0,
|
|
151
|
+
userAddress: (0, import_viem.getAddress)(session.userAddress)
|
|
362
152
|
};
|
|
363
153
|
this.sessions.set(session.tokenId, normalized);
|
|
364
154
|
}
|
|
@@ -376,7 +166,7 @@ var MemorySessionStore = class {
|
|
|
376
166
|
this.sessions.delete(tokenId);
|
|
377
167
|
}
|
|
378
168
|
async revokeAllSessions(userAddress) {
|
|
379
|
-
const key = (0,
|
|
169
|
+
const key = (0, import_viem.getAddress)(userAddress);
|
|
380
170
|
for (const [tokenId, session] of this.sessions.entries()) {
|
|
381
171
|
if (session.userAddress === key) {
|
|
382
172
|
this.sessions.delete(tokenId);
|
|
@@ -422,8 +212,8 @@ var NonceManager = class {
|
|
|
422
212
|
// src/auth/loginVerifier.ts
|
|
423
213
|
var import_node_crypto2 = require("crypto");
|
|
424
214
|
var import_jose = require("jose");
|
|
425
|
-
var
|
|
426
|
-
var
|
|
215
|
+
var import_viem2 = require("viem");
|
|
216
|
+
var import_core = require("@pafi-dev/core");
|
|
427
217
|
|
|
428
218
|
// src/auth/errors.ts
|
|
429
219
|
var AuthError = class extends Error {
|
|
@@ -446,8 +236,8 @@ var AuthService = class {
|
|
|
446
236
|
nonceManager;
|
|
447
237
|
now;
|
|
448
238
|
constructor(config) {
|
|
449
|
-
if (!config.jwtSecret || config.jwtSecret.length <
|
|
450
|
-
throw new Error("AuthService: jwtSecret must be at least
|
|
239
|
+
if (!config.jwtSecret || config.jwtSecret.length < 32) {
|
|
240
|
+
throw new Error("AuthService: jwtSecret must be at least 32 characters for HS256 security");
|
|
451
241
|
}
|
|
452
242
|
this.sessionStore = config.sessionStore;
|
|
453
243
|
this.jwtSecret = new TextEncoder().encode(config.jwtSecret);
|
|
@@ -471,11 +261,17 @@ var AuthService = class {
|
|
|
471
261
|
async login(message, signature) {
|
|
472
262
|
let parsed;
|
|
473
263
|
try {
|
|
474
|
-
parsed = (0,
|
|
264
|
+
parsed = (0, import_core.parseLoginMessage)(message);
|
|
475
265
|
} catch (err) {
|
|
476
266
|
const msg = err instanceof Error ? err.message : String(err);
|
|
477
267
|
throw new AuthError("INVALID_MESSAGE", `Could not parse login message: ${msg}`);
|
|
478
268
|
}
|
|
269
|
+
if (parsed.expirationTime == null) {
|
|
270
|
+
throw new AuthError(
|
|
271
|
+
"INVALID_MESSAGE",
|
|
272
|
+
"login message must include expirationTime"
|
|
273
|
+
);
|
|
274
|
+
}
|
|
479
275
|
if (parsed.domain !== this.domain) {
|
|
480
276
|
throw new AuthError(
|
|
481
277
|
"DOMAIN_MISMATCH",
|
|
@@ -498,7 +294,7 @@ var AuthService = class {
|
|
|
498
294
|
if (parsed.expirationTime && parsed.expirationTime.getTime() <= now.getTime()) {
|
|
499
295
|
throw new AuthError("MESSAGE_EXPIRED", "Login message has expired");
|
|
500
296
|
}
|
|
501
|
-
const verifyResult = await (0,
|
|
297
|
+
const verifyResult = await (0, import_core.verifyLoginMessage)(message, signature);
|
|
502
298
|
if (!verifyResult.valid) {
|
|
503
299
|
throw new AuthError(
|
|
504
300
|
"SIGNATURE_INVALID",
|
|
@@ -512,7 +308,7 @@ var AuthService = class {
|
|
|
512
308
|
"Nonce is unknown, expired, or already used"
|
|
513
309
|
);
|
|
514
310
|
}
|
|
515
|
-
const userAddress = (0,
|
|
311
|
+
const userAddress = (0, import_viem2.getAddress)(verifyResult.address);
|
|
516
312
|
const tokenId = (0, import_node_crypto2.randomBytes)(16).toString("hex");
|
|
517
313
|
const issuedAt = now;
|
|
518
314
|
const expiresAt = parseExpiry(issuedAt, this.jwtExpiresIn);
|
|
@@ -540,7 +336,11 @@ var AuthService = class {
|
|
|
540
336
|
if (payload.jti) {
|
|
541
337
|
await this.sessionStore.revokeSession(payload.jti);
|
|
542
338
|
}
|
|
543
|
-
} catch {
|
|
339
|
+
} catch (err) {
|
|
340
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
341
|
+
if (!msg.includes("not found") && !msg.includes("expired")) {
|
|
342
|
+
console.error("[PAFI] AuthService logout: session store error", err);
|
|
343
|
+
}
|
|
544
344
|
}
|
|
545
345
|
}
|
|
546
346
|
/**
|
|
@@ -579,7 +379,7 @@ var AuthService = class {
|
|
|
579
379
|
throw new AuthError("TOKEN_INVALID", "JWT payload is malformed");
|
|
580
380
|
}
|
|
581
381
|
return {
|
|
582
|
-
userAddress: (0,
|
|
382
|
+
userAddress: (0, import_viem2.getAddress)(userAddress),
|
|
583
383
|
chainId,
|
|
584
384
|
tokenId
|
|
585
385
|
};
|
|
@@ -630,8 +430,8 @@ var RelayError = class extends Error {
|
|
|
630
430
|
};
|
|
631
431
|
|
|
632
432
|
// src/relay/relayService.ts
|
|
633
|
-
var
|
|
634
|
-
var
|
|
433
|
+
var import_viem3 = require("viem");
|
|
434
|
+
var import_core2 = require("@pafi-dev/core");
|
|
635
435
|
var RelayService = class {
|
|
636
436
|
/**
|
|
637
437
|
* Build an unsigned UserOp for Scenario 1 (Mint) — sig-gated
|
|
@@ -665,9 +465,20 @@ var RelayService = class {
|
|
|
665
465
|
if (params.deadline <= 0n) {
|
|
666
466
|
throw new RelayError("ENCODE_FAILED", "prepareMint: deadline must be positive");
|
|
667
467
|
}
|
|
468
|
+
const nowSecs = BigInt(Math.floor(Date.now() / 1e3));
|
|
469
|
+
if (params.deadline <= nowSecs) {
|
|
470
|
+
throw new RelayError("ENCODE_FAILED", "prepareMint: deadline is in the past");
|
|
471
|
+
}
|
|
472
|
+
const MAX_DEADLINE_WINDOW = 3600n;
|
|
473
|
+
if (params.deadline > nowSecs + MAX_DEADLINE_WINDOW) {
|
|
474
|
+
throw new RelayError(
|
|
475
|
+
"ENCODE_FAILED",
|
|
476
|
+
"prepareMint: deadline exceeds maximum allowed window (1 hour)"
|
|
477
|
+
);
|
|
478
|
+
}
|
|
668
479
|
let minterSig;
|
|
669
480
|
try {
|
|
670
|
-
const sig = await (0,
|
|
481
|
+
const sig = await (0, import_core2.signMintRequest)(
|
|
671
482
|
params.issuerSignerWallet,
|
|
672
483
|
params.domain,
|
|
673
484
|
{
|
|
@@ -687,8 +498,8 @@ var RelayService = class {
|
|
|
687
498
|
}
|
|
688
499
|
let mintCallData;
|
|
689
500
|
try {
|
|
690
|
-
mintCallData = (0,
|
|
691
|
-
abi:
|
|
501
|
+
mintCallData = (0, import_viem3.encodeFunctionData)({
|
|
502
|
+
abi: import_core2.POINT_TOKEN_V2_ABI,
|
|
692
503
|
functionName: "mint",
|
|
693
504
|
args: [params.userAddress, params.amount, params.deadline, minterSig]
|
|
694
505
|
});
|
|
@@ -713,17 +524,23 @@ var RelayService = class {
|
|
|
713
524
|
"prepareMint: feeRecipient required when feeAmount > 0"
|
|
714
525
|
);
|
|
715
526
|
}
|
|
527
|
+
if (params.feeRecipient === "0x0000000000000000000000000000000000000000") {
|
|
528
|
+
throw new RelayError(
|
|
529
|
+
"ENCODE_FAILED",
|
|
530
|
+
"prepareMint: feeRecipient must not be zero address"
|
|
531
|
+
);
|
|
532
|
+
}
|
|
716
533
|
operations.push({
|
|
717
534
|
target: params.pointTokenAddress,
|
|
718
535
|
value: 0n,
|
|
719
|
-
data: (0,
|
|
720
|
-
abi:
|
|
536
|
+
data: (0, import_viem3.encodeFunctionData)({
|
|
537
|
+
abi: import_viem3.erc20Abi,
|
|
721
538
|
functionName: "transfer",
|
|
722
539
|
args: [params.feeRecipient, params.feeAmount]
|
|
723
540
|
})
|
|
724
541
|
});
|
|
725
542
|
}
|
|
726
|
-
return (0,
|
|
543
|
+
return (0, import_core2.buildPartialUserOperation)({
|
|
727
544
|
sender: params.userAddress,
|
|
728
545
|
nonce: params.aaNonce,
|
|
729
546
|
operations,
|
|
@@ -761,8 +578,8 @@ var RelayService = class {
|
|
|
761
578
|
if (!params.burnRequest || !params.burnerSignature) {
|
|
762
579
|
throw new Error("burnWithSig requires burnRequest + burnerSignature");
|
|
763
580
|
}
|
|
764
|
-
burnCallData = (0,
|
|
765
|
-
abi:
|
|
581
|
+
burnCallData = (0, import_viem3.encodeFunctionData)({
|
|
582
|
+
abi: import_core2.POINT_TOKEN_V2_ABI,
|
|
766
583
|
functionName: "burn",
|
|
767
584
|
args: [
|
|
768
585
|
params.burnRequest.from,
|
|
@@ -772,8 +589,8 @@ var RelayService = class {
|
|
|
772
589
|
]
|
|
773
590
|
});
|
|
774
591
|
} else {
|
|
775
|
-
burnCallData = (0,
|
|
776
|
-
abi:
|
|
592
|
+
burnCallData = (0, import_viem3.encodeFunctionData)({
|
|
593
|
+
abi: import_core2.POINT_TOKEN_V2_ABI,
|
|
777
594
|
functionName: "burn",
|
|
778
595
|
args: [params.userAddress, params.amount]
|
|
779
596
|
});
|
|
@@ -792,7 +609,7 @@ var RelayService = class {
|
|
|
792
609
|
data: burnCallData
|
|
793
610
|
}
|
|
794
611
|
];
|
|
795
|
-
return (0,
|
|
612
|
+
return (0, import_core2.buildPartialUserOperation)({
|
|
796
613
|
sender: params.userAddress,
|
|
797
614
|
nonce: params.aaNonce,
|
|
798
615
|
operations,
|
|
@@ -811,11 +628,14 @@ function errorMessage(err) {
|
|
|
811
628
|
// src/relay/feeManager.ts
|
|
812
629
|
var DEFAULT_GAS_UNITS = 500000n;
|
|
813
630
|
var DEFAULT_PREMIUM_BPS = 12e3;
|
|
814
|
-
var FeeManager = class {
|
|
631
|
+
var FeeManager = class _FeeManager {
|
|
815
632
|
provider;
|
|
816
633
|
gasUnits;
|
|
817
634
|
gasPremiumBps;
|
|
818
635
|
quoteNativeToFee;
|
|
636
|
+
cachedFee = null;
|
|
637
|
+
cacheExpiresAt = 0;
|
|
638
|
+
static CACHE_TTL_MS = 1e4;
|
|
819
639
|
constructor(config) {
|
|
820
640
|
if (!config.provider) throw new Error("FeeManager: provider required");
|
|
821
641
|
if (!config.quoteNativeToFee)
|
|
@@ -838,10 +658,17 @@ var FeeManager = class {
|
|
|
838
658
|
* currency depends on how the caller wired `quoteNativeToFee`.
|
|
839
659
|
*/
|
|
840
660
|
async estimateGasFee() {
|
|
661
|
+
const now = Date.now();
|
|
662
|
+
if (this.cachedFee !== null && now < this.cacheExpiresAt) {
|
|
663
|
+
return this.cachedFee;
|
|
664
|
+
}
|
|
841
665
|
const gasPrice = await this.provider.getGasPrice();
|
|
842
666
|
const nativeCost = gasPrice * this.gasUnits;
|
|
843
667
|
const withPremium = nativeCost * BigInt(this.gasPremiumBps) / 10000n;
|
|
844
|
-
|
|
668
|
+
const fee = await this.quoteNativeToFee(withPremium);
|
|
669
|
+
this.cachedFee = fee;
|
|
670
|
+
this.cacheExpiresAt = now + _FeeManager.CACHE_TTL_MS;
|
|
671
|
+
return fee;
|
|
845
672
|
}
|
|
846
673
|
};
|
|
847
674
|
|
|
@@ -857,8 +684,8 @@ var InMemoryCursorStore = class {
|
|
|
857
684
|
};
|
|
858
685
|
|
|
859
686
|
// src/indexer/pointIndexer.ts
|
|
860
|
-
var
|
|
861
|
-
var TRANSFER_EVENT = (0,
|
|
687
|
+
var import_viem4 = require("viem");
|
|
688
|
+
var TRANSFER_EVENT = (0, import_viem4.parseAbiItem)(
|
|
862
689
|
"event Transfer(address indexed from, address indexed to, uint256 value)"
|
|
863
690
|
);
|
|
864
691
|
var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
@@ -928,7 +755,8 @@ var PointIndexer = class {
|
|
|
928
755
|
return;
|
|
929
756
|
}
|
|
930
757
|
await this.processBlockRange(from, safeHead);
|
|
931
|
-
} catch {
|
|
758
|
+
} catch (err) {
|
|
759
|
+
console.error("[PAFI] PointIndexer tick error:", err);
|
|
932
760
|
}
|
|
933
761
|
this.scheduleNext();
|
|
934
762
|
}
|
|
@@ -978,10 +806,10 @@ var PointIndexer = class {
|
|
|
978
806
|
for (const log of logs) {
|
|
979
807
|
const args = log.args;
|
|
980
808
|
if (!args.from || !args.to || args.value === void 0) continue;
|
|
981
|
-
if ((0,
|
|
809
|
+
if ((0, import_viem4.getAddress)(args.from) !== ZERO_ADDRESS) continue;
|
|
982
810
|
if (log.blockNumber === null || log.transactionHash === null) continue;
|
|
983
811
|
out.push({
|
|
984
|
-
to: (0,
|
|
812
|
+
to: (0, import_viem4.getAddress)(args.to),
|
|
985
813
|
amount: args.value,
|
|
986
814
|
blockNumber: log.blockNumber,
|
|
987
815
|
txHash: log.transactionHash,
|
|
@@ -1037,8 +865,8 @@ function pickMatchingLock(locks, amount) {
|
|
|
1037
865
|
}
|
|
1038
866
|
|
|
1039
867
|
// src/indexer/burnIndexer.ts
|
|
1040
|
-
var
|
|
1041
|
-
var TRANSFER_EVENT2 = (0,
|
|
868
|
+
var import_viem5 = require("viem");
|
|
869
|
+
var TRANSFER_EVENT2 = (0, import_viem5.parseAbiItem)(
|
|
1042
870
|
"event Transfer(address indexed from, address indexed to, uint256 value)"
|
|
1043
871
|
);
|
|
1044
872
|
var ZERO_ADDRESS2 = "0x0000000000000000000000000000000000000000";
|
|
@@ -1054,18 +882,7 @@ var BurnIndexer = class {
|
|
|
1054
882
|
confirmations;
|
|
1055
883
|
batchSize;
|
|
1056
884
|
pollIntervalMs;
|
|
1057
|
-
|
|
1058
|
-
* Caller-supplied matcher. Return the lockId to resolve for a given
|
|
1059
|
-
* burn event, or `undefined` to skip. Runs synchronously via the
|
|
1060
|
-
* ledger's query path.
|
|
1061
|
-
*
|
|
1062
|
-
* Default: try `ledger.resolveCreditByBurnTx` keyed on a synthetic
|
|
1063
|
-
* lock id `burn-${from}-${amount}` — the in-memory ledger assigns
|
|
1064
|
-
* incrementing IDs so callers with the memory ledger must provide a
|
|
1065
|
-
* custom matcher. Real DB-backed ledgers override this to JOIN on
|
|
1066
|
-
* their `pending_credits` table.
|
|
1067
|
-
*/
|
|
1068
|
-
matchLockId = async () => void 0;
|
|
885
|
+
matchLockId;
|
|
1069
886
|
running = false;
|
|
1070
887
|
timer;
|
|
1071
888
|
constructor(config) {
|
|
@@ -1083,6 +900,12 @@ var BurnIndexer = class {
|
|
|
1083
900
|
);
|
|
1084
901
|
this.batchSize = BigInt(config.batchSize ?? Number(DEFAULT_BATCH_SIZE2));
|
|
1085
902
|
this.pollIntervalMs = config.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS2;
|
|
903
|
+
if (!config.matchLockId) {
|
|
904
|
+
throw new Error(
|
|
905
|
+
"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."
|
|
906
|
+
);
|
|
907
|
+
}
|
|
908
|
+
this.matchLockId = config.matchLockId;
|
|
1086
909
|
}
|
|
1087
910
|
start() {
|
|
1088
911
|
if (this.running) return;
|
|
@@ -1112,7 +935,8 @@ var BurnIndexer = class {
|
|
|
1112
935
|
return;
|
|
1113
936
|
}
|
|
1114
937
|
await this.processBlockRange(from, safeHead);
|
|
1115
|
-
} catch {
|
|
938
|
+
} catch (err) {
|
|
939
|
+
console.error("[PAFI] BurnIndexer tick error:", err);
|
|
1116
940
|
}
|
|
1117
941
|
this.scheduleNext();
|
|
1118
942
|
}
|
|
@@ -1157,10 +981,10 @@ var BurnIndexer = class {
|
|
|
1157
981
|
for (const log of logs) {
|
|
1158
982
|
const args = log.args;
|
|
1159
983
|
if (!args.from || !args.to || args.value === void 0) continue;
|
|
1160
|
-
if ((0,
|
|
984
|
+
if ((0, import_viem5.getAddress)(args.to) !== ZERO_ADDRESS2) continue;
|
|
1161
985
|
if (log.blockNumber === null || log.transactionHash === null) continue;
|
|
1162
986
|
out.push({
|
|
1163
|
-
from: (0,
|
|
987
|
+
from: (0, import_viem5.getAddress)(args.from),
|
|
1164
988
|
amount: args.value,
|
|
1165
989
|
blockNumber: log.blockNumber,
|
|
1166
990
|
txHash: log.transactionHash,
|
|
@@ -1175,21 +999,28 @@ var BurnIndexer = class {
|
|
|
1175
999
|
* log + skip.
|
|
1176
1000
|
*/
|
|
1177
1001
|
async finalize(evt) {
|
|
1002
|
+
const txHash = evt.txHash;
|
|
1178
1003
|
const lockId = await this.matchLockId(evt);
|
|
1179
|
-
if (
|
|
1004
|
+
if (lockId === void 0) {
|
|
1005
|
+
console.warn(
|
|
1006
|
+
"[PAFI] BurnIndexer: matchLockId returned undefined for burn tx " + txHash + ". This burn will NOT be credited. Implement matchLockId to map burn events to lock IDs."
|
|
1007
|
+
);
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1180
1010
|
if (!this.ledger.resolveCreditByBurnTx) {
|
|
1181
1011
|
return;
|
|
1182
1012
|
}
|
|
1183
1013
|
try {
|
|
1184
1014
|
await this.ledger.resolveCreditByBurnTx(lockId, evt.txHash);
|
|
1185
|
-
} catch {
|
|
1015
|
+
} catch (err) {
|
|
1016
|
+
console.error("[PAFI] BurnIndexer finalize error \u2014 credit may be lost:", err);
|
|
1186
1017
|
}
|
|
1187
1018
|
}
|
|
1188
1019
|
};
|
|
1189
1020
|
|
|
1190
1021
|
// src/api/handlers.ts
|
|
1191
|
-
var
|
|
1192
|
-
var
|
|
1022
|
+
var import_viem6 = require("viem");
|
|
1023
|
+
var import_core3 = require("@pafi-dev/core");
|
|
1193
1024
|
var IssuerApiHandlers = class {
|
|
1194
1025
|
authService;
|
|
1195
1026
|
ledger;
|
|
@@ -1199,15 +1030,12 @@ var IssuerApiHandlers = class {
|
|
|
1199
1030
|
* validate the request's `pointTokenAddress` against this set.
|
|
1200
1031
|
*/
|
|
1201
1032
|
supportedTokens;
|
|
1202
|
-
/** First supported token — used as default when a handler doesn't
|
|
1203
|
-
* receive a `pointTokenAddress` in the request (shouldn't happen in
|
|
1204
|
-
* practice, but keeps type-narrowing happy). */
|
|
1205
|
-
defaultToken;
|
|
1206
1033
|
chainId;
|
|
1207
1034
|
contracts;
|
|
1208
1035
|
pafiWebUrl;
|
|
1209
1036
|
feeManager;
|
|
1210
1037
|
poolsProvider;
|
|
1038
|
+
claim;
|
|
1211
1039
|
constructor(config) {
|
|
1212
1040
|
this.authService = config.authService;
|
|
1213
1041
|
this.ledger = config.ledger;
|
|
@@ -1218,14 +1046,14 @@ var IssuerApiHandlers = class {
|
|
|
1218
1046
|
"IssuerApiHandlers: pointTokenAddress or pointTokenAddresses required"
|
|
1219
1047
|
);
|
|
1220
1048
|
}
|
|
1221
|
-
const normalized = raw.map((a) => (0,
|
|
1049
|
+
const normalized = raw.map((a) => (0, import_viem6.getAddress)(a));
|
|
1222
1050
|
this.supportedTokens = new Set(normalized);
|
|
1223
|
-
this.defaultToken = normalized[0];
|
|
1224
1051
|
this.chainId = config.chainId;
|
|
1225
1052
|
this.contracts = config.contracts;
|
|
1226
1053
|
if (config.pafiWebUrl) this.pafiWebUrl = config.pafiWebUrl;
|
|
1227
1054
|
if (config.feeManager) this.feeManager = config.feeManager;
|
|
1228
1055
|
if (config.poolsProvider) this.poolsProvider = config.poolsProvider;
|
|
1056
|
+
if (config.claim) this.claim = config.claim;
|
|
1229
1057
|
}
|
|
1230
1058
|
// =========================================================================
|
|
1231
1059
|
// Public handlers (no auth required)
|
|
@@ -1240,6 +1068,12 @@ var IssuerApiHandlers = class {
|
|
|
1240
1068
|
if (!body || typeof body.message !== "string" || body.message.length === 0 || typeof body.signature !== "string" || body.signature.length <= 2) {
|
|
1241
1069
|
throw new Error("handleLogin: message and signature are required");
|
|
1242
1070
|
}
|
|
1071
|
+
if (body.message.length > 4096) {
|
|
1072
|
+
throw new Error("message too long");
|
|
1073
|
+
}
|
|
1074
|
+
if (body.signature.length > 260) {
|
|
1075
|
+
throw new Error("signature too long");
|
|
1076
|
+
}
|
|
1243
1077
|
const result = await this.authService.login(body.message, body.signature);
|
|
1244
1078
|
return {
|
|
1245
1079
|
token: result.token,
|
|
@@ -1254,9 +1088,12 @@ var IssuerApiHandlers = class {
|
|
|
1254
1088
|
* needs to build EIP-712 messages and interact with on-chain.
|
|
1255
1089
|
*/
|
|
1256
1090
|
async handleConfig(chainId) {
|
|
1091
|
+
if (!Number.isInteger(chainId) || chainId <= 0) {
|
|
1092
|
+
throw new Error("invalid chainId");
|
|
1093
|
+
}
|
|
1257
1094
|
if (chainId !== this.chainId) {
|
|
1258
1095
|
throw new Error(
|
|
1259
|
-
`handleConfig: unsupported chainId ${chainId}
|
|
1096
|
+
`handleConfig: unsupported chainId ${chainId}`
|
|
1260
1097
|
);
|
|
1261
1098
|
}
|
|
1262
1099
|
const contracts = {
|
|
@@ -1319,25 +1156,25 @@ var IssuerApiHandlers = class {
|
|
|
1319
1156
|
`handleUser: unsupported chainId ${request.chainId}`
|
|
1320
1157
|
);
|
|
1321
1158
|
}
|
|
1322
|
-
const normalizedAuthed = (0,
|
|
1323
|
-
const normalizedRequest = (0,
|
|
1159
|
+
const normalizedAuthed = (0, import_viem6.getAddress)(userAddress);
|
|
1160
|
+
const normalizedRequest = (0, import_viem6.getAddress)(request.userAddress);
|
|
1324
1161
|
if (normalizedAuthed !== normalizedRequest) {
|
|
1325
1162
|
throw new Error(
|
|
1326
1163
|
"handleUser: request userAddress must match authenticated user"
|
|
1327
1164
|
);
|
|
1328
1165
|
}
|
|
1329
|
-
const pointToken = (0,
|
|
1166
|
+
const pointToken = (0, import_viem6.getAddress)(request.pointTokenAddress);
|
|
1330
1167
|
if (!this.supportedTokens.has(pointToken)) {
|
|
1331
1168
|
throw new Error(
|
|
1332
1169
|
`handleUser: unsupported pointToken ${pointToken}`
|
|
1333
1170
|
);
|
|
1334
1171
|
}
|
|
1335
1172
|
const [mintRequestNonce, receiverConsentNonce, offChainBalance, onChainBalance, minter] = await Promise.all([
|
|
1336
|
-
(0,
|
|
1337
|
-
(0,
|
|
1173
|
+
(0, import_core3.getMintRequestNonce)(this.provider, pointToken, normalizedAuthed),
|
|
1174
|
+
(0, import_core3.getReceiverConsentNonce)(this.provider, pointToken, normalizedAuthed),
|
|
1338
1175
|
this.ledger.getBalance(normalizedAuthed, pointToken),
|
|
1339
|
-
(0,
|
|
1340
|
-
(0,
|
|
1176
|
+
(0, import_core3.getPointTokenBalance)(this.provider, pointToken, normalizedAuthed),
|
|
1177
|
+
(0, import_core3.isMinter)(this.provider, pointToken, normalizedAuthed)
|
|
1341
1178
|
]);
|
|
1342
1179
|
return {
|
|
1343
1180
|
mintRequestNonce,
|
|
@@ -1369,19 +1206,32 @@ var IssuerApiHandlers = class {
|
|
|
1369
1206
|
`handleBuildConsentTypedData: unsupported chainId ${request.chainId}`
|
|
1370
1207
|
);
|
|
1371
1208
|
}
|
|
1372
|
-
const pointToken = (0,
|
|
1209
|
+
const pointToken = (0, import_viem6.getAddress)(request.pointTokenAddress);
|
|
1373
1210
|
if (!this.supportedTokens.has(pointToken)) {
|
|
1374
1211
|
throw new Error(
|
|
1375
1212
|
`handleBuildConsentTypedData: unsupported pointToken ${pointToken}`
|
|
1376
1213
|
);
|
|
1377
1214
|
}
|
|
1378
|
-
const
|
|
1215
|
+
const consent = request.receiverConsent;
|
|
1216
|
+
if ((0, import_viem6.getAddress)(consent.originalReceiver) !== (0, import_viem6.getAddress)(userAddress)) {
|
|
1217
|
+
throw new Error(
|
|
1218
|
+
"handleBuildConsentTypedData: receiverConsent.originalReceiver must match authenticated user"
|
|
1219
|
+
);
|
|
1220
|
+
}
|
|
1221
|
+
if (consent.amount <= 0n) {
|
|
1222
|
+
throw new Error("handleBuildConsentTypedData: amount must be positive");
|
|
1223
|
+
}
|
|
1224
|
+
const nowSecs = BigInt(Math.floor(Date.now() / 1e3));
|
|
1225
|
+
if (consent.deadline <= nowSecs) {
|
|
1226
|
+
throw new Error("handleBuildConsentTypedData: deadline is in the past");
|
|
1227
|
+
}
|
|
1228
|
+
const name = await (0, import_core3.getTokenName)(this.provider, pointToken);
|
|
1379
1229
|
const domain = {
|
|
1380
1230
|
name,
|
|
1381
1231
|
verifyingContract: pointToken,
|
|
1382
1232
|
chainId: this.chainId
|
|
1383
1233
|
};
|
|
1384
|
-
const typedData = (0,
|
|
1234
|
+
const typedData = (0, import_core3.buildReceiverConsentTypedData)(domain, consent);
|
|
1385
1235
|
return {
|
|
1386
1236
|
typedData: {
|
|
1387
1237
|
domain: typedData.domain,
|
|
@@ -1391,11 +1241,96 @@ var IssuerApiHandlers = class {
|
|
|
1391
1241
|
}
|
|
1392
1242
|
};
|
|
1393
1243
|
}
|
|
1244
|
+
/**
|
|
1245
|
+
* `POST /claim`
|
|
1246
|
+
*
|
|
1247
|
+
* Policy gate + ledger lock + MintRequest signing in a single atomic
|
|
1248
|
+
* step. Returns an unsigned UserOp the frontend attaches paymaster data
|
|
1249
|
+
* to and submits via EIP-7702 + Bundler.
|
|
1250
|
+
*
|
|
1251
|
+
* Order of operations:
|
|
1252
|
+
* 1. Validate request fields.
|
|
1253
|
+
* 2. policy.evaluate() — throws if denied; cannot be bypassed.
|
|
1254
|
+
* 3. ledger.lockForMinting() — reserves the balance.
|
|
1255
|
+
* 4. Read on-chain mintRequestNonce + token name in parallel.
|
|
1256
|
+
* 5. relayService.prepareMint() — sign MintRequest + encode UserOp.
|
|
1257
|
+
* 6. On any error after step 3, release the lock before re-throwing.
|
|
1258
|
+
*/
|
|
1259
|
+
async handleClaim(userAddress, request) {
|
|
1260
|
+
if (!this.claim) {
|
|
1261
|
+
throw new Error("handleClaim: claim is not configured on this issuer");
|
|
1262
|
+
}
|
|
1263
|
+
if (request.chainId !== this.chainId) {
|
|
1264
|
+
throw new Error(`handleClaim: unsupported chainId ${request.chainId}`);
|
|
1265
|
+
}
|
|
1266
|
+
const pointToken = (0, import_viem6.getAddress)(request.pointTokenAddress);
|
|
1267
|
+
if (!this.supportedTokens.has(pointToken)) {
|
|
1268
|
+
throw new Error(`handleClaim: unsupported pointToken ${pointToken}`);
|
|
1269
|
+
}
|
|
1270
|
+
if (request.amount <= 0n) {
|
|
1271
|
+
throw new Error("handleClaim: amount must be positive");
|
|
1272
|
+
}
|
|
1273
|
+
const nowSecs = BigInt(Math.floor(Date.now() / 1e3));
|
|
1274
|
+
if (request.deadline <= nowSecs) {
|
|
1275
|
+
throw new Error("handleClaim: deadline is in the past");
|
|
1276
|
+
}
|
|
1277
|
+
const { policy, relayService, issuerSignerWallet, batchExecutorAddress } = this.claim;
|
|
1278
|
+
const lockDurationMs = this.claim.lockDurationMs ?? 15 * 60 * 1e3;
|
|
1279
|
+
const normalizedUser = (0, import_viem6.getAddress)(userAddress);
|
|
1280
|
+
const decision = await policy.evaluate({
|
|
1281
|
+
userAddress: normalizedUser,
|
|
1282
|
+
amount: request.amount,
|
|
1283
|
+
pointTokenAddress: pointToken,
|
|
1284
|
+
chainId: this.chainId
|
|
1285
|
+
});
|
|
1286
|
+
if (!decision.approved) {
|
|
1287
|
+
throw new Error(`handleClaim: policy denied \u2014 ${decision.reason ?? "no reason given"}`);
|
|
1288
|
+
}
|
|
1289
|
+
const lockId = await this.ledger.lockForMinting(
|
|
1290
|
+
normalizedUser,
|
|
1291
|
+
request.amount,
|
|
1292
|
+
lockDurationMs,
|
|
1293
|
+
pointToken
|
|
1294
|
+
);
|
|
1295
|
+
try {
|
|
1296
|
+
const [mintRequestNonce, tokenName] = await Promise.all([
|
|
1297
|
+
(0, import_core3.getMintRequestNonce)(this.provider, pointToken, normalizedUser),
|
|
1298
|
+
(0, import_core3.getTokenName)(this.provider, pointToken)
|
|
1299
|
+
]);
|
|
1300
|
+
const domain = {
|
|
1301
|
+
name: tokenName,
|
|
1302
|
+
verifyingContract: pointToken,
|
|
1303
|
+
chainId: this.chainId
|
|
1304
|
+
};
|
|
1305
|
+
const userOp = await relayService.prepareMint({
|
|
1306
|
+
userAddress: normalizedUser,
|
|
1307
|
+
aaNonce: request.aaNonce,
|
|
1308
|
+
batchExecutorAddress,
|
|
1309
|
+
pointTokenAddress: pointToken,
|
|
1310
|
+
amount: request.amount,
|
|
1311
|
+
issuerSignerWallet,
|
|
1312
|
+
domain,
|
|
1313
|
+
mintRequestNonce,
|
|
1314
|
+
deadline: request.deadline,
|
|
1315
|
+
feeAmount: request.feeAmount,
|
|
1316
|
+
feeRecipient: request.feeRecipient
|
|
1317
|
+
});
|
|
1318
|
+
return {
|
|
1319
|
+
lockId,
|
|
1320
|
+
userOp,
|
|
1321
|
+
expiresInSeconds: Math.floor(lockDurationMs / 1e3)
|
|
1322
|
+
};
|
|
1323
|
+
} catch (err) {
|
|
1324
|
+
await this.ledger.releaseLock(lockId).catch(() => {
|
|
1325
|
+
});
|
|
1326
|
+
throw err;
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1394
1329
|
};
|
|
1395
1330
|
|
|
1396
1331
|
// src/api/handlers/ptRedeemHandler.ts
|
|
1397
|
-
var
|
|
1398
|
-
var
|
|
1332
|
+
var import_viem7 = require("viem");
|
|
1333
|
+
var import_core4 = require("@pafi-dev/core");
|
|
1399
1334
|
var DEFAULT_REDEEM_LOCK_MS = 15 * 60 * 1e3;
|
|
1400
1335
|
var DEFAULT_SIG_DEADLINE_SEC = 15 * 60;
|
|
1401
1336
|
var PTRedeemError = class extends Error {
|
|
@@ -1434,16 +1369,25 @@ var PTRedeemHandler = class {
|
|
|
1434
1369
|
this.ledger = config.ledger;
|
|
1435
1370
|
this.relayService = config.relayService;
|
|
1436
1371
|
this.provider = config.provider;
|
|
1437
|
-
this.pointTokenAddress = (0,
|
|
1438
|
-
this.batchExecutorAddress = (0,
|
|
1372
|
+
this.pointTokenAddress = (0, import_viem7.getAddress)(config.pointTokenAddress);
|
|
1373
|
+
this.batchExecutorAddress = (0, import_viem7.getAddress)(config.batchExecutorAddress);
|
|
1439
1374
|
this.chainId = config.chainId;
|
|
1440
1375
|
this.domain = config.domain;
|
|
1441
1376
|
this.burnerSignerWallet = config.burnerSignerWallet;
|
|
1377
|
+
if (this.burnerSignerWallet?.account?.type === "local") {
|
|
1378
|
+
console.warn("[PAFI] PTRedeemHandler: burnerSignerWallet uses a local (private key) account. Use a KMS-backed signer in production.");
|
|
1379
|
+
}
|
|
1442
1380
|
this.redeemLockDurationMs = config.redeemLockDurationMs ?? DEFAULT_REDEEM_LOCK_MS;
|
|
1443
1381
|
this.signatureDeadlineSeconds = config.signatureDeadlineSeconds ?? DEFAULT_SIG_DEADLINE_SEC;
|
|
1444
1382
|
this.now = config.now ?? (() => Date.now());
|
|
1445
1383
|
}
|
|
1446
1384
|
async handle(request) {
|
|
1385
|
+
if ((0, import_viem7.getAddress)(request.authenticatedAddress) !== (0, import_viem7.getAddress)(request.userAddress)) {
|
|
1386
|
+
throw new PTRedeemError(
|
|
1387
|
+
"UNAUTHORIZED",
|
|
1388
|
+
`userAddress (${request.userAddress}) does not match authenticated session (${request.authenticatedAddress})`
|
|
1389
|
+
);
|
|
1390
|
+
}
|
|
1447
1391
|
if (request.amount <= 0n) {
|
|
1448
1392
|
throw new PTRedeemError("INVALID_AMOUNT", "redeem amount must be positive");
|
|
1449
1393
|
}
|
|
@@ -1451,7 +1395,7 @@ var PTRedeemHandler = class {
|
|
|
1451
1395
|
try {
|
|
1452
1396
|
burnNonce = await this.provider.readContract({
|
|
1453
1397
|
address: this.pointTokenAddress,
|
|
1454
|
-
abi:
|
|
1398
|
+
abi: import_core4.POINT_TOKEN_V2_ABI,
|
|
1455
1399
|
functionName: "burnRequestNonces",
|
|
1456
1400
|
args: [request.userAddress]
|
|
1457
1401
|
});
|
|
@@ -1461,6 +1405,17 @@ var PTRedeemHandler = class {
|
|
|
1461
1405
|
`failed to read burnRequestNonces(${request.userAddress}): ${err instanceof Error ? err.message : String(err)}`
|
|
1462
1406
|
);
|
|
1463
1407
|
}
|
|
1408
|
+
const onChainBalance = await (0, import_core4.getPointTokenBalance)(
|
|
1409
|
+
this.provider,
|
|
1410
|
+
this.pointTokenAddress,
|
|
1411
|
+
request.userAddress
|
|
1412
|
+
);
|
|
1413
|
+
if (onChainBalance < request.amount) {
|
|
1414
|
+
throw new PTRedeemError(
|
|
1415
|
+
"INVALID_AMOUNT",
|
|
1416
|
+
`insufficient on-chain PT balance: have ${onChainBalance}, need ${request.amount}`
|
|
1417
|
+
);
|
|
1418
|
+
}
|
|
1464
1419
|
const deadline = BigInt(
|
|
1465
1420
|
Math.floor(this.now() / 1e3) + this.signatureDeadlineSeconds
|
|
1466
1421
|
);
|
|
@@ -1477,7 +1432,7 @@ var PTRedeemHandler = class {
|
|
|
1477
1432
|
};
|
|
1478
1433
|
let burnerSignature;
|
|
1479
1434
|
try {
|
|
1480
|
-
const sig = await (0,
|
|
1435
|
+
const sig = await (0, import_core4.signBurnRequest)(
|
|
1481
1436
|
this.burnerSignerWallet,
|
|
1482
1437
|
domain,
|
|
1483
1438
|
burnRequest
|
|
@@ -1514,8 +1469,8 @@ var PTRedeemHandler = class {
|
|
|
1514
1469
|
};
|
|
1515
1470
|
|
|
1516
1471
|
// src/api/handlers/topUpRedemptionHandler.ts
|
|
1517
|
-
var
|
|
1518
|
-
var
|
|
1472
|
+
var import_viem8 = require("viem");
|
|
1473
|
+
var import_core5 = require("@pafi-dev/core");
|
|
1519
1474
|
var TopUpRedemptionError = class extends Error {
|
|
1520
1475
|
constructor(code, message) {
|
|
1521
1476
|
super(message);
|
|
@@ -1533,9 +1488,15 @@ var TopUpRedemptionHandler = class {
|
|
|
1533
1488
|
this.ledger = config.ledger;
|
|
1534
1489
|
this.ptRedeemHandler = config.ptRedeemHandler;
|
|
1535
1490
|
this.provider = config.provider;
|
|
1536
|
-
this.pointTokenAddress = (0,
|
|
1491
|
+
this.pointTokenAddress = (0, import_viem8.getAddress)(config.pointTokenAddress);
|
|
1537
1492
|
}
|
|
1538
1493
|
async handle(request) {
|
|
1494
|
+
if ((0, import_viem8.getAddress)(request.authenticatedAddress) !== (0, import_viem8.getAddress)(request.userAddress)) {
|
|
1495
|
+
throw new TopUpRedemptionError(
|
|
1496
|
+
"UNAUTHORIZED",
|
|
1497
|
+
`userAddress (${request.userAddress}) does not match authenticated session (${request.authenticatedAddress})`
|
|
1498
|
+
);
|
|
1499
|
+
}
|
|
1539
1500
|
const offChainBalance = await this.ledger.getBalance(
|
|
1540
1501
|
request.userAddress,
|
|
1541
1502
|
this.pointTokenAddress
|
|
@@ -1544,7 +1505,7 @@ var TopUpRedemptionHandler = class {
|
|
|
1544
1505
|
return { action: "NO_TOP_UP_NEEDED", offChainBalance };
|
|
1545
1506
|
}
|
|
1546
1507
|
const shortfall = request.requiredAmount - offChainBalance;
|
|
1547
|
-
const onChainBalance = await (0,
|
|
1508
|
+
const onChainBalance = await (0, import_core5.getPointTokenBalance)(
|
|
1548
1509
|
this.provider,
|
|
1549
1510
|
this.pointTokenAddress,
|
|
1550
1511
|
request.userAddress
|
|
@@ -1558,6 +1519,7 @@ var TopUpRedemptionHandler = class {
|
|
|
1558
1519
|
};
|
|
1559
1520
|
}
|
|
1560
1521
|
const redeem = await this.ptRedeemHandler.handle({
|
|
1522
|
+
authenticatedAddress: request.authenticatedAddress,
|
|
1561
1523
|
userAddress: request.userAddress,
|
|
1562
1524
|
amount: shortfall,
|
|
1563
1525
|
aaNonce: request.aaNonce
|
|
@@ -1571,6 +1533,7 @@ var TopUpRedemptionHandler = class {
|
|
|
1571
1533
|
};
|
|
1572
1534
|
|
|
1573
1535
|
// src/pools/subgraphPoolsProvider.ts
|
|
1536
|
+
var import_viem9 = require("viem");
|
|
1574
1537
|
var DEFAULT_CACHE_TTL_MS = 3e4;
|
|
1575
1538
|
var POOL_QUERY = `
|
|
1576
1539
|
query GetPoolForPointToken($id: ID!) {
|
|
@@ -1593,6 +1556,19 @@ function createSubgraphPoolsProvider(config) {
|
|
|
1593
1556
|
"createSubgraphPoolsProvider: subgraphUrl is required"
|
|
1594
1557
|
);
|
|
1595
1558
|
}
|
|
1559
|
+
try {
|
|
1560
|
+
const parsed = new URL(config.subgraphUrl);
|
|
1561
|
+
if (process.env.NODE_ENV === "production" && parsed.protocol !== "https:") {
|
|
1562
|
+
throw new Error("subgraphUrl must use HTTPS in production");
|
|
1563
|
+
}
|
|
1564
|
+
} catch (err) {
|
|
1565
|
+
if (err instanceof TypeError) {
|
|
1566
|
+
throw new Error(
|
|
1567
|
+
`subgraphPoolsProvider: invalid subgraphUrl: ${config.subgraphUrl}`
|
|
1568
|
+
);
|
|
1569
|
+
}
|
|
1570
|
+
throw err;
|
|
1571
|
+
}
|
|
1596
1572
|
const cacheTtl = config.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
|
|
1597
1573
|
const fetchImpl = config.fetchImpl ?? globalThis.fetch;
|
|
1598
1574
|
const now = config.now ?? (() => Date.now());
|
|
@@ -1661,6 +1637,26 @@ async function fetchPoolsFromSubgraph(fetchImpl, subgraphUrl, pointTokenAddress)
|
|
|
1661
1637
|
return [];
|
|
1662
1638
|
}
|
|
1663
1639
|
const { pool } = token;
|
|
1640
|
+
if (!(0, import_viem9.isAddress)(pool.hooks)) {
|
|
1641
|
+
console.error(
|
|
1642
|
+
"[PAFI] SubgraphPoolsProvider: invalid hooks address in response:",
|
|
1643
|
+
pool.hooks,
|
|
1644
|
+
"\u2014 skipping pool"
|
|
1645
|
+
);
|
|
1646
|
+
return [];
|
|
1647
|
+
}
|
|
1648
|
+
if (!(0, import_viem9.isAddress)(pool.token0.id) || !(0, import_viem9.isAddress)(pool.token1.id)) {
|
|
1649
|
+
console.error(
|
|
1650
|
+
"[PAFI] SubgraphPoolsProvider: invalid token address in response \u2014 skipping pool"
|
|
1651
|
+
);
|
|
1652
|
+
return [];
|
|
1653
|
+
}
|
|
1654
|
+
if (!Number.isFinite(Number(pool.feeTier)) || !Number.isFinite(Number(pool.tickSpacing))) {
|
|
1655
|
+
console.error(
|
|
1656
|
+
"[PAFI] SubgraphPoolsProvider: invalid feeTier/tickSpacing \u2014 skipping pool"
|
|
1657
|
+
);
|
|
1658
|
+
return [];
|
|
1659
|
+
}
|
|
1664
1660
|
const [currency0, currency1] = sortCurrencies(
|
|
1665
1661
|
pool.token0.id,
|
|
1666
1662
|
pool.token1.id
|
|
@@ -1696,6 +1692,19 @@ function createSubgraphNativeUsdtQuoter(config) {
|
|
|
1696
1692
|
"createSubgraphNativeUsdtQuoter: subgraphUrl is required"
|
|
1697
1693
|
);
|
|
1698
1694
|
}
|
|
1695
|
+
try {
|
|
1696
|
+
const parsed = new URL(config.subgraphUrl);
|
|
1697
|
+
if (process.env.NODE_ENV === "production" && parsed.protocol !== "https:") {
|
|
1698
|
+
throw new Error("subgraphUrl must use HTTPS in production");
|
|
1699
|
+
}
|
|
1700
|
+
} catch (err) {
|
|
1701
|
+
if (err instanceof TypeError) {
|
|
1702
|
+
throw new Error(
|
|
1703
|
+
`subgraphPoolsProvider: invalid subgraphUrl: ${config.subgraphUrl}`
|
|
1704
|
+
);
|
|
1705
|
+
}
|
|
1706
|
+
throw err;
|
|
1707
|
+
}
|
|
1699
1708
|
const usdtDecimals = config.usdtDecimals ?? DEFAULT_USDT_DECIMALS;
|
|
1700
1709
|
const nativeDecimals = config.nativeDecimals ?? DEFAULT_NATIVE_DECIMALS;
|
|
1701
1710
|
const cacheTtl = config.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS2;
|
|
@@ -1772,6 +1781,14 @@ async function fetchEthPriceFromSubgraph(fetchImpl, subgraphUrl) {
|
|
|
1772
1781
|
);
|
|
1773
1782
|
return null;
|
|
1774
1783
|
}
|
|
1784
|
+
const MIN_REASONABLE_ETH_PRICE = 100;
|
|
1785
|
+
const MAX_REASONABLE_ETH_PRICE = 1e5;
|
|
1786
|
+
if (parsed < MIN_REASONABLE_ETH_PRICE || parsed > MAX_REASONABLE_ETH_PRICE) {
|
|
1787
|
+
console.warn(
|
|
1788
|
+
`[PAFI] SubgraphNativeUsdtQuoter: ETH/USD price ${parsed} is outside reasonable range. Using fallback.`
|
|
1789
|
+
);
|
|
1790
|
+
return null;
|
|
1791
|
+
}
|
|
1775
1792
|
return parsed;
|
|
1776
1793
|
}
|
|
1777
1794
|
function toUsdtPerNative(priceFloat, usdtDecimals) {
|
|
@@ -1782,7 +1799,7 @@ function toUsdtPerNative(priceFloat, usdtDecimals) {
|
|
|
1782
1799
|
}
|
|
1783
1800
|
|
|
1784
1801
|
// src/balance/balanceAggregator.ts
|
|
1785
|
-
var
|
|
1802
|
+
var import_core6 = require("@pafi-dev/core");
|
|
1786
1803
|
var BalanceAggregator = class {
|
|
1787
1804
|
provider;
|
|
1788
1805
|
ledger;
|
|
@@ -1803,7 +1820,7 @@ var BalanceAggregator = class {
|
|
|
1803
1820
|
async getCombinedBalance(user, pointToken) {
|
|
1804
1821
|
const [offChain, onChain] = await Promise.all([
|
|
1805
1822
|
this.ledger.getBalance(user, pointToken),
|
|
1806
|
-
(0,
|
|
1823
|
+
(0, import_core6.getPointTokenBalance)(this.provider, pointToken, user)
|
|
1807
1824
|
]);
|
|
1808
1825
|
return {
|
|
1809
1826
|
offChain,
|
|
@@ -1862,12 +1879,104 @@ var PafiBackendError = class extends Error {
|
|
|
1862
1879
|
}
|
|
1863
1880
|
};
|
|
1864
1881
|
|
|
1882
|
+
// src/pafi-backend/client.ts
|
|
1883
|
+
function serializeBigInt(_key, value) {
|
|
1884
|
+
return typeof value === "bigint" ? value.toString(10) : value;
|
|
1885
|
+
}
|
|
1886
|
+
function sleep(ms) {
|
|
1887
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1888
|
+
}
|
|
1889
|
+
var PafiBackendClient = class {
|
|
1890
|
+
config;
|
|
1891
|
+
constructor(config) {
|
|
1892
|
+
if (!config.url) throw new Error("PafiBackendClient: url is required");
|
|
1893
|
+
if (!config.issuerId) throw new Error("PafiBackendClient: issuerId is required");
|
|
1894
|
+
this.config = config;
|
|
1895
|
+
}
|
|
1896
|
+
async requestSponsorship(request) {
|
|
1897
|
+
const maxAttempts = this.config.retry?.maxAttempts ?? 1;
|
|
1898
|
+
const initialDelayMs = this.config.retry?.initialDelayMs ?? 100;
|
|
1899
|
+
const maxDelayMs = this.config.retry?.maxDelayMs ?? 1e4;
|
|
1900
|
+
const maxRetryAfterMs = this.config.retry?.maxRetryAfterMs;
|
|
1901
|
+
let lastError;
|
|
1902
|
+
let delay = initialDelayMs;
|
|
1903
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
1904
|
+
try {
|
|
1905
|
+
return await this._doRequest(request);
|
|
1906
|
+
} catch (err) {
|
|
1907
|
+
if (!(err instanceof PafiBackendError)) throw err;
|
|
1908
|
+
lastError = err;
|
|
1909
|
+
if (attempt >= maxAttempts) break;
|
|
1910
|
+
if (!err.safeToRetry) break;
|
|
1911
|
+
const retryAfterMs = err.retryAfter !== void 0 ? err.retryAfter * 1e3 : void 0;
|
|
1912
|
+
if (maxRetryAfterMs !== void 0 && retryAfterMs !== void 0 && retryAfterMs > maxRetryAfterMs) {
|
|
1913
|
+
break;
|
|
1914
|
+
}
|
|
1915
|
+
await sleep(retryAfterMs ?? delay);
|
|
1916
|
+
delay = Math.min(delay * 2, maxDelayMs);
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
throw lastError;
|
|
1920
|
+
}
|
|
1921
|
+
async _doRequest(request) {
|
|
1922
|
+
const fetchFn = this.config.fetchImpl ?? fetch;
|
|
1923
|
+
const url = `${this.config.url}/paymaster/sponsor`;
|
|
1924
|
+
const body = JSON.stringify(request, serializeBigInt);
|
|
1925
|
+
let response;
|
|
1926
|
+
try {
|
|
1927
|
+
response = await fetchFn(url, {
|
|
1928
|
+
method: "POST",
|
|
1929
|
+
headers: {
|
|
1930
|
+
"Content-Type": "application/json",
|
|
1931
|
+
Authorization: `Bearer ${this.config.apiKey}`,
|
|
1932
|
+
"X-Issuer-Id": this.config.issuerId
|
|
1933
|
+
},
|
|
1934
|
+
body
|
|
1935
|
+
});
|
|
1936
|
+
} catch (err) {
|
|
1937
|
+
throw new PafiBackendError(
|
|
1938
|
+
"NETWORK_ERROR",
|
|
1939
|
+
`Network error: ${err instanceof Error ? err.message : String(err)}`,
|
|
1940
|
+
0
|
|
1941
|
+
);
|
|
1942
|
+
}
|
|
1943
|
+
const text = await response.text();
|
|
1944
|
+
let json = {};
|
|
1945
|
+
try {
|
|
1946
|
+
json = JSON.parse(text);
|
|
1947
|
+
} catch {
|
|
1948
|
+
}
|
|
1949
|
+
if (!response.ok) {
|
|
1950
|
+
const code = json.code ?? "INTERNAL_ERROR";
|
|
1951
|
+
const message = json.message ?? `HTTP ${response.status}`;
|
|
1952
|
+
const retryAfter = typeof json.retryAfter === "number" ? json.retryAfter : void 0;
|
|
1953
|
+
const safeToRetry = typeof json.safeToRetry === "boolean" ? json.safeToRetry : void 0;
|
|
1954
|
+
throw new PafiBackendError(code, message, response.status, json, {
|
|
1955
|
+
retryAfter,
|
|
1956
|
+
safeToRetry
|
|
1957
|
+
});
|
|
1958
|
+
}
|
|
1959
|
+
return {
|
|
1960
|
+
paymaster: json.paymaster,
|
|
1961
|
+
paymasterData: json.paymasterData,
|
|
1962
|
+
paymasterVerificationGasLimit: BigInt(
|
|
1963
|
+
json.paymasterVerificationGasLimit
|
|
1964
|
+
),
|
|
1965
|
+
paymasterPostOpGasLimit: BigInt(json.paymasterPostOpGasLimit),
|
|
1966
|
+
expiresAt: json.expiresAt
|
|
1967
|
+
};
|
|
1968
|
+
}
|
|
1969
|
+
};
|
|
1970
|
+
|
|
1865
1971
|
// src/config.ts
|
|
1866
|
-
var
|
|
1972
|
+
var import_viem10 = require("viem");
|
|
1867
1973
|
function createIssuerService(config) {
|
|
1868
1974
|
if (!config.provider) {
|
|
1869
1975
|
throw new Error("createIssuerService: provider is required");
|
|
1870
1976
|
}
|
|
1977
|
+
if (!config.ledger) {
|
|
1978
|
+
throw new Error("createIssuerService: ledger is required");
|
|
1979
|
+
}
|
|
1871
1980
|
if (!config.auth?.jwtSecret) {
|
|
1872
1981
|
throw new Error("createIssuerService: auth.jwtSecret is required");
|
|
1873
1982
|
}
|
|
@@ -1880,8 +1989,8 @@ function createIssuerService(config) {
|
|
|
1880
1989
|
"createIssuerService: at least one of pointTokenAddress / pointTokenAddresses is required"
|
|
1881
1990
|
);
|
|
1882
1991
|
}
|
|
1883
|
-
const tokenAddresses = rawAddresses.map((a) => (0,
|
|
1884
|
-
const ledger = config.ledger
|
|
1992
|
+
const tokenAddresses = rawAddresses.map((a) => (0, import_viem10.getAddress)(a));
|
|
1993
|
+
const ledger = config.ledger;
|
|
1885
1994
|
const sessionStore = config.sessionStore ?? new MemorySessionStore();
|
|
1886
1995
|
const policy = config.policy ?? new DefaultPolicyEngine({ ledger });
|
|
1887
1996
|
const authServiceConfig = {
|
|
@@ -1937,6 +2046,15 @@ function createIssuerService(config) {
|
|
|
1937
2046
|
};
|
|
1938
2047
|
if (feeManager) handlersConfig.feeManager = feeManager;
|
|
1939
2048
|
if (config.poolsProvider) handlersConfig.poolsProvider = config.poolsProvider;
|
|
2049
|
+
if (config.claim) {
|
|
2050
|
+
handlersConfig.claim = {
|
|
2051
|
+
policy,
|
|
2052
|
+
relayService,
|
|
2053
|
+
issuerSignerWallet: config.claim.issuerSignerWallet,
|
|
2054
|
+
batchExecutorAddress: config.claim.batchExecutorAddress,
|
|
2055
|
+
lockDurationMs: config.claim.lockDurationMs
|
|
2056
|
+
};
|
|
2057
|
+
}
|
|
1940
2058
|
const handlers = new IssuerApiHandlers(handlersConfig);
|
|
1941
2059
|
if (config.indexer?.autoStart) {
|
|
1942
2060
|
for (const idx of indexers.values()) {
|
|
@@ -1968,15 +2086,14 @@ var PAFI_ISSUER_SDK_VERSION = "0.1.0";
|
|
|
1968
2086
|
FeeManager,
|
|
1969
2087
|
InMemoryCursorStore,
|
|
1970
2088
|
IssuerApiHandlers,
|
|
1971
|
-
MemoryPointLedger,
|
|
1972
2089
|
MemorySessionStore,
|
|
1973
2090
|
NonceManager,
|
|
1974
2091
|
PAFI_ISSUER_SDK_VERSION,
|
|
1975
2092
|
PTRedeemError,
|
|
1976
2093
|
PTRedeemHandler,
|
|
2094
|
+
PafiBackendClient,
|
|
1977
2095
|
PafiBackendError,
|
|
1978
2096
|
PointIndexer,
|
|
1979
|
-
PrivateKeySigner,
|
|
1980
2097
|
RelayError,
|
|
1981
2098
|
RelayService,
|
|
1982
2099
|
TopUpRedemptionError,
|