@pafi-dev/issuer 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -11,19 +11,21 @@ var MemoryPointLedger = class {
11
11
  // -------------------------------------------------------------------------
12
12
  // Read
13
13
  // -------------------------------------------------------------------------
14
- async getBalance(userAddress) {
15
- const key = getAddress(userAddress);
14
+ async getBalance(userAddress, tokenAddress) {
15
+ const user = getAddress(userAddress);
16
+ const token = normalizeToken(tokenAddress);
16
17
  this.purgeExpired();
17
- const total = this.balances.get(key) ?? 0n;
18
- const locked = this.lockedTotalFor(key);
18
+ const total = this.balances.get(balanceKey(user, token)) ?? 0n;
19
+ const locked = this.lockedTotalFor(user, token);
19
20
  return total - locked;
20
21
  }
21
- async getLockedRequests(userAddress) {
22
- const key = getAddress(userAddress);
22
+ async getLockedRequests(userAddress, tokenAddress) {
23
+ const user = getAddress(userAddress);
24
+ const token = normalizeToken(tokenAddress);
23
25
  this.purgeExpired();
24
26
  const out = [];
25
27
  for (const lock of this.locks.values()) {
26
- if (lock.userAddress === key && lock.status === "PENDING") {
28
+ if (lock.userAddress === user && lock.status === "PENDING" && (lock.tokenAddress ?? DEFAULT_TOKEN_KEY) === token) {
27
29
  out.push({ ...lock });
28
30
  }
29
31
  }
@@ -32,25 +34,28 @@ var MemoryPointLedger = class {
32
34
  // -------------------------------------------------------------------------
33
35
  // Write
34
36
  // -------------------------------------------------------------------------
35
- async creditBalance(userAddress, amount, _reason) {
37
+ async creditBalance(userAddress, amount, _reason, tokenAddress) {
36
38
  if (amount <= 0n) {
37
39
  throw new Error("MemoryPointLedger: credit amount must be positive");
38
40
  }
39
- const key = getAddress(userAddress);
41
+ const user = getAddress(userAddress);
42
+ const token = normalizeToken(tokenAddress);
43
+ const key = balanceKey(user, token);
40
44
  const current = this.balances.get(key) ?? 0n;
41
45
  this.balances.set(key, current + amount);
42
46
  }
43
- async lockForMinting(userAddress, amount, lockDurationMs) {
47
+ async lockForMinting(userAddress, amount, lockDurationMs, tokenAddress) {
44
48
  if (amount <= 0n) {
45
49
  throw new Error("MemoryPointLedger: lock amount must be positive");
46
50
  }
47
51
  if (lockDurationMs <= 0) {
48
52
  throw new Error("MemoryPointLedger: lockDurationMs must be positive");
49
53
  }
50
- const key = getAddress(userAddress);
54
+ const user = getAddress(userAddress);
55
+ const token = normalizeToken(tokenAddress);
51
56
  this.purgeExpired();
52
- const total = this.balances.get(key) ?? 0n;
53
- const alreadyLocked = this.lockedTotalFor(key);
57
+ const total = this.balances.get(balanceKey(user, token)) ?? 0n;
58
+ const alreadyLocked = this.lockedTotalFor(user, token);
54
59
  const available = total - alreadyLocked;
55
60
  if (available < amount) {
56
61
  throw new Error(
@@ -59,14 +64,18 @@ var MemoryPointLedger = class {
59
64
  }
60
65
  const lockId = `lock-${this.nextLockId++}`;
61
66
  const now = this.now();
62
- this.locks.set(lockId, {
67
+ const lock = {
63
68
  lockId,
64
- userAddress: key,
69
+ userAddress: user,
65
70
  amount,
66
71
  status: "PENDING",
67
72
  createdAt: now,
68
73
  expiresAt: now + lockDurationMs
69
- });
74
+ };
75
+ if (tokenAddress !== void 0) {
76
+ lock.tokenAddress = getAddress(tokenAddress);
77
+ }
78
+ this.locks.set(lockId, lock);
70
79
  return lockId;
71
80
  }
72
81
  async releaseLock(lockId) {
@@ -76,11 +85,13 @@ var MemoryPointLedger = class {
76
85
  this.locks.delete(lockId);
77
86
  }
78
87
  }
79
- async deductBalance(userAddress, amount, txHash) {
88
+ async deductBalance(userAddress, amount, txHash, tokenAddress) {
80
89
  if (amount <= 0n) {
81
90
  throw new Error("MemoryPointLedger: deduct amount must be positive");
82
91
  }
83
- const key = getAddress(userAddress);
92
+ const user = getAddress(userAddress);
93
+ const token = normalizeToken(tokenAddress);
94
+ const key = balanceKey(user, token);
84
95
  const current = this.balances.get(key) ?? 0n;
85
96
  if (current < amount) {
86
97
  throw new Error(
@@ -89,7 +100,7 @@ var MemoryPointLedger = class {
89
100
  }
90
101
  this.balances.set(key, current - amount);
91
102
  for (const lock of this.locks.values()) {
92
- if (lock.userAddress === key && lock.status === "PENDING" && lock.amount === amount) {
103
+ if (lock.userAddress === user && lock.status === "PENDING" && lock.amount === amount && (lock.tokenAddress ?? DEFAULT_TOKEN_KEY) === token) {
93
104
  lock.status = "MINTED";
94
105
  lock.txHash = txHash;
95
106
  return;
@@ -119,16 +130,23 @@ var MemoryPointLedger = class {
119
130
  }
120
131
  }
121
132
  }
122
- lockedTotalFor(userAddress) {
133
+ lockedTotalFor(userAddress, tokenKey) {
123
134
  let total = 0n;
124
135
  for (const lock of this.locks.values()) {
125
- if (lock.userAddress === userAddress && lock.status === "PENDING") {
136
+ if (lock.userAddress === userAddress && lock.status === "PENDING" && (lock.tokenAddress ?? DEFAULT_TOKEN_KEY) === tokenKey) {
126
137
  total += lock.amount;
127
138
  }
128
139
  }
129
140
  return total;
130
141
  }
131
142
  };
143
+ var DEFAULT_TOKEN_KEY = "default";
144
+ function normalizeToken(tokenAddress) {
145
+ return tokenAddress === void 0 ? DEFAULT_TOKEN_KEY : getAddress(tokenAddress);
146
+ }
147
+ function balanceKey(user, tokenKey) {
148
+ return `${user}|${tokenKey}`;
149
+ }
132
150
 
133
151
  // src/policy/defaultPolicy.ts
134
152
  var DefaultPolicyEngine = class {
@@ -150,7 +168,10 @@ var DefaultPolicyEngine = class {
150
168
  if (request.amount <= 0n) {
151
169
  return { approved: false, reason: "Amount must be positive" };
152
170
  }
153
- const available = await this.ledger.getBalance(request.userAddress);
171
+ const available = await this.ledger.getBalance(
172
+ request.userAddress,
173
+ request.pointTokenAddress
174
+ );
154
175
  if (available < request.amount) {
155
176
  return {
156
177
  approved: false,
@@ -838,7 +859,8 @@ var MintingGateway = class {
838
859
  lockId = await this.ledger.lockForMinting(
839
860
  request.userAddress,
840
861
  receiverConsent.amount,
841
- lockDurationMs
862
+ lockDurationMs,
863
+ request.pointTokenAddress
842
864
  );
843
865
  } catch (err) {
844
866
  throw new MintingGatewayError(
@@ -1128,11 +1150,19 @@ var PointIndexer = class {
1128
1150
  * issuer to mint without going through the gateway.
1129
1151
  */
1130
1152
  async finalize(evt) {
1131
- const locks = await this.ledger.getLockedRequests(evt.to);
1153
+ const locks = await this.ledger.getLockedRequests(
1154
+ evt.to,
1155
+ this.pointTokenAddress
1156
+ );
1132
1157
  const match = pickMatchingLock(locks, evt.amount);
1133
1158
  if (!match) return;
1134
1159
  try {
1135
- await this.ledger.deductBalance(evt.to, evt.amount, evt.txHash);
1160
+ await this.ledger.deductBalance(
1161
+ evt.to,
1162
+ evt.amount,
1163
+ evt.txHash,
1164
+ this.pointTokenAddress
1165
+ );
1136
1166
  } catch {
1137
1167
  return;
1138
1168
  }
@@ -1158,6 +1188,7 @@ function pickMatchingLock(locks, amount) {
1158
1188
  import { getAddress as getAddress5 } from "viem";
1159
1189
  import {
1160
1190
  getMintRequestNonce,
1191
+ getPointTokenBalance,
1161
1192
  getReceiverConsentNonce,
1162
1193
  getTokenName,
1163
1194
  isMinter,
@@ -1168,7 +1199,15 @@ var IssuerApiHandlers = class {
1168
1199
  gateway;
1169
1200
  ledger;
1170
1201
  provider;
1171
- pointTokenAddress;
1202
+ /**
1203
+ * Set of supported PointToken addresses (checksum-normalized). Handlers
1204
+ * validate the request's `pointTokenAddress` against this set.
1205
+ */
1206
+ supportedTokens;
1207
+ /** First supported token — used as default when a handler doesn't
1208
+ * receive a `pointTokenAddress` in the request (shouldn't happen in
1209
+ * practice, but keeps type-narrowing happy). */
1210
+ defaultToken;
1172
1211
  chainId;
1173
1212
  contracts;
1174
1213
  feeManager;
@@ -1178,7 +1217,15 @@ var IssuerApiHandlers = class {
1178
1217
  this.gateway = config.gateway;
1179
1218
  this.ledger = config.ledger;
1180
1219
  this.provider = config.provider;
1181
- this.pointTokenAddress = config.pointTokenAddress;
1220
+ const raw = config.pointTokenAddresses && config.pointTokenAddresses.length > 0 ? config.pointTokenAddresses : config.pointTokenAddress ? [config.pointTokenAddress] : [];
1221
+ if (raw.length === 0) {
1222
+ throw new Error(
1223
+ "IssuerApiHandlers: pointTokenAddress or pointTokenAddresses required"
1224
+ );
1225
+ }
1226
+ const normalized = raw.map((a) => getAddress5(a));
1227
+ this.supportedTokens = new Set(normalized);
1228
+ this.defaultToken = normalized[0];
1182
1229
  this.chainId = config.chainId;
1183
1230
  this.contracts = config.contracts;
1184
1231
  if (config.feeManager) this.feeManager = config.feeManager;
@@ -1275,21 +1322,26 @@ var IssuerApiHandlers = class {
1275
1322
  );
1276
1323
  }
1277
1324
  const pointToken = getAddress5(request.pointTokenAddress);
1278
- if (pointToken !== this.pointTokenAddress) {
1325
+ if (!this.supportedTokens.has(pointToken)) {
1279
1326
  throw new Error(
1280
1327
  `handleUser: unsupported pointToken ${pointToken}`
1281
1328
  );
1282
1329
  }
1283
- const [mintRequestNonce, receiverConsentNonce, balance, minter] = await Promise.all([
1330
+ const [mintRequestNonce, receiverConsentNonce, offChainBalance, onChainBalance, minter] = await Promise.all([
1284
1331
  getMintRequestNonce(this.provider, pointToken, normalizedAuthed),
1285
1332
  getReceiverConsentNonce(this.provider, pointToken, normalizedAuthed),
1286
- this.ledger.getBalance(normalizedAuthed),
1333
+ this.ledger.getBalance(normalizedAuthed, pointToken),
1334
+ getPointTokenBalance(this.provider, pointToken, normalizedAuthed),
1287
1335
  isMinter(this.provider, pointToken, normalizedAuthed)
1288
1336
  ]);
1289
1337
  return {
1290
1338
  mintRequestNonce,
1291
1339
  receiverConsentNonce,
1292
- balance,
1340
+ offChainBalance,
1341
+ onChainBalance,
1342
+ totalBalance: offChainBalance + onChainBalance,
1343
+ balance: offChainBalance,
1344
+ // deprecated alias
1293
1345
  isMinter: minter
1294
1346
  };
1295
1347
  }
@@ -1313,7 +1365,7 @@ var IssuerApiHandlers = class {
1313
1365
  );
1314
1366
  }
1315
1367
  const pointToken = getAddress5(request.pointTokenAddress);
1316
- if (pointToken !== this.pointTokenAddress) {
1368
+ if (!this.supportedTokens.has(pointToken)) {
1317
1369
  throw new Error(
1318
1370
  `handleBuildConsentTypedData: unsupported pointToken ${pointToken}`
1319
1371
  );
@@ -1347,7 +1399,7 @@ var IssuerApiHandlers = class {
1347
1399
  );
1348
1400
  }
1349
1401
  const pointToken = getAddress5(request.pointTokenAddress);
1350
- if (pointToken !== this.pointTokenAddress) {
1402
+ if (!this.supportedTokens.has(pointToken)) {
1351
1403
  throw new Error(
1352
1404
  `handleClaimAndSwap: unsupported pointToken ${pointToken}`
1353
1405
  );
@@ -1585,6 +1637,7 @@ function toUsdtPerNative(priceFloat, usdtDecimals) {
1585
1637
  }
1586
1638
 
1587
1639
  // src/config.ts
1640
+ import { getAddress as getAddress6 } from "viem";
1588
1641
  function createIssuerService(config) {
1589
1642
  if (!config.provider) {
1590
1643
  throw new Error("createIssuerService: provider is required");
@@ -1595,9 +1648,6 @@ function createIssuerService(config) {
1595
1648
  if (!config.signer) {
1596
1649
  throw new Error("createIssuerService: signer is required");
1597
1650
  }
1598
- if (!config.pointTokenAddress) {
1599
- throw new Error("createIssuerService: pointTokenAddress is required");
1600
- }
1601
1651
  if (!config.relayAddress) {
1602
1652
  throw new Error("createIssuerService: relayAddress is required");
1603
1653
  }
@@ -1607,6 +1657,13 @@ function createIssuerService(config) {
1607
1657
  if (!config.auth?.domain) {
1608
1658
  throw new Error("createIssuerService: auth.domain is required");
1609
1659
  }
1660
+ const rawAddresses = config.pointTokenAddresses && config.pointTokenAddresses.length > 0 ? config.pointTokenAddresses : config.pointTokenAddress ? [config.pointTokenAddress] : [];
1661
+ if (rawAddresses.length === 0) {
1662
+ throw new Error(
1663
+ "createIssuerService: at least one of pointTokenAddress / pointTokenAddresses is required"
1664
+ );
1665
+ }
1666
+ const tokenAddresses = rawAddresses.map((a) => getAddress6(a));
1610
1667
  const ledger = config.ledger ?? new MemoryPointLedger();
1611
1668
  const sessionStore = config.sessionStore ?? new MemorySessionStore();
1612
1669
  const policy = config.policy ?? new DefaultPolicyEngine({ ledger });
@@ -1650,33 +1707,37 @@ function createIssuerService(config) {
1650
1707
  gatewayConfig.defaultLockBufferMs = config.gateway.defaultLockBufferMs;
1651
1708
  }
1652
1709
  const gateway = new MintingGateway(gatewayConfig);
1653
- const indexerConfig = {
1654
- provider: config.provider,
1655
- pointTokenAddress: config.pointTokenAddress,
1656
- ledger
1657
- };
1658
- if (config.indexer?.fromBlock !== void 0) {
1659
- indexerConfig.fromBlock = config.indexer.fromBlock;
1660
- }
1661
- if (config.indexer?.cursorStore) {
1662
- indexerConfig.cursorStore = config.indexer.cursorStore;
1663
- }
1664
- if (config.indexer?.confirmations !== void 0) {
1665
- indexerConfig.confirmations = config.indexer.confirmations;
1666
- }
1667
- if (config.indexer?.batchSize !== void 0) {
1668
- indexerConfig.batchSize = config.indexer.batchSize;
1669
- }
1670
- if (config.indexer?.pollIntervalMs !== void 0) {
1671
- indexerConfig.pollIntervalMs = config.indexer.pollIntervalMs;
1710
+ const indexers = /* @__PURE__ */ new Map();
1711
+ for (const tokenAddress of tokenAddresses) {
1712
+ const indexerConfig = {
1713
+ provider: config.provider,
1714
+ pointTokenAddress: tokenAddress,
1715
+ ledger
1716
+ };
1717
+ if (config.indexer?.fromBlock !== void 0) {
1718
+ indexerConfig.fromBlock = config.indexer.fromBlock;
1719
+ }
1720
+ if (config.indexer?.cursorStore) {
1721
+ indexerConfig.cursorStore = config.indexer.cursorStore;
1722
+ }
1723
+ if (config.indexer?.confirmations !== void 0) {
1724
+ indexerConfig.confirmations = config.indexer.confirmations;
1725
+ }
1726
+ if (config.indexer?.batchSize !== void 0) {
1727
+ indexerConfig.batchSize = config.indexer.batchSize;
1728
+ }
1729
+ if (config.indexer?.pollIntervalMs !== void 0) {
1730
+ indexerConfig.pollIntervalMs = config.indexer.pollIntervalMs;
1731
+ }
1732
+ indexers.set(tokenAddress, new PointIndexer(indexerConfig));
1672
1733
  }
1673
- const indexer = new PointIndexer(indexerConfig);
1734
+ const firstIndexer = indexers.get(tokenAddresses[0]);
1674
1735
  const handlersConfig = {
1675
1736
  authService,
1676
1737
  gateway,
1677
1738
  ledger,
1678
1739
  provider: config.provider,
1679
- pointTokenAddress: config.pointTokenAddress,
1740
+ pointTokenAddresses: tokenAddresses,
1680
1741
  chainId: config.chainId,
1681
1742
  contracts: config.contracts
1682
1743
  };
@@ -1684,7 +1745,9 @@ function createIssuerService(config) {
1684
1745
  if (config.poolsProvider) handlersConfig.poolsProvider = config.poolsProvider;
1685
1746
  const handlers = new IssuerApiHandlers(handlersConfig);
1686
1747
  if (config.indexer?.autoStart) {
1687
- indexer.start();
1748
+ for (const idx of indexers.values()) {
1749
+ idx.start();
1750
+ }
1688
1751
  }
1689
1752
  return {
1690
1753
  authService,
@@ -1695,7 +1758,8 @@ function createIssuerService(config) {
1695
1758
  relayService,
1696
1759
  feeManager,
1697
1760
  gateway,
1698
- indexer,
1761
+ indexers,
1762
+ indexer: firstIndexer,
1699
1763
  handlers
1700
1764
  };
1701
1765
  }