@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.d.cts CHANGED
@@ -18,6 +18,12 @@ interface LockedMintRequest {
18
18
  lockId: string;
19
19
  userAddress: Address;
20
20
  amount: bigint;
21
+ /**
22
+ * Which PointToken this lock belongs to. Added in 0.2.0 for multi-token
23
+ * issuer support. Optional for backward compat with 0.1.x ledgers —
24
+ * single-token consumers can ignore it.
25
+ */
26
+ tokenAddress?: Address;
21
27
  /** Lifecycle status */
22
28
  status: MintingStatus;
23
29
  /** When the lock was created (epoch ms) */
@@ -33,20 +39,28 @@ interface LockedMintRequest {
33
39
  *
34
40
  * Issuers replace the in-memory default with their own database-backed
35
41
  * implementation (Postgres, Redis, etc.).
42
+ *
43
+ * **Multi-token support (0.2.0):**
44
+ * Every mutating method accepts an optional `tokenAddress` parameter so
45
+ * balances can be scoped per `(user, token)`. Single-token issuers can
46
+ * ignore the parameter entirely — legacy 0.1.x implementations remain
47
+ * compatible. Multi-token issuers must persist + query balances keyed by
48
+ * `(userAddress, tokenAddress)`.
36
49
  */
37
50
  interface IPointLedger {
38
51
  /** Get a user's available off-chain point balance (excluding locked). */
39
- getBalance(userAddress: Address): Promise<bigint>;
52
+ getBalance(userAddress: Address, tokenAddress?: Address): Promise<bigint>;
40
53
  /**
41
54
  * Lock an amount for a pending mint request. Locked amounts are reserved
42
55
  * but not yet deducted; they protect against double-spend during the
43
56
  * EIP-712 validity window.
44
57
  *
45
58
  * @param lockDurationMs how long the lock should be held before auto-expiry
59
+ * @param tokenAddress which PointToken this lock is for (0.2.0+)
46
60
  * @returns lockId — opaque handle used by `releaseLock` / `updateMintStatus`
47
61
  * @throws if the user's available balance is below `amount`
48
62
  */
49
- lockForMinting(userAddress: Address, amount: bigint, lockDurationMs: number): Promise<string>;
63
+ lockForMinting(userAddress: Address, amount: bigint, lockDurationMs: number, tokenAddress?: Address): Promise<string>;
50
64
  /** Release a previously created lock (e.g. on tx failure / cancel). */
51
65
  releaseLock(lockId: string): Promise<void>;
52
66
  /**
@@ -54,11 +68,11 @@ interface IPointLedger {
54
68
  * mint has been observed by the indexer. Should also resolve any matching
55
69
  * lock so the funds aren't double-counted.
56
70
  */
57
- deductBalance(userAddress: Address, amount: bigint, txHash: Hex): Promise<void>;
71
+ deductBalance(userAddress: Address, amount: bigint, txHash: Hex, tokenAddress?: Address): Promise<void>;
58
72
  /** Credit points to a user's balance (e.g. from merchant activity). */
59
- creditBalance(userAddress: Address, amount: bigint, reason: string): Promise<void>;
73
+ creditBalance(userAddress: Address, amount: bigint, reason: string, tokenAddress?: Address): Promise<void>;
60
74
  /** List currently-pending locked mint requests for a user. */
61
- getLockedRequests(userAddress: Address): Promise<LockedMintRequest[]>;
75
+ getLockedRequests(userAddress: Address, tokenAddress?: Address): Promise<LockedMintRequest[]>;
62
76
  /**
63
77
  * Transition a lock to a new lifecycle status. The on-chain tx hash is
64
78
  * supplied when the status is `MINTED`.
@@ -75,6 +89,10 @@ interface IPointLedger {
75
89
  * Concurrency model: single-process, single-threaded (Node.js event loop).
76
90
  * The lock check + insert is atomic within a tick because no awaits sit
77
91
  * between balance read and lock write.
92
+ *
93
+ * **Multi-token (0.2.0):** Balances are keyed by `(user, token)`. If callers
94
+ * omit `tokenAddress`, the literal string "default" is used — that keeps
95
+ * single-token usage working exactly like 0.1.x.
78
96
  */
79
97
  declare class MemoryPointLedger implements IPointLedger {
80
98
  private balances;
@@ -84,12 +102,12 @@ declare class MemoryPointLedger implements IPointLedger {
84
102
  constructor(opts?: {
85
103
  now?: () => number;
86
104
  });
87
- getBalance(userAddress: Address): Promise<bigint>;
88
- getLockedRequests(userAddress: Address): Promise<LockedMintRequest[]>;
89
- creditBalance(userAddress: Address, amount: bigint, _reason: string): Promise<void>;
90
- lockForMinting(userAddress: Address, amount: bigint, lockDurationMs: number): Promise<string>;
105
+ getBalance(userAddress: Address, tokenAddress?: Address): Promise<bigint>;
106
+ getLockedRequests(userAddress: Address, tokenAddress?: Address): Promise<LockedMintRequest[]>;
107
+ creditBalance(userAddress: Address, amount: bigint, _reason: string, tokenAddress?: Address): Promise<void>;
108
+ lockForMinting(userAddress: Address, amount: bigint, lockDurationMs: number, tokenAddress?: Address): Promise<string>;
91
109
  releaseLock(lockId: string): Promise<void>;
92
- deductBalance(userAddress: Address, amount: bigint, txHash: Hex): Promise<void>;
110
+ deductBalance(userAddress: Address, amount: bigint, txHash: Hex, tokenAddress?: Address): Promise<void>;
93
111
  updateMintStatus(lockId: string, status: MintingStatus, txHash?: Hex): Promise<void>;
94
112
  /**
95
113
  * Auto-expire any PENDING lock past its expiry. Called lazily on every
@@ -927,6 +945,25 @@ interface ApiUserRequest {
927
945
  interface ApiUserResponse {
928
946
  mintRequestNonce: bigint;
929
947
  receiverConsentNonce: bigint;
948
+ /**
949
+ * Off-chain point balance from the issuer's ledger (excludes PENDING locks).
950
+ * This is what the user can claim into on-chain PT via `/claim`.
951
+ */
952
+ offChainBalance: bigint;
953
+ /**
954
+ * On-chain ERC-20 balance from `PointToken.balanceOf(user)`.
955
+ * Points the user has already claimed but hasn't swapped or redeemed.
956
+ */
957
+ onChainBalance: bigint;
958
+ /**
959
+ * Sum of off-chain + on-chain balance. FE renders this as a single
960
+ * "your points" number, per the unified-balance UX spec.
961
+ */
962
+ totalBalance: bigint;
963
+ /**
964
+ * @deprecated use `offChainBalance` instead. Kept for backward compatibility
965
+ * — will be removed in 0.2.0.
966
+ */
930
967
  balance: bigint;
931
968
  isMinter: boolean;
932
969
  }
@@ -983,7 +1020,16 @@ interface IssuerApiHandlersConfig {
983
1020
  ledger: IPointLedger;
984
1021
  /** Used by `handleUser` to read on-chain nonces and minter status. */
985
1022
  provider: PublicClient;
986
- pointTokenAddress: Address;
1023
+ /**
1024
+ * Legacy single-token config. Prefer `pointTokenAddresses` for multi-token
1025
+ * issuers (0.2.0+).
1026
+ */
1027
+ pointTokenAddress?: Address;
1028
+ /**
1029
+ * All supported PointToken addresses. Handlers accept any address in this
1030
+ * list; others are rejected with "unsupported pointToken".
1031
+ */
1032
+ pointTokenAddresses?: Address[];
987
1033
  chainId: number;
988
1034
  contracts: ApiConfigResponse["contracts"];
989
1035
  /** Required by `handleGasFee`; omit to disable the endpoint. */
@@ -1008,7 +1054,15 @@ declare class IssuerApiHandlers {
1008
1054
  private readonly gateway;
1009
1055
  private readonly ledger;
1010
1056
  private readonly provider;
1011
- private readonly pointTokenAddress;
1057
+ /**
1058
+ * Set of supported PointToken addresses (checksum-normalized). Handlers
1059
+ * validate the request's `pointTokenAddress` against this set.
1060
+ */
1061
+ private readonly supportedTokens;
1062
+ /** First supported token — used as default when a handler doesn't
1063
+ * receive a `pointTokenAddress` in the request (shouldn't happen in
1064
+ * practice, but keeps type-narrowing happy). */
1065
+ private readonly defaultToken;
1012
1066
  private readonly chainId;
1013
1067
  private readonly contracts;
1014
1068
  private readonly feeManager?;
@@ -1199,11 +1253,24 @@ declare function createSubgraphNativeUsdtQuoter(config: SubgraphNativeUsdtQuoter
1199
1253
  * falls back to the in-memory dev defaults — that makes the happy path
1200
1254
  * a single-call wire-up while still letting production issuers plug in
1201
1255
  * their own ledger, session store, policy engine, and KMS signer.
1256
+ *
1257
+ * **Multi-token (0.2.0):** Pass `pointTokenAddresses: Address[]` to
1258
+ * support multiple PointTokens under a single issuer backend. Legacy
1259
+ * `pointTokenAddress: Address` still works for single-token deployments.
1260
+ * When both are provided, `pointTokenAddresses` takes precedence.
1202
1261
  */
1203
1262
  interface IssuerServiceConfig {
1204
1263
  chainId: number;
1205
- /** Address of the deployed PointToken (one per issuer instance). */
1206
- pointTokenAddress: Address;
1264
+ /**
1265
+ * Address of the deployed PointToken. Legacy single-token shortcut;
1266
+ * prefer `pointTokenAddresses` for multi-token issuers.
1267
+ */
1268
+ pointTokenAddress?: Address;
1269
+ /**
1270
+ * All PointToken addresses this issuer supports. Takes precedence over
1271
+ * `pointTokenAddress`. Factory creates one `PointIndexer` per address.
1272
+ */
1273
+ pointTokenAddresses?: Address[];
1207
1274
  /** Address of the deployed Relay contract. */
1208
1275
  relayAddress: Address;
1209
1276
  /**
@@ -1276,6 +1343,16 @@ interface IssuerService {
1276
1343
  relayService: RelayService;
1277
1344
  feeManager: FeeManager | undefined;
1278
1345
  gateway: MintingGateway;
1346
+ /**
1347
+ * All indexers keyed by PointToken address. For multi-token issuers there
1348
+ * is one per configured token. Single-token issuers will find one entry.
1349
+ */
1350
+ indexers: Map<Address, PointIndexer>;
1351
+ /**
1352
+ * First indexer. Kept for backward compat with 0.1.x callers that
1353
+ * expected `service.indexer` to be a single instance.
1354
+ * @deprecated use `indexers.get(tokenAddress)` for multi-token.
1355
+ */
1279
1356
  indexer: PointIndexer;
1280
1357
  handlers: IssuerApiHandlers;
1281
1358
  }
@@ -1293,7 +1370,7 @@ interface IssuerService {
1293
1370
  * - `indexer.autoStart` → false
1294
1371
  *
1295
1372
  * Throws synchronously if any required field (`signer`, `provider`,
1296
- * `operatorWallet`, `auth.jwtSecret`, `pointTokenAddress`, ) is missing.
1373
+ * `operatorWallet`, `auth.jwtSecret`, at least one point token) is missing.
1297
1374
  */
1298
1375
  declare function createIssuerService(config: IssuerServiceConfig): IssuerService;
1299
1376
 
package/dist/index.d.ts CHANGED
@@ -18,6 +18,12 @@ interface LockedMintRequest {
18
18
  lockId: string;
19
19
  userAddress: Address;
20
20
  amount: bigint;
21
+ /**
22
+ * Which PointToken this lock belongs to. Added in 0.2.0 for multi-token
23
+ * issuer support. Optional for backward compat with 0.1.x ledgers —
24
+ * single-token consumers can ignore it.
25
+ */
26
+ tokenAddress?: Address;
21
27
  /** Lifecycle status */
22
28
  status: MintingStatus;
23
29
  /** When the lock was created (epoch ms) */
@@ -33,20 +39,28 @@ interface LockedMintRequest {
33
39
  *
34
40
  * Issuers replace the in-memory default with their own database-backed
35
41
  * implementation (Postgres, Redis, etc.).
42
+ *
43
+ * **Multi-token support (0.2.0):**
44
+ * Every mutating method accepts an optional `tokenAddress` parameter so
45
+ * balances can be scoped per `(user, token)`. Single-token issuers can
46
+ * ignore the parameter entirely — legacy 0.1.x implementations remain
47
+ * compatible. Multi-token issuers must persist + query balances keyed by
48
+ * `(userAddress, tokenAddress)`.
36
49
  */
37
50
  interface IPointLedger {
38
51
  /** Get a user's available off-chain point balance (excluding locked). */
39
- getBalance(userAddress: Address): Promise<bigint>;
52
+ getBalance(userAddress: Address, tokenAddress?: Address): Promise<bigint>;
40
53
  /**
41
54
  * Lock an amount for a pending mint request. Locked amounts are reserved
42
55
  * but not yet deducted; they protect against double-spend during the
43
56
  * EIP-712 validity window.
44
57
  *
45
58
  * @param lockDurationMs how long the lock should be held before auto-expiry
59
+ * @param tokenAddress which PointToken this lock is for (0.2.0+)
46
60
  * @returns lockId — opaque handle used by `releaseLock` / `updateMintStatus`
47
61
  * @throws if the user's available balance is below `amount`
48
62
  */
49
- lockForMinting(userAddress: Address, amount: bigint, lockDurationMs: number): Promise<string>;
63
+ lockForMinting(userAddress: Address, amount: bigint, lockDurationMs: number, tokenAddress?: Address): Promise<string>;
50
64
  /** Release a previously created lock (e.g. on tx failure / cancel). */
51
65
  releaseLock(lockId: string): Promise<void>;
52
66
  /**
@@ -54,11 +68,11 @@ interface IPointLedger {
54
68
  * mint has been observed by the indexer. Should also resolve any matching
55
69
  * lock so the funds aren't double-counted.
56
70
  */
57
- deductBalance(userAddress: Address, amount: bigint, txHash: Hex): Promise<void>;
71
+ deductBalance(userAddress: Address, amount: bigint, txHash: Hex, tokenAddress?: Address): Promise<void>;
58
72
  /** Credit points to a user's balance (e.g. from merchant activity). */
59
- creditBalance(userAddress: Address, amount: bigint, reason: string): Promise<void>;
73
+ creditBalance(userAddress: Address, amount: bigint, reason: string, tokenAddress?: Address): Promise<void>;
60
74
  /** List currently-pending locked mint requests for a user. */
61
- getLockedRequests(userAddress: Address): Promise<LockedMintRequest[]>;
75
+ getLockedRequests(userAddress: Address, tokenAddress?: Address): Promise<LockedMintRequest[]>;
62
76
  /**
63
77
  * Transition a lock to a new lifecycle status. The on-chain tx hash is
64
78
  * supplied when the status is `MINTED`.
@@ -75,6 +89,10 @@ interface IPointLedger {
75
89
  * Concurrency model: single-process, single-threaded (Node.js event loop).
76
90
  * The lock check + insert is atomic within a tick because no awaits sit
77
91
  * between balance read and lock write.
92
+ *
93
+ * **Multi-token (0.2.0):** Balances are keyed by `(user, token)`. If callers
94
+ * omit `tokenAddress`, the literal string "default" is used — that keeps
95
+ * single-token usage working exactly like 0.1.x.
78
96
  */
79
97
  declare class MemoryPointLedger implements IPointLedger {
80
98
  private balances;
@@ -84,12 +102,12 @@ declare class MemoryPointLedger implements IPointLedger {
84
102
  constructor(opts?: {
85
103
  now?: () => number;
86
104
  });
87
- getBalance(userAddress: Address): Promise<bigint>;
88
- getLockedRequests(userAddress: Address): Promise<LockedMintRequest[]>;
89
- creditBalance(userAddress: Address, amount: bigint, _reason: string): Promise<void>;
90
- lockForMinting(userAddress: Address, amount: bigint, lockDurationMs: number): Promise<string>;
105
+ getBalance(userAddress: Address, tokenAddress?: Address): Promise<bigint>;
106
+ getLockedRequests(userAddress: Address, tokenAddress?: Address): Promise<LockedMintRequest[]>;
107
+ creditBalance(userAddress: Address, amount: bigint, _reason: string, tokenAddress?: Address): Promise<void>;
108
+ lockForMinting(userAddress: Address, amount: bigint, lockDurationMs: number, tokenAddress?: Address): Promise<string>;
91
109
  releaseLock(lockId: string): Promise<void>;
92
- deductBalance(userAddress: Address, amount: bigint, txHash: Hex): Promise<void>;
110
+ deductBalance(userAddress: Address, amount: bigint, txHash: Hex, tokenAddress?: Address): Promise<void>;
93
111
  updateMintStatus(lockId: string, status: MintingStatus, txHash?: Hex): Promise<void>;
94
112
  /**
95
113
  * Auto-expire any PENDING lock past its expiry. Called lazily on every
@@ -927,6 +945,25 @@ interface ApiUserRequest {
927
945
  interface ApiUserResponse {
928
946
  mintRequestNonce: bigint;
929
947
  receiverConsentNonce: bigint;
948
+ /**
949
+ * Off-chain point balance from the issuer's ledger (excludes PENDING locks).
950
+ * This is what the user can claim into on-chain PT via `/claim`.
951
+ */
952
+ offChainBalance: bigint;
953
+ /**
954
+ * On-chain ERC-20 balance from `PointToken.balanceOf(user)`.
955
+ * Points the user has already claimed but hasn't swapped or redeemed.
956
+ */
957
+ onChainBalance: bigint;
958
+ /**
959
+ * Sum of off-chain + on-chain balance. FE renders this as a single
960
+ * "your points" number, per the unified-balance UX spec.
961
+ */
962
+ totalBalance: bigint;
963
+ /**
964
+ * @deprecated use `offChainBalance` instead. Kept for backward compatibility
965
+ * — will be removed in 0.2.0.
966
+ */
930
967
  balance: bigint;
931
968
  isMinter: boolean;
932
969
  }
@@ -983,7 +1020,16 @@ interface IssuerApiHandlersConfig {
983
1020
  ledger: IPointLedger;
984
1021
  /** Used by `handleUser` to read on-chain nonces and minter status. */
985
1022
  provider: PublicClient;
986
- pointTokenAddress: Address;
1023
+ /**
1024
+ * Legacy single-token config. Prefer `pointTokenAddresses` for multi-token
1025
+ * issuers (0.2.0+).
1026
+ */
1027
+ pointTokenAddress?: Address;
1028
+ /**
1029
+ * All supported PointToken addresses. Handlers accept any address in this
1030
+ * list; others are rejected with "unsupported pointToken".
1031
+ */
1032
+ pointTokenAddresses?: Address[];
987
1033
  chainId: number;
988
1034
  contracts: ApiConfigResponse["contracts"];
989
1035
  /** Required by `handleGasFee`; omit to disable the endpoint. */
@@ -1008,7 +1054,15 @@ declare class IssuerApiHandlers {
1008
1054
  private readonly gateway;
1009
1055
  private readonly ledger;
1010
1056
  private readonly provider;
1011
- private readonly pointTokenAddress;
1057
+ /**
1058
+ * Set of supported PointToken addresses (checksum-normalized). Handlers
1059
+ * validate the request's `pointTokenAddress` against this set.
1060
+ */
1061
+ private readonly supportedTokens;
1062
+ /** First supported token — used as default when a handler doesn't
1063
+ * receive a `pointTokenAddress` in the request (shouldn't happen in
1064
+ * practice, but keeps type-narrowing happy). */
1065
+ private readonly defaultToken;
1012
1066
  private readonly chainId;
1013
1067
  private readonly contracts;
1014
1068
  private readonly feeManager?;
@@ -1199,11 +1253,24 @@ declare function createSubgraphNativeUsdtQuoter(config: SubgraphNativeUsdtQuoter
1199
1253
  * falls back to the in-memory dev defaults — that makes the happy path
1200
1254
  * a single-call wire-up while still letting production issuers plug in
1201
1255
  * their own ledger, session store, policy engine, and KMS signer.
1256
+ *
1257
+ * **Multi-token (0.2.0):** Pass `pointTokenAddresses: Address[]` to
1258
+ * support multiple PointTokens under a single issuer backend. Legacy
1259
+ * `pointTokenAddress: Address` still works for single-token deployments.
1260
+ * When both are provided, `pointTokenAddresses` takes precedence.
1202
1261
  */
1203
1262
  interface IssuerServiceConfig {
1204
1263
  chainId: number;
1205
- /** Address of the deployed PointToken (one per issuer instance). */
1206
- pointTokenAddress: Address;
1264
+ /**
1265
+ * Address of the deployed PointToken. Legacy single-token shortcut;
1266
+ * prefer `pointTokenAddresses` for multi-token issuers.
1267
+ */
1268
+ pointTokenAddress?: Address;
1269
+ /**
1270
+ * All PointToken addresses this issuer supports. Takes precedence over
1271
+ * `pointTokenAddress`. Factory creates one `PointIndexer` per address.
1272
+ */
1273
+ pointTokenAddresses?: Address[];
1207
1274
  /** Address of the deployed Relay contract. */
1208
1275
  relayAddress: Address;
1209
1276
  /**
@@ -1276,6 +1343,16 @@ interface IssuerService {
1276
1343
  relayService: RelayService;
1277
1344
  feeManager: FeeManager | undefined;
1278
1345
  gateway: MintingGateway;
1346
+ /**
1347
+ * All indexers keyed by PointToken address. For multi-token issuers there
1348
+ * is one per configured token. Single-token issuers will find one entry.
1349
+ */
1350
+ indexers: Map<Address, PointIndexer>;
1351
+ /**
1352
+ * First indexer. Kept for backward compat with 0.1.x callers that
1353
+ * expected `service.indexer` to be a single instance.
1354
+ * @deprecated use `indexers.get(tokenAddress)` for multi-token.
1355
+ */
1279
1356
  indexer: PointIndexer;
1280
1357
  handlers: IssuerApiHandlers;
1281
1358
  }
@@ -1293,7 +1370,7 @@ interface IssuerService {
1293
1370
  * - `indexer.autoStart` → false
1294
1371
  *
1295
1372
  * Throws synchronously if any required field (`signer`, `provider`,
1296
- * `operatorWallet`, `auth.jwtSecret`, `pointTokenAddress`, ) is missing.
1373
+ * `operatorWallet`, `auth.jwtSecret`, at least one point token) is missing.
1297
1374
  */
1298
1375
  declare function createIssuerService(config: IssuerServiceConfig): IssuerService;
1299
1376