@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.cjs CHANGED
@@ -57,19 +57,21 @@ var MemoryPointLedger = class {
57
57
  // -------------------------------------------------------------------------
58
58
  // Read
59
59
  // -------------------------------------------------------------------------
60
- async getBalance(userAddress) {
61
- const key = (0, import_viem.getAddress)(userAddress);
60
+ async getBalance(userAddress, tokenAddress) {
61
+ const user = (0, import_viem.getAddress)(userAddress);
62
+ const token = normalizeToken(tokenAddress);
62
63
  this.purgeExpired();
63
- const total = this.balances.get(key) ?? 0n;
64
- const locked = this.lockedTotalFor(key);
64
+ const total = this.balances.get(balanceKey(user, token)) ?? 0n;
65
+ const locked = this.lockedTotalFor(user, token);
65
66
  return total - locked;
66
67
  }
67
- async getLockedRequests(userAddress) {
68
- const key = (0, import_viem.getAddress)(userAddress);
68
+ async getLockedRequests(userAddress, tokenAddress) {
69
+ const user = (0, import_viem.getAddress)(userAddress);
70
+ const token = normalizeToken(tokenAddress);
69
71
  this.purgeExpired();
70
72
  const out = [];
71
73
  for (const lock of this.locks.values()) {
72
- if (lock.userAddress === key && lock.status === "PENDING") {
74
+ if (lock.userAddress === user && lock.status === "PENDING" && (lock.tokenAddress ?? DEFAULT_TOKEN_KEY) === token) {
73
75
  out.push({ ...lock });
74
76
  }
75
77
  }
@@ -78,25 +80,28 @@ var MemoryPointLedger = class {
78
80
  // -------------------------------------------------------------------------
79
81
  // Write
80
82
  // -------------------------------------------------------------------------
81
- async creditBalance(userAddress, amount, _reason) {
83
+ async creditBalance(userAddress, amount, _reason, tokenAddress) {
82
84
  if (amount <= 0n) {
83
85
  throw new Error("MemoryPointLedger: credit amount must be positive");
84
86
  }
85
- const key = (0, import_viem.getAddress)(userAddress);
87
+ const user = (0, import_viem.getAddress)(userAddress);
88
+ const token = normalizeToken(tokenAddress);
89
+ const key = balanceKey(user, token);
86
90
  const current = this.balances.get(key) ?? 0n;
87
91
  this.balances.set(key, current + amount);
88
92
  }
89
- async lockForMinting(userAddress, amount, lockDurationMs) {
93
+ async lockForMinting(userAddress, amount, lockDurationMs, tokenAddress) {
90
94
  if (amount <= 0n) {
91
95
  throw new Error("MemoryPointLedger: lock amount must be positive");
92
96
  }
93
97
  if (lockDurationMs <= 0) {
94
98
  throw new Error("MemoryPointLedger: lockDurationMs must be positive");
95
99
  }
96
- const key = (0, import_viem.getAddress)(userAddress);
100
+ const user = (0, import_viem.getAddress)(userAddress);
101
+ const token = normalizeToken(tokenAddress);
97
102
  this.purgeExpired();
98
- const total = this.balances.get(key) ?? 0n;
99
- const alreadyLocked = this.lockedTotalFor(key);
103
+ const total = this.balances.get(balanceKey(user, token)) ?? 0n;
104
+ const alreadyLocked = this.lockedTotalFor(user, token);
100
105
  const available = total - alreadyLocked;
101
106
  if (available < amount) {
102
107
  throw new Error(
@@ -105,14 +110,18 @@ var MemoryPointLedger = class {
105
110
  }
106
111
  const lockId = `lock-${this.nextLockId++}`;
107
112
  const now = this.now();
108
- this.locks.set(lockId, {
113
+ const lock = {
109
114
  lockId,
110
- userAddress: key,
115
+ userAddress: user,
111
116
  amount,
112
117
  status: "PENDING",
113
118
  createdAt: now,
114
119
  expiresAt: now + lockDurationMs
115
- });
120
+ };
121
+ if (tokenAddress !== void 0) {
122
+ lock.tokenAddress = (0, import_viem.getAddress)(tokenAddress);
123
+ }
124
+ this.locks.set(lockId, lock);
116
125
  return lockId;
117
126
  }
118
127
  async releaseLock(lockId) {
@@ -122,11 +131,13 @@ var MemoryPointLedger = class {
122
131
  this.locks.delete(lockId);
123
132
  }
124
133
  }
125
- async deductBalance(userAddress, amount, txHash) {
134
+ async deductBalance(userAddress, amount, txHash, tokenAddress) {
126
135
  if (amount <= 0n) {
127
136
  throw new Error("MemoryPointLedger: deduct amount must be positive");
128
137
  }
129
- const key = (0, import_viem.getAddress)(userAddress);
138
+ const user = (0, import_viem.getAddress)(userAddress);
139
+ const token = normalizeToken(tokenAddress);
140
+ const key = balanceKey(user, token);
130
141
  const current = this.balances.get(key) ?? 0n;
131
142
  if (current < amount) {
132
143
  throw new Error(
@@ -135,7 +146,7 @@ var MemoryPointLedger = class {
135
146
  }
136
147
  this.balances.set(key, current - amount);
137
148
  for (const lock of this.locks.values()) {
138
- if (lock.userAddress === key && lock.status === "PENDING" && lock.amount === amount) {
149
+ if (lock.userAddress === user && lock.status === "PENDING" && lock.amount === amount && (lock.tokenAddress ?? DEFAULT_TOKEN_KEY) === token) {
139
150
  lock.status = "MINTED";
140
151
  lock.txHash = txHash;
141
152
  return;
@@ -165,16 +176,23 @@ var MemoryPointLedger = class {
165
176
  }
166
177
  }
167
178
  }
168
- lockedTotalFor(userAddress) {
179
+ lockedTotalFor(userAddress, tokenKey) {
169
180
  let total = 0n;
170
181
  for (const lock of this.locks.values()) {
171
- if (lock.userAddress === userAddress && lock.status === "PENDING") {
182
+ if (lock.userAddress === userAddress && lock.status === "PENDING" && (lock.tokenAddress ?? DEFAULT_TOKEN_KEY) === tokenKey) {
172
183
  total += lock.amount;
173
184
  }
174
185
  }
175
186
  return total;
176
187
  }
177
188
  };
189
+ var DEFAULT_TOKEN_KEY = "default";
190
+ function normalizeToken(tokenAddress) {
191
+ return tokenAddress === void 0 ? DEFAULT_TOKEN_KEY : (0, import_viem.getAddress)(tokenAddress);
192
+ }
193
+ function balanceKey(user, tokenKey) {
194
+ return `${user}|${tokenKey}`;
195
+ }
178
196
 
179
197
  // src/policy/defaultPolicy.ts
180
198
  var DefaultPolicyEngine = class {
@@ -196,7 +214,10 @@ var DefaultPolicyEngine = class {
196
214
  if (request.amount <= 0n) {
197
215
  return { approved: false, reason: "Amount must be positive" };
198
216
  }
199
- const available = await this.ledger.getBalance(request.userAddress);
217
+ const available = await this.ledger.getBalance(
218
+ request.userAddress,
219
+ request.pointTokenAddress
220
+ );
200
221
  if (available < request.amount) {
201
222
  return {
202
223
  approved: false,
@@ -874,7 +895,8 @@ var MintingGateway = class {
874
895
  lockId = await this.ledger.lockForMinting(
875
896
  request.userAddress,
876
897
  receiverConsent.amount,
877
- lockDurationMs
898
+ lockDurationMs,
899
+ request.pointTokenAddress
878
900
  );
879
901
  } catch (err) {
880
902
  throw new MintingGatewayError(
@@ -1164,11 +1186,19 @@ var PointIndexer = class {
1164
1186
  * issuer to mint without going through the gateway.
1165
1187
  */
1166
1188
  async finalize(evt) {
1167
- const locks = await this.ledger.getLockedRequests(evt.to);
1189
+ const locks = await this.ledger.getLockedRequests(
1190
+ evt.to,
1191
+ this.pointTokenAddress
1192
+ );
1168
1193
  const match = pickMatchingLock(locks, evt.amount);
1169
1194
  if (!match) return;
1170
1195
  try {
1171
- await this.ledger.deductBalance(evt.to, evt.amount, evt.txHash);
1196
+ await this.ledger.deductBalance(
1197
+ evt.to,
1198
+ evt.amount,
1199
+ evt.txHash,
1200
+ this.pointTokenAddress
1201
+ );
1172
1202
  } catch {
1173
1203
  return;
1174
1204
  }
@@ -1198,7 +1228,15 @@ var IssuerApiHandlers = class {
1198
1228
  gateway;
1199
1229
  ledger;
1200
1230
  provider;
1201
- pointTokenAddress;
1231
+ /**
1232
+ * Set of supported PointToken addresses (checksum-normalized). Handlers
1233
+ * validate the request's `pointTokenAddress` against this set.
1234
+ */
1235
+ supportedTokens;
1236
+ /** First supported token — used as default when a handler doesn't
1237
+ * receive a `pointTokenAddress` in the request (shouldn't happen in
1238
+ * practice, but keeps type-narrowing happy). */
1239
+ defaultToken;
1202
1240
  chainId;
1203
1241
  contracts;
1204
1242
  feeManager;
@@ -1208,7 +1246,15 @@ var IssuerApiHandlers = class {
1208
1246
  this.gateway = config.gateway;
1209
1247
  this.ledger = config.ledger;
1210
1248
  this.provider = config.provider;
1211
- this.pointTokenAddress = config.pointTokenAddress;
1249
+ const raw = config.pointTokenAddresses && config.pointTokenAddresses.length > 0 ? config.pointTokenAddresses : config.pointTokenAddress ? [config.pointTokenAddress] : [];
1250
+ if (raw.length === 0) {
1251
+ throw new Error(
1252
+ "IssuerApiHandlers: pointTokenAddress or pointTokenAddresses required"
1253
+ );
1254
+ }
1255
+ const normalized = raw.map((a) => (0, import_viem6.getAddress)(a));
1256
+ this.supportedTokens = new Set(normalized);
1257
+ this.defaultToken = normalized[0];
1212
1258
  this.chainId = config.chainId;
1213
1259
  this.contracts = config.contracts;
1214
1260
  if (config.feeManager) this.feeManager = config.feeManager;
@@ -1305,21 +1351,26 @@ var IssuerApiHandlers = class {
1305
1351
  );
1306
1352
  }
1307
1353
  const pointToken = (0, import_viem6.getAddress)(request.pointTokenAddress);
1308
- if (pointToken !== this.pointTokenAddress) {
1354
+ if (!this.supportedTokens.has(pointToken)) {
1309
1355
  throw new Error(
1310
1356
  `handleUser: unsupported pointToken ${pointToken}`
1311
1357
  );
1312
1358
  }
1313
- const [mintRequestNonce, receiverConsentNonce, balance, minter] = await Promise.all([
1359
+ const [mintRequestNonce, receiverConsentNonce, offChainBalance, onChainBalance, minter] = await Promise.all([
1314
1360
  (0, import_core5.getMintRequestNonce)(this.provider, pointToken, normalizedAuthed),
1315
1361
  (0, import_core5.getReceiverConsentNonce)(this.provider, pointToken, normalizedAuthed),
1316
- this.ledger.getBalance(normalizedAuthed),
1362
+ this.ledger.getBalance(normalizedAuthed, pointToken),
1363
+ (0, import_core5.getPointTokenBalance)(this.provider, pointToken, normalizedAuthed),
1317
1364
  (0, import_core5.isMinter)(this.provider, pointToken, normalizedAuthed)
1318
1365
  ]);
1319
1366
  return {
1320
1367
  mintRequestNonce,
1321
1368
  receiverConsentNonce,
1322
- balance,
1369
+ offChainBalance,
1370
+ onChainBalance,
1371
+ totalBalance: offChainBalance + onChainBalance,
1372
+ balance: offChainBalance,
1373
+ // deprecated alias
1323
1374
  isMinter: minter
1324
1375
  };
1325
1376
  }
@@ -1343,7 +1394,7 @@ var IssuerApiHandlers = class {
1343
1394
  );
1344
1395
  }
1345
1396
  const pointToken = (0, import_viem6.getAddress)(request.pointTokenAddress);
1346
- if (pointToken !== this.pointTokenAddress) {
1397
+ if (!this.supportedTokens.has(pointToken)) {
1347
1398
  throw new Error(
1348
1399
  `handleBuildConsentTypedData: unsupported pointToken ${pointToken}`
1349
1400
  );
@@ -1377,7 +1428,7 @@ var IssuerApiHandlers = class {
1377
1428
  );
1378
1429
  }
1379
1430
  const pointToken = (0, import_viem6.getAddress)(request.pointTokenAddress);
1380
- if (pointToken !== this.pointTokenAddress) {
1431
+ if (!this.supportedTokens.has(pointToken)) {
1381
1432
  throw new Error(
1382
1433
  `handleClaimAndSwap: unsupported pointToken ${pointToken}`
1383
1434
  );
@@ -1615,6 +1666,7 @@ function toUsdtPerNative(priceFloat, usdtDecimals) {
1615
1666
  }
1616
1667
 
1617
1668
  // src/config.ts
1669
+ var import_viem7 = require("viem");
1618
1670
  function createIssuerService(config) {
1619
1671
  if (!config.provider) {
1620
1672
  throw new Error("createIssuerService: provider is required");
@@ -1625,9 +1677,6 @@ function createIssuerService(config) {
1625
1677
  if (!config.signer) {
1626
1678
  throw new Error("createIssuerService: signer is required");
1627
1679
  }
1628
- if (!config.pointTokenAddress) {
1629
- throw new Error("createIssuerService: pointTokenAddress is required");
1630
- }
1631
1680
  if (!config.relayAddress) {
1632
1681
  throw new Error("createIssuerService: relayAddress is required");
1633
1682
  }
@@ -1637,6 +1686,13 @@ function createIssuerService(config) {
1637
1686
  if (!config.auth?.domain) {
1638
1687
  throw new Error("createIssuerService: auth.domain is required");
1639
1688
  }
1689
+ const rawAddresses = config.pointTokenAddresses && config.pointTokenAddresses.length > 0 ? config.pointTokenAddresses : config.pointTokenAddress ? [config.pointTokenAddress] : [];
1690
+ if (rawAddresses.length === 0) {
1691
+ throw new Error(
1692
+ "createIssuerService: at least one of pointTokenAddress / pointTokenAddresses is required"
1693
+ );
1694
+ }
1695
+ const tokenAddresses = rawAddresses.map((a) => (0, import_viem7.getAddress)(a));
1640
1696
  const ledger = config.ledger ?? new MemoryPointLedger();
1641
1697
  const sessionStore = config.sessionStore ?? new MemorySessionStore();
1642
1698
  const policy = config.policy ?? new DefaultPolicyEngine({ ledger });
@@ -1680,33 +1736,37 @@ function createIssuerService(config) {
1680
1736
  gatewayConfig.defaultLockBufferMs = config.gateway.defaultLockBufferMs;
1681
1737
  }
1682
1738
  const gateway = new MintingGateway(gatewayConfig);
1683
- const indexerConfig = {
1684
- provider: config.provider,
1685
- pointTokenAddress: config.pointTokenAddress,
1686
- ledger
1687
- };
1688
- if (config.indexer?.fromBlock !== void 0) {
1689
- indexerConfig.fromBlock = config.indexer.fromBlock;
1690
- }
1691
- if (config.indexer?.cursorStore) {
1692
- indexerConfig.cursorStore = config.indexer.cursorStore;
1693
- }
1694
- if (config.indexer?.confirmations !== void 0) {
1695
- indexerConfig.confirmations = config.indexer.confirmations;
1696
- }
1697
- if (config.indexer?.batchSize !== void 0) {
1698
- indexerConfig.batchSize = config.indexer.batchSize;
1699
- }
1700
- if (config.indexer?.pollIntervalMs !== void 0) {
1701
- indexerConfig.pollIntervalMs = config.indexer.pollIntervalMs;
1739
+ const indexers = /* @__PURE__ */ new Map();
1740
+ for (const tokenAddress of tokenAddresses) {
1741
+ const indexerConfig = {
1742
+ provider: config.provider,
1743
+ pointTokenAddress: tokenAddress,
1744
+ ledger
1745
+ };
1746
+ if (config.indexer?.fromBlock !== void 0) {
1747
+ indexerConfig.fromBlock = config.indexer.fromBlock;
1748
+ }
1749
+ if (config.indexer?.cursorStore) {
1750
+ indexerConfig.cursorStore = config.indexer.cursorStore;
1751
+ }
1752
+ if (config.indexer?.confirmations !== void 0) {
1753
+ indexerConfig.confirmations = config.indexer.confirmations;
1754
+ }
1755
+ if (config.indexer?.batchSize !== void 0) {
1756
+ indexerConfig.batchSize = config.indexer.batchSize;
1757
+ }
1758
+ if (config.indexer?.pollIntervalMs !== void 0) {
1759
+ indexerConfig.pollIntervalMs = config.indexer.pollIntervalMs;
1760
+ }
1761
+ indexers.set(tokenAddress, new PointIndexer(indexerConfig));
1702
1762
  }
1703
- const indexer = new PointIndexer(indexerConfig);
1763
+ const firstIndexer = indexers.get(tokenAddresses[0]);
1704
1764
  const handlersConfig = {
1705
1765
  authService,
1706
1766
  gateway,
1707
1767
  ledger,
1708
1768
  provider: config.provider,
1709
- pointTokenAddress: config.pointTokenAddress,
1769
+ pointTokenAddresses: tokenAddresses,
1710
1770
  chainId: config.chainId,
1711
1771
  contracts: config.contracts
1712
1772
  };
@@ -1714,7 +1774,9 @@ function createIssuerService(config) {
1714
1774
  if (config.poolsProvider) handlersConfig.poolsProvider = config.poolsProvider;
1715
1775
  const handlers = new IssuerApiHandlers(handlersConfig);
1716
1776
  if (config.indexer?.autoStart) {
1717
- indexer.start();
1777
+ for (const idx of indexers.values()) {
1778
+ idx.start();
1779
+ }
1718
1780
  }
1719
1781
  return {
1720
1782
  authService,
@@ -1725,7 +1787,8 @@ function createIssuerService(config) {
1725
1787
  relayService,
1726
1788
  feeManager,
1727
1789
  gateway,
1728
- indexer,
1790
+ indexers,
1791
+ indexer: firstIndexer,
1729
1792
  handlers
1730
1793
  };
1731
1794
  }