@pafi-dev/issuer 0.1.2 → 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,7 +1351,7 @@ 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
  );
@@ -1313,7 +1359,7 @@ var IssuerApiHandlers = class {
1313
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),
1317
1363
  (0, import_core5.getPointTokenBalance)(this.provider, pointToken, normalizedAuthed),
1318
1364
  (0, import_core5.isMinter)(this.provider, pointToken, normalizedAuthed)
1319
1365
  ]);
@@ -1348,7 +1394,7 @@ var IssuerApiHandlers = class {
1348
1394
  );
1349
1395
  }
1350
1396
  const pointToken = (0, import_viem6.getAddress)(request.pointTokenAddress);
1351
- if (pointToken !== this.pointTokenAddress) {
1397
+ if (!this.supportedTokens.has(pointToken)) {
1352
1398
  throw new Error(
1353
1399
  `handleBuildConsentTypedData: unsupported pointToken ${pointToken}`
1354
1400
  );
@@ -1382,7 +1428,7 @@ var IssuerApiHandlers = class {
1382
1428
  );
1383
1429
  }
1384
1430
  const pointToken = (0, import_viem6.getAddress)(request.pointTokenAddress);
1385
- if (pointToken !== this.pointTokenAddress) {
1431
+ if (!this.supportedTokens.has(pointToken)) {
1386
1432
  throw new Error(
1387
1433
  `handleClaimAndSwap: unsupported pointToken ${pointToken}`
1388
1434
  );
@@ -1620,6 +1666,7 @@ function toUsdtPerNative(priceFloat, usdtDecimals) {
1620
1666
  }
1621
1667
 
1622
1668
  // src/config.ts
1669
+ var import_viem7 = require("viem");
1623
1670
  function createIssuerService(config) {
1624
1671
  if (!config.provider) {
1625
1672
  throw new Error("createIssuerService: provider is required");
@@ -1630,9 +1677,6 @@ function createIssuerService(config) {
1630
1677
  if (!config.signer) {
1631
1678
  throw new Error("createIssuerService: signer is required");
1632
1679
  }
1633
- if (!config.pointTokenAddress) {
1634
- throw new Error("createIssuerService: pointTokenAddress is required");
1635
- }
1636
1680
  if (!config.relayAddress) {
1637
1681
  throw new Error("createIssuerService: relayAddress is required");
1638
1682
  }
@@ -1642,6 +1686,13 @@ function createIssuerService(config) {
1642
1686
  if (!config.auth?.domain) {
1643
1687
  throw new Error("createIssuerService: auth.domain is required");
1644
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));
1645
1696
  const ledger = config.ledger ?? new MemoryPointLedger();
1646
1697
  const sessionStore = config.sessionStore ?? new MemorySessionStore();
1647
1698
  const policy = config.policy ?? new DefaultPolicyEngine({ ledger });
@@ -1685,33 +1736,37 @@ function createIssuerService(config) {
1685
1736
  gatewayConfig.defaultLockBufferMs = config.gateway.defaultLockBufferMs;
1686
1737
  }
1687
1738
  const gateway = new MintingGateway(gatewayConfig);
1688
- const indexerConfig = {
1689
- provider: config.provider,
1690
- pointTokenAddress: config.pointTokenAddress,
1691
- ledger
1692
- };
1693
- if (config.indexer?.fromBlock !== void 0) {
1694
- indexerConfig.fromBlock = config.indexer.fromBlock;
1695
- }
1696
- if (config.indexer?.cursorStore) {
1697
- indexerConfig.cursorStore = config.indexer.cursorStore;
1698
- }
1699
- if (config.indexer?.confirmations !== void 0) {
1700
- indexerConfig.confirmations = config.indexer.confirmations;
1701
- }
1702
- if (config.indexer?.batchSize !== void 0) {
1703
- indexerConfig.batchSize = config.indexer.batchSize;
1704
- }
1705
- if (config.indexer?.pollIntervalMs !== void 0) {
1706
- 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));
1707
1762
  }
1708
- const indexer = new PointIndexer(indexerConfig);
1763
+ const firstIndexer = indexers.get(tokenAddresses[0]);
1709
1764
  const handlersConfig = {
1710
1765
  authService,
1711
1766
  gateway,
1712
1767
  ledger,
1713
1768
  provider: config.provider,
1714
- pointTokenAddress: config.pointTokenAddress,
1769
+ pointTokenAddresses: tokenAddresses,
1715
1770
  chainId: config.chainId,
1716
1771
  contracts: config.contracts
1717
1772
  };
@@ -1719,7 +1774,9 @@ function createIssuerService(config) {
1719
1774
  if (config.poolsProvider) handlersConfig.poolsProvider = config.poolsProvider;
1720
1775
  const handlers = new IssuerApiHandlers(handlersConfig);
1721
1776
  if (config.indexer?.autoStart) {
1722
- indexer.start();
1777
+ for (const idx of indexers.values()) {
1778
+ idx.start();
1779
+ }
1723
1780
  }
1724
1781
  return {
1725
1782
  authService,
@@ -1730,7 +1787,8 @@ function createIssuerService(config) {
1730
1787
  relayService,
1731
1788
  feeManager,
1732
1789
  gateway,
1733
- indexer,
1790
+ indexers,
1791
+ indexer: firstIndexer,
1734
1792
  handlers
1735
1793
  };
1736
1794
  }