@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.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
  }
@@ -1169,7 +1199,15 @@ var IssuerApiHandlers = class {
1169
1199
  gateway;
1170
1200
  ledger;
1171
1201
  provider;
1172
- 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;
1173
1211
  chainId;
1174
1212
  contracts;
1175
1213
  feeManager;
@@ -1179,7 +1217,15 @@ var IssuerApiHandlers = class {
1179
1217
  this.gateway = config.gateway;
1180
1218
  this.ledger = config.ledger;
1181
1219
  this.provider = config.provider;
1182
- 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];
1183
1229
  this.chainId = config.chainId;
1184
1230
  this.contracts = config.contracts;
1185
1231
  if (config.feeManager) this.feeManager = config.feeManager;
@@ -1276,7 +1322,7 @@ var IssuerApiHandlers = class {
1276
1322
  );
1277
1323
  }
1278
1324
  const pointToken = getAddress5(request.pointTokenAddress);
1279
- if (pointToken !== this.pointTokenAddress) {
1325
+ if (!this.supportedTokens.has(pointToken)) {
1280
1326
  throw new Error(
1281
1327
  `handleUser: unsupported pointToken ${pointToken}`
1282
1328
  );
@@ -1284,7 +1330,7 @@ var IssuerApiHandlers = class {
1284
1330
  const [mintRequestNonce, receiverConsentNonce, offChainBalance, onChainBalance, minter] = await Promise.all([
1285
1331
  getMintRequestNonce(this.provider, pointToken, normalizedAuthed),
1286
1332
  getReceiverConsentNonce(this.provider, pointToken, normalizedAuthed),
1287
- this.ledger.getBalance(normalizedAuthed),
1333
+ this.ledger.getBalance(normalizedAuthed, pointToken),
1288
1334
  getPointTokenBalance(this.provider, pointToken, normalizedAuthed),
1289
1335
  isMinter(this.provider, pointToken, normalizedAuthed)
1290
1336
  ]);
@@ -1319,7 +1365,7 @@ var IssuerApiHandlers = class {
1319
1365
  );
1320
1366
  }
1321
1367
  const pointToken = getAddress5(request.pointTokenAddress);
1322
- if (pointToken !== this.pointTokenAddress) {
1368
+ if (!this.supportedTokens.has(pointToken)) {
1323
1369
  throw new Error(
1324
1370
  `handleBuildConsentTypedData: unsupported pointToken ${pointToken}`
1325
1371
  );
@@ -1353,7 +1399,7 @@ var IssuerApiHandlers = class {
1353
1399
  );
1354
1400
  }
1355
1401
  const pointToken = getAddress5(request.pointTokenAddress);
1356
- if (pointToken !== this.pointTokenAddress) {
1402
+ if (!this.supportedTokens.has(pointToken)) {
1357
1403
  throw new Error(
1358
1404
  `handleClaimAndSwap: unsupported pointToken ${pointToken}`
1359
1405
  );
@@ -1591,6 +1637,7 @@ function toUsdtPerNative(priceFloat, usdtDecimals) {
1591
1637
  }
1592
1638
 
1593
1639
  // src/config.ts
1640
+ import { getAddress as getAddress6 } from "viem";
1594
1641
  function createIssuerService(config) {
1595
1642
  if (!config.provider) {
1596
1643
  throw new Error("createIssuerService: provider is required");
@@ -1601,9 +1648,6 @@ function createIssuerService(config) {
1601
1648
  if (!config.signer) {
1602
1649
  throw new Error("createIssuerService: signer is required");
1603
1650
  }
1604
- if (!config.pointTokenAddress) {
1605
- throw new Error("createIssuerService: pointTokenAddress is required");
1606
- }
1607
1651
  if (!config.relayAddress) {
1608
1652
  throw new Error("createIssuerService: relayAddress is required");
1609
1653
  }
@@ -1613,6 +1657,13 @@ function createIssuerService(config) {
1613
1657
  if (!config.auth?.domain) {
1614
1658
  throw new Error("createIssuerService: auth.domain is required");
1615
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));
1616
1667
  const ledger = config.ledger ?? new MemoryPointLedger();
1617
1668
  const sessionStore = config.sessionStore ?? new MemorySessionStore();
1618
1669
  const policy = config.policy ?? new DefaultPolicyEngine({ ledger });
@@ -1656,33 +1707,37 @@ function createIssuerService(config) {
1656
1707
  gatewayConfig.defaultLockBufferMs = config.gateway.defaultLockBufferMs;
1657
1708
  }
1658
1709
  const gateway = new MintingGateway(gatewayConfig);
1659
- const indexerConfig = {
1660
- provider: config.provider,
1661
- pointTokenAddress: config.pointTokenAddress,
1662
- ledger
1663
- };
1664
- if (config.indexer?.fromBlock !== void 0) {
1665
- indexerConfig.fromBlock = config.indexer.fromBlock;
1666
- }
1667
- if (config.indexer?.cursorStore) {
1668
- indexerConfig.cursorStore = config.indexer.cursorStore;
1669
- }
1670
- if (config.indexer?.confirmations !== void 0) {
1671
- indexerConfig.confirmations = config.indexer.confirmations;
1672
- }
1673
- if (config.indexer?.batchSize !== void 0) {
1674
- indexerConfig.batchSize = config.indexer.batchSize;
1675
- }
1676
- if (config.indexer?.pollIntervalMs !== void 0) {
1677
- 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));
1678
1733
  }
1679
- const indexer = new PointIndexer(indexerConfig);
1734
+ const firstIndexer = indexers.get(tokenAddresses[0]);
1680
1735
  const handlersConfig = {
1681
1736
  authService,
1682
1737
  gateway,
1683
1738
  ledger,
1684
1739
  provider: config.provider,
1685
- pointTokenAddress: config.pointTokenAddress,
1740
+ pointTokenAddresses: tokenAddresses,
1686
1741
  chainId: config.chainId,
1687
1742
  contracts: config.contracts
1688
1743
  };
@@ -1690,7 +1745,9 @@ function createIssuerService(config) {
1690
1745
  if (config.poolsProvider) handlersConfig.poolsProvider = config.poolsProvider;
1691
1746
  const handlers = new IssuerApiHandlers(handlersConfig);
1692
1747
  if (config.indexer?.autoStart) {
1693
- indexer.start();
1748
+ for (const idx of indexers.values()) {
1749
+ idx.start();
1750
+ }
1694
1751
  }
1695
1752
  return {
1696
1753
  authService,
@@ -1701,7 +1758,8 @@ function createIssuerService(config) {
1701
1758
  relayService,
1702
1759
  feeManager,
1703
1760
  gateway,
1704
- indexer,
1761
+ indexers,
1762
+ indexer: firstIndexer,
1705
1763
  handlers
1706
1764
  };
1707
1765
  }