@pafi-dev/issuer 0.30.0 → 0.32.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 +87 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +120 -16
- package/dist/index.d.ts +120 -16
- package/dist/index.js +79 -13
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -1551,9 +1551,18 @@ interface SettlementClientConfig {
|
|
|
1551
1551
|
/**
|
|
1552
1552
|
* chainId — used to derive the issuer-api base URL via
|
|
1553
1553
|
* `getPafiServiceUrls(chainId).issuerApi`. SDK ships with the URL
|
|
1554
|
-
* per chainId; bump SDK version to retarget
|
|
1554
|
+
* per chainId; bump SDK version to retarget — OR pass `baseUrl`
|
|
1555
|
+
* to override per-deployment.
|
|
1555
1556
|
*/
|
|
1556
1557
|
chainId: number;
|
|
1558
|
+
/**
|
|
1559
|
+
* Optional override for the issuer-api base URL. Audit PACI5-17 —
|
|
1560
|
+
* production issuer backends should set this from an env var
|
|
1561
|
+
* (e.g. `PAFI_ISSUER_API_URL`) so the deployed binary doesn't
|
|
1562
|
+
* depend on the SDK ship-default that may target dev infrastructure.
|
|
1563
|
+
* Undefined / empty → use the ship-default for `chainId`.
|
|
1564
|
+
*/
|
|
1565
|
+
baseUrl?: string;
|
|
1557
1566
|
/** PAFI-assigned issuer id used in `X-Issuer-Id` header. */
|
|
1558
1567
|
issuerId: string;
|
|
1559
1568
|
/** Raw API key sent as `Authorization: Bearer <apiKey>`. */
|
|
@@ -1566,6 +1575,34 @@ interface SettlementClientConfig {
|
|
|
1566
1575
|
interface PolicyProviderConfig extends SettlementClientConfig {
|
|
1567
1576
|
/** Cache TTL in milliseconds. Default 5 * 60 * 1000 (5min). */
|
|
1568
1577
|
cacheTtlMs?: number;
|
|
1578
|
+
/**
|
|
1579
|
+
* Behavior khi settlement-api fetch fail (network blip, 5xx, timeout).
|
|
1580
|
+
*
|
|
1581
|
+
* Audit PACI5-17 — the pre-flight redeem limit is the SOLE gate
|
|
1582
|
+
* before the issuer signer mints a BurnRequest. Silently degrading
|
|
1583
|
+
* to a permissive default during settlement outages let users
|
|
1584
|
+
* exceed their configured limit until the outage cleared.
|
|
1585
|
+
*
|
|
1586
|
+
* - `'fail-closed'` (DEFAULT, recommended): throw
|
|
1587
|
+
* `PolicyProviderUnavailableError`. Caller maps to HTTP 503 so
|
|
1588
|
+
* the FE can retry; redemptions are blocked until policy fetch
|
|
1589
|
+
* succeeds.
|
|
1590
|
+
*
|
|
1591
|
+
* - `'permissive-default'`: return `defaultPolicyFor(issuerId)` and
|
|
1592
|
+
* fire `onWarning`. Pre-fix behavior. Operators who genuinely
|
|
1593
|
+
* prefer availability over enforcement (e.g. test issuers, low-
|
|
1594
|
+
* risk PT) MUST opt in explicitly AND wire `onWarning` to a
|
|
1595
|
+
* pager / Slack / Sentry alert. Silent permissive fallback is no
|
|
1596
|
+
* longer the default.
|
|
1597
|
+
*/
|
|
1598
|
+
onFetchFailure?: "fail-closed" | "permissive-default";
|
|
1599
|
+
/**
|
|
1600
|
+
* Observability hook for non-fatal events: permissive-default
|
|
1601
|
+
* fallbacks (when explicitly opted in). Wire to your logger /
|
|
1602
|
+
* Sentry / Datadog so the `policy_provider_fallback` event reaches
|
|
1603
|
+
* the on-call dashboard.
|
|
1604
|
+
*/
|
|
1605
|
+
onWarning?: (msg: string, ctx: Record<string, unknown>) => void;
|
|
1569
1606
|
/**
|
|
1570
1607
|
* Optional clock for testability. Returns unix milliseconds.
|
|
1571
1608
|
* Defaults to () => Date.now().
|
|
@@ -1596,6 +1633,8 @@ declare class PolicyProvider {
|
|
|
1596
1633
|
private readonly client;
|
|
1597
1634
|
private readonly issuerId;
|
|
1598
1635
|
private readonly cacheTtlMs;
|
|
1636
|
+
private readonly onFetchFailure;
|
|
1637
|
+
private readonly onWarning?;
|
|
1599
1638
|
private readonly now;
|
|
1600
1639
|
private cache;
|
|
1601
1640
|
private inflight;
|
|
@@ -1889,17 +1928,6 @@ interface PTRedeemHandlerConfig {
|
|
|
1889
1928
|
* Every `handle()` call validates the request's `pointTokenAddress`
|
|
1890
1929
|
* against this set BEFORE any chain read, signer call, or pending
|
|
1891
1930
|
* credit reservation.
|
|
1892
|
-
*
|
|
1893
|
-
* Audit PACI5-18 (redeem twin of the claim-side finding) — the read
|
|
1894
|
-
* surface (`IssuerApiHandlers.handleUser` / `handleRedemptionEvaluate`)
|
|
1895
|
-
* enforces this allowlist, but the write surface previously skipped
|
|
1896
|
-
* it. The asymmetry meant an issuer burner signer whitelisted on
|
|
1897
|
-
* multiple PointTokens could be coerced into signing a valid
|
|
1898
|
-
* `BurnRequest` for an off-allowlist token where no BurnIndexer is
|
|
1899
|
-
* running — the on-chain burn would succeed but the off-chain
|
|
1900
|
-
* pending credit would stay PENDING forever (or worse, resolve via
|
|
1901
|
-
* the bundler-receipt fallback against an unbacked credit balance).
|
|
1902
|
-
*
|
|
1903
1931
|
* Pass the SAME set used to construct `IssuerApiHandlers` and
|
|
1904
1932
|
* `PTClaimHandler` so the read, claim, and redeem paths agree on
|
|
1905
1933
|
* what this issuer is willing to sign for.
|
|
@@ -1998,7 +2026,7 @@ interface PTRedeemResponse {
|
|
|
1998
2026
|
/** The BurnRequest deadline (unix seconds) — FE uses this to surface a countdown. */
|
|
1999
2027
|
signatureDeadline: bigint;
|
|
2000
2028
|
}
|
|
2001
|
-
type PTRedeemErrorCode = "UNAUTHORIZED" | "INVALID_AMOUNT" | "NONCE_READ_FAILED" | "NONCE_IN_FLIGHT" | "LEDGER_NOT_SUPPORTED" | "SIGNING_FAILED" | "REDEMPTION_POLICY_DENIED" | "UNSUPPORTED_POINT_TOKEN";
|
|
2029
|
+
type PTRedeemErrorCode = "UNAUTHORIZED" | "INVALID_AMOUNT" | "NONCE_READ_FAILED" | "NONCE_IN_FLIGHT" | "LEDGER_NOT_SUPPORTED" | "SIGNING_FAILED" | "REDEMPTION_POLICY_DENIED" | "REDEMPTION_POLICY_UNAVAILABLE" | "UNSUPPORTED_POINT_TOKEN";
|
|
2002
2030
|
declare class PTRedeemError extends PafiSdkError {
|
|
2003
2031
|
readonly httpStatus: "unprocessable";
|
|
2004
2032
|
readonly code: PTRedeemErrorCode;
|
|
@@ -2050,9 +2078,18 @@ interface PafiBackendConfig {
|
|
|
2050
2078
|
/**
|
|
2051
2079
|
* chainId — used to derive the sponsor-relayer base URL via
|
|
2052
2080
|
* `getPafiServiceUrls(chainId).sponsorRelayer`. SDK ships with the
|
|
2053
|
-
* URL per chainId; bump SDK version to retarget
|
|
2081
|
+
* URL per chainId; bump SDK version to retarget — OR pass
|
|
2082
|
+
* `baseUrl` to override per-deployment.
|
|
2054
2083
|
*/
|
|
2055
2084
|
chainId: number;
|
|
2085
|
+
/**
|
|
2086
|
+
* Optional override for the sponsor-relayer base URL. Audit
|
|
2087
|
+
* PACI5-17 — production issuer backends should set this from an env
|
|
2088
|
+
* var (e.g. `PAFI_SPONSOR_RELAYER_URL`) so the binary doesn't depend
|
|
2089
|
+
* on the SDK ship-default that may target dev infrastructure.
|
|
2090
|
+
* Undefined / empty → use the ship-default for `chainId`.
|
|
2091
|
+
*/
|
|
2092
|
+
baseUrl?: string;
|
|
2056
2093
|
issuerId: string;
|
|
2057
2094
|
apiKey: string;
|
|
2058
2095
|
fetchImpl?: typeof fetch;
|
|
@@ -2295,6 +2332,23 @@ interface PendingUserOpEntry {
|
|
|
2295
2332
|
verificationGasLimit: string;
|
|
2296
2333
|
preVerificationGas: string;
|
|
2297
2334
|
userOpHash: Hex;
|
|
2335
|
+
/**
|
|
2336
|
+
* Lock id (PendingCredit on redeem, LockedMint on claim) reserved
|
|
2337
|
+
* for the FALLBACK path — distinct from the outer entry's
|
|
2338
|
+
* sponsored lock because the on-chain amount the user burns/mints
|
|
2339
|
+
* differs between variants.
|
|
2340
|
+
*
|
|
2341
|
+
* Audit PACI5-21 — pre-fix `handleMobileSubmit.bindUserOpHash`
|
|
2342
|
+
* always bound the submitted userOpHash to the SPONSORED lockId,
|
|
2343
|
+
* even when the user submitted the fallback variant. The
|
|
2344
|
+
* bundler-receipt fallback in `handleRedeemStatus` then resolved
|
|
2345
|
+
* the sponsored credit (`amount - fee`) against the on-chain
|
|
2346
|
+
* burn of the FULL `amount` — every fee-bearing fallback redeem
|
|
2347
|
+
* permanently under-credited the user by exactly the fee. Surface
|
|
2348
|
+
* the fallback lockId here so submit can route the bind to the
|
|
2349
|
+
* correct ledger row.
|
|
2350
|
+
*/
|
|
2351
|
+
lockId?: string;
|
|
2298
2352
|
};
|
|
2299
2353
|
/**
|
|
2300
2354
|
* EIP-7702 authorization tuple — present only on the `delegate`
|
|
@@ -2515,6 +2569,21 @@ interface PrepareMobileUserOpParams {
|
|
|
2515
2569
|
maxFeePerGas?: bigint;
|
|
2516
2570
|
maxPriorityFeePerGas?: bigint;
|
|
2517
2571
|
};
|
|
2572
|
+
/**
|
|
2573
|
+
* Optional separate lock id for the FALLBACK path — required when
|
|
2574
|
+
* the upstream handler reserves a DIFFERENT ledger row for the
|
|
2575
|
+
* fallback variant (PTRedeemHandler reserves
|
|
2576
|
+
* `amount` for fallback vs `amount - fee` for sponsored).
|
|
2577
|
+
*
|
|
2578
|
+
* Audit PACI5-21 — when omitted, `handleMobileSubmit` binds the
|
|
2579
|
+
* fallback userOpHash to the outer sponsored `lockId`, which made
|
|
2580
|
+
* `handleRedeemStatus`'s bundler-receipt fallback resolve the
|
|
2581
|
+
* SMALLER sponsored credit against the on-chain burn of the FULL
|
|
2582
|
+
* amount. Every fee-bearing fallback redeem permanently
|
|
2583
|
+
* under-credited the user by exactly the fee. Pass the fallback
|
|
2584
|
+
* lockId here so submit can route the bind to the correct row.
|
|
2585
|
+
*/
|
|
2586
|
+
lockIdFallback?: string;
|
|
2518
2587
|
/** Paymaster sponsorship response, or `undefined` if PAFI declined. */
|
|
2519
2588
|
paymasterFields?: {
|
|
2520
2589
|
paymaster: Address;
|
|
@@ -2628,6 +2697,13 @@ interface HandleMobilePrepareParams {
|
|
|
2628
2697
|
partialUserOp: PartialUserOperation;
|
|
2629
2698
|
/** Optional fee-stripped fallback variant. */
|
|
2630
2699
|
partialUserOpFallback?: PartialUserOperation;
|
|
2700
|
+
/**
|
|
2701
|
+
* Optional separate lock id for the fallback path. Required when the
|
|
2702
|
+
* upstream handler (e.g. PTRedeemHandler) reserves a DIFFERENT
|
|
2703
|
+
* ledger row for the fallback variant. See audit PACI5-21 +
|
|
2704
|
+
* `prepareMobileUserOp` for the full rationale.
|
|
2705
|
+
*/
|
|
2706
|
+
lockIdFallback?: string;
|
|
2631
2707
|
/**
|
|
2632
2708
|
* Scenario tag — passed to `requestSponsorship` so the relayer can
|
|
2633
2709
|
* apply per-scenario L1 enforcement (`mint`, `burn`, etc.).
|
|
@@ -2781,8 +2857,6 @@ interface PTClaimHandlerConfig {
|
|
|
2781
2857
|
* Every `handle()` call validates the request's `pointTokenAddress`
|
|
2782
2858
|
* against this set BEFORE any chain read or signer call.
|
|
2783
2859
|
*
|
|
2784
|
-
* Audit PACI5-18 — the read surface (`IssuerApiHandlers.handleUser`,
|
|
2785
|
-
* `handleRedemptionEvaluate`) enforces this allowlist, but the
|
|
2786
2860
|
* write surface (claim/redeem) previously skipped it. The asymmetry
|
|
2787
2861
|
* meant an issuer signer whitelisted as minter on PointTokens
|
|
2788
2862
|
* outside the configured indexer set could be coerced into signing
|
|
@@ -3159,6 +3233,30 @@ interface IssuerServiceConfig {
|
|
|
3159
3233
|
issuerId: string;
|
|
3160
3234
|
apiKey: string;
|
|
3161
3235
|
historyStore: IRedemptionHistoryStore;
|
|
3236
|
+
/**
|
|
3237
|
+
* Optional override for the PAFI issuer-api base URL. Audit
|
|
3238
|
+
* PACI5-17 — production issuer backends should set this from an
|
|
3239
|
+
* env var (e.g. `PAFI_ISSUER_API_URL`) so policy fetch hits the
|
|
3240
|
+
* canonical environment for the deploy. Undefined → SDK
|
|
3241
|
+
* ship-default per chainId.
|
|
3242
|
+
*/
|
|
3243
|
+
baseUrl?: string;
|
|
3244
|
+
/**
|
|
3245
|
+
* Behavior khi settlement-api fetch fail. Default `'fail-closed'`
|
|
3246
|
+
* (audit PACI5-17 — pre-flight redeem limit is the sole gate; do
|
|
3247
|
+
* not silently degrade to a permissive default).
|
|
3248
|
+
*
|
|
3249
|
+
* Opt in to `'permissive-default'` ONLY when paired with an alert
|
|
3250
|
+
* on the `policy_provider_fallback` event surfaced via
|
|
3251
|
+
* `onPolicyWarning`.
|
|
3252
|
+
*/
|
|
3253
|
+
onFetchFailure?: "fail-closed" | "permissive-default";
|
|
3254
|
+
/**
|
|
3255
|
+
* Observability hook for `policy_provider_fallback` events. Wire
|
|
3256
|
+
* to your logger / Sentry / Datadog so the on-call dashboard sees
|
|
3257
|
+
* settlement-api degradation.
|
|
3258
|
+
*/
|
|
3259
|
+
onPolicyWarning?: (msg: string, ctx: Record<string, unknown>) => void;
|
|
3162
3260
|
/** Override fetch (testing). */
|
|
3163
3261
|
fetchImpl?: typeof fetch;
|
|
3164
3262
|
/** Per-fetch timeout in ms. Default 1000. */
|
|
@@ -3324,6 +3422,12 @@ interface MobilePrepareDto {
|
|
|
3324
3422
|
interface RedeemPrepareDto extends MobilePrepareDto {
|
|
3325
3423
|
netCreditAmount: string;
|
|
3326
3424
|
netCreditAmountFallback?: string;
|
|
3425
|
+
/**
|
|
3426
|
+
* Lock id reserved for the FALLBACK redeem path (= full `amount`).
|
|
3427
|
+
* Mobile FE polls `/redeem/status/:lockIdFallback` when it submits
|
|
3428
|
+
* the fallback variant (`variant: 'fallback'` on `/redeem/submit`).
|
|
3429
|
+
*/
|
|
3430
|
+
lockIdFallback?: string;
|
|
3327
3431
|
}
|
|
3328
3432
|
interface MobileSubmitDto {
|
|
3329
3433
|
userOpHash: Hex;
|
package/dist/index.d.ts
CHANGED
|
@@ -1551,9 +1551,18 @@ interface SettlementClientConfig {
|
|
|
1551
1551
|
/**
|
|
1552
1552
|
* chainId — used to derive the issuer-api base URL via
|
|
1553
1553
|
* `getPafiServiceUrls(chainId).issuerApi`. SDK ships with the URL
|
|
1554
|
-
* per chainId; bump SDK version to retarget
|
|
1554
|
+
* per chainId; bump SDK version to retarget — OR pass `baseUrl`
|
|
1555
|
+
* to override per-deployment.
|
|
1555
1556
|
*/
|
|
1556
1557
|
chainId: number;
|
|
1558
|
+
/**
|
|
1559
|
+
* Optional override for the issuer-api base URL. Audit PACI5-17 —
|
|
1560
|
+
* production issuer backends should set this from an env var
|
|
1561
|
+
* (e.g. `PAFI_ISSUER_API_URL`) so the deployed binary doesn't
|
|
1562
|
+
* depend on the SDK ship-default that may target dev infrastructure.
|
|
1563
|
+
* Undefined / empty → use the ship-default for `chainId`.
|
|
1564
|
+
*/
|
|
1565
|
+
baseUrl?: string;
|
|
1557
1566
|
/** PAFI-assigned issuer id used in `X-Issuer-Id` header. */
|
|
1558
1567
|
issuerId: string;
|
|
1559
1568
|
/** Raw API key sent as `Authorization: Bearer <apiKey>`. */
|
|
@@ -1566,6 +1575,34 @@ interface SettlementClientConfig {
|
|
|
1566
1575
|
interface PolicyProviderConfig extends SettlementClientConfig {
|
|
1567
1576
|
/** Cache TTL in milliseconds. Default 5 * 60 * 1000 (5min). */
|
|
1568
1577
|
cacheTtlMs?: number;
|
|
1578
|
+
/**
|
|
1579
|
+
* Behavior khi settlement-api fetch fail (network blip, 5xx, timeout).
|
|
1580
|
+
*
|
|
1581
|
+
* Audit PACI5-17 — the pre-flight redeem limit is the SOLE gate
|
|
1582
|
+
* before the issuer signer mints a BurnRequest. Silently degrading
|
|
1583
|
+
* to a permissive default during settlement outages let users
|
|
1584
|
+
* exceed their configured limit until the outage cleared.
|
|
1585
|
+
*
|
|
1586
|
+
* - `'fail-closed'` (DEFAULT, recommended): throw
|
|
1587
|
+
* `PolicyProviderUnavailableError`. Caller maps to HTTP 503 so
|
|
1588
|
+
* the FE can retry; redemptions are blocked until policy fetch
|
|
1589
|
+
* succeeds.
|
|
1590
|
+
*
|
|
1591
|
+
* - `'permissive-default'`: return `defaultPolicyFor(issuerId)` and
|
|
1592
|
+
* fire `onWarning`. Pre-fix behavior. Operators who genuinely
|
|
1593
|
+
* prefer availability over enforcement (e.g. test issuers, low-
|
|
1594
|
+
* risk PT) MUST opt in explicitly AND wire `onWarning` to a
|
|
1595
|
+
* pager / Slack / Sentry alert. Silent permissive fallback is no
|
|
1596
|
+
* longer the default.
|
|
1597
|
+
*/
|
|
1598
|
+
onFetchFailure?: "fail-closed" | "permissive-default";
|
|
1599
|
+
/**
|
|
1600
|
+
* Observability hook for non-fatal events: permissive-default
|
|
1601
|
+
* fallbacks (when explicitly opted in). Wire to your logger /
|
|
1602
|
+
* Sentry / Datadog so the `policy_provider_fallback` event reaches
|
|
1603
|
+
* the on-call dashboard.
|
|
1604
|
+
*/
|
|
1605
|
+
onWarning?: (msg: string, ctx: Record<string, unknown>) => void;
|
|
1569
1606
|
/**
|
|
1570
1607
|
* Optional clock for testability. Returns unix milliseconds.
|
|
1571
1608
|
* Defaults to () => Date.now().
|
|
@@ -1596,6 +1633,8 @@ declare class PolicyProvider {
|
|
|
1596
1633
|
private readonly client;
|
|
1597
1634
|
private readonly issuerId;
|
|
1598
1635
|
private readonly cacheTtlMs;
|
|
1636
|
+
private readonly onFetchFailure;
|
|
1637
|
+
private readonly onWarning?;
|
|
1599
1638
|
private readonly now;
|
|
1600
1639
|
private cache;
|
|
1601
1640
|
private inflight;
|
|
@@ -1889,17 +1928,6 @@ interface PTRedeemHandlerConfig {
|
|
|
1889
1928
|
* Every `handle()` call validates the request's `pointTokenAddress`
|
|
1890
1929
|
* against this set BEFORE any chain read, signer call, or pending
|
|
1891
1930
|
* credit reservation.
|
|
1892
|
-
*
|
|
1893
|
-
* Audit PACI5-18 (redeem twin of the claim-side finding) — the read
|
|
1894
|
-
* surface (`IssuerApiHandlers.handleUser` / `handleRedemptionEvaluate`)
|
|
1895
|
-
* enforces this allowlist, but the write surface previously skipped
|
|
1896
|
-
* it. The asymmetry meant an issuer burner signer whitelisted on
|
|
1897
|
-
* multiple PointTokens could be coerced into signing a valid
|
|
1898
|
-
* `BurnRequest` for an off-allowlist token where no BurnIndexer is
|
|
1899
|
-
* running — the on-chain burn would succeed but the off-chain
|
|
1900
|
-
* pending credit would stay PENDING forever (or worse, resolve via
|
|
1901
|
-
* the bundler-receipt fallback against an unbacked credit balance).
|
|
1902
|
-
*
|
|
1903
1931
|
* Pass the SAME set used to construct `IssuerApiHandlers` and
|
|
1904
1932
|
* `PTClaimHandler` so the read, claim, and redeem paths agree on
|
|
1905
1933
|
* what this issuer is willing to sign for.
|
|
@@ -1998,7 +2026,7 @@ interface PTRedeemResponse {
|
|
|
1998
2026
|
/** The BurnRequest deadline (unix seconds) — FE uses this to surface a countdown. */
|
|
1999
2027
|
signatureDeadline: bigint;
|
|
2000
2028
|
}
|
|
2001
|
-
type PTRedeemErrorCode = "UNAUTHORIZED" | "INVALID_AMOUNT" | "NONCE_READ_FAILED" | "NONCE_IN_FLIGHT" | "LEDGER_NOT_SUPPORTED" | "SIGNING_FAILED" | "REDEMPTION_POLICY_DENIED" | "UNSUPPORTED_POINT_TOKEN";
|
|
2029
|
+
type PTRedeemErrorCode = "UNAUTHORIZED" | "INVALID_AMOUNT" | "NONCE_READ_FAILED" | "NONCE_IN_FLIGHT" | "LEDGER_NOT_SUPPORTED" | "SIGNING_FAILED" | "REDEMPTION_POLICY_DENIED" | "REDEMPTION_POLICY_UNAVAILABLE" | "UNSUPPORTED_POINT_TOKEN";
|
|
2002
2030
|
declare class PTRedeemError extends PafiSdkError {
|
|
2003
2031
|
readonly httpStatus: "unprocessable";
|
|
2004
2032
|
readonly code: PTRedeemErrorCode;
|
|
@@ -2050,9 +2078,18 @@ interface PafiBackendConfig {
|
|
|
2050
2078
|
/**
|
|
2051
2079
|
* chainId — used to derive the sponsor-relayer base URL via
|
|
2052
2080
|
* `getPafiServiceUrls(chainId).sponsorRelayer`. SDK ships with the
|
|
2053
|
-
* URL per chainId; bump SDK version to retarget
|
|
2081
|
+
* URL per chainId; bump SDK version to retarget — OR pass
|
|
2082
|
+
* `baseUrl` to override per-deployment.
|
|
2054
2083
|
*/
|
|
2055
2084
|
chainId: number;
|
|
2085
|
+
/**
|
|
2086
|
+
* Optional override for the sponsor-relayer base URL. Audit
|
|
2087
|
+
* PACI5-17 — production issuer backends should set this from an env
|
|
2088
|
+
* var (e.g. `PAFI_SPONSOR_RELAYER_URL`) so the binary doesn't depend
|
|
2089
|
+
* on the SDK ship-default that may target dev infrastructure.
|
|
2090
|
+
* Undefined / empty → use the ship-default for `chainId`.
|
|
2091
|
+
*/
|
|
2092
|
+
baseUrl?: string;
|
|
2056
2093
|
issuerId: string;
|
|
2057
2094
|
apiKey: string;
|
|
2058
2095
|
fetchImpl?: typeof fetch;
|
|
@@ -2295,6 +2332,23 @@ interface PendingUserOpEntry {
|
|
|
2295
2332
|
verificationGasLimit: string;
|
|
2296
2333
|
preVerificationGas: string;
|
|
2297
2334
|
userOpHash: Hex;
|
|
2335
|
+
/**
|
|
2336
|
+
* Lock id (PendingCredit on redeem, LockedMint on claim) reserved
|
|
2337
|
+
* for the FALLBACK path — distinct from the outer entry's
|
|
2338
|
+
* sponsored lock because the on-chain amount the user burns/mints
|
|
2339
|
+
* differs between variants.
|
|
2340
|
+
*
|
|
2341
|
+
* Audit PACI5-21 — pre-fix `handleMobileSubmit.bindUserOpHash`
|
|
2342
|
+
* always bound the submitted userOpHash to the SPONSORED lockId,
|
|
2343
|
+
* even when the user submitted the fallback variant. The
|
|
2344
|
+
* bundler-receipt fallback in `handleRedeemStatus` then resolved
|
|
2345
|
+
* the sponsored credit (`amount - fee`) against the on-chain
|
|
2346
|
+
* burn of the FULL `amount` — every fee-bearing fallback redeem
|
|
2347
|
+
* permanently under-credited the user by exactly the fee. Surface
|
|
2348
|
+
* the fallback lockId here so submit can route the bind to the
|
|
2349
|
+
* correct ledger row.
|
|
2350
|
+
*/
|
|
2351
|
+
lockId?: string;
|
|
2298
2352
|
};
|
|
2299
2353
|
/**
|
|
2300
2354
|
* EIP-7702 authorization tuple — present only on the `delegate`
|
|
@@ -2515,6 +2569,21 @@ interface PrepareMobileUserOpParams {
|
|
|
2515
2569
|
maxFeePerGas?: bigint;
|
|
2516
2570
|
maxPriorityFeePerGas?: bigint;
|
|
2517
2571
|
};
|
|
2572
|
+
/**
|
|
2573
|
+
* Optional separate lock id for the FALLBACK path — required when
|
|
2574
|
+
* the upstream handler reserves a DIFFERENT ledger row for the
|
|
2575
|
+
* fallback variant (PTRedeemHandler reserves
|
|
2576
|
+
* `amount` for fallback vs `amount - fee` for sponsored).
|
|
2577
|
+
*
|
|
2578
|
+
* Audit PACI5-21 — when omitted, `handleMobileSubmit` binds the
|
|
2579
|
+
* fallback userOpHash to the outer sponsored `lockId`, which made
|
|
2580
|
+
* `handleRedeemStatus`'s bundler-receipt fallback resolve the
|
|
2581
|
+
* SMALLER sponsored credit against the on-chain burn of the FULL
|
|
2582
|
+
* amount. Every fee-bearing fallback redeem permanently
|
|
2583
|
+
* under-credited the user by exactly the fee. Pass the fallback
|
|
2584
|
+
* lockId here so submit can route the bind to the correct row.
|
|
2585
|
+
*/
|
|
2586
|
+
lockIdFallback?: string;
|
|
2518
2587
|
/** Paymaster sponsorship response, or `undefined` if PAFI declined. */
|
|
2519
2588
|
paymasterFields?: {
|
|
2520
2589
|
paymaster: Address;
|
|
@@ -2628,6 +2697,13 @@ interface HandleMobilePrepareParams {
|
|
|
2628
2697
|
partialUserOp: PartialUserOperation;
|
|
2629
2698
|
/** Optional fee-stripped fallback variant. */
|
|
2630
2699
|
partialUserOpFallback?: PartialUserOperation;
|
|
2700
|
+
/**
|
|
2701
|
+
* Optional separate lock id for the fallback path. Required when the
|
|
2702
|
+
* upstream handler (e.g. PTRedeemHandler) reserves a DIFFERENT
|
|
2703
|
+
* ledger row for the fallback variant. See audit PACI5-21 +
|
|
2704
|
+
* `prepareMobileUserOp` for the full rationale.
|
|
2705
|
+
*/
|
|
2706
|
+
lockIdFallback?: string;
|
|
2631
2707
|
/**
|
|
2632
2708
|
* Scenario tag — passed to `requestSponsorship` so the relayer can
|
|
2633
2709
|
* apply per-scenario L1 enforcement (`mint`, `burn`, etc.).
|
|
@@ -2781,8 +2857,6 @@ interface PTClaimHandlerConfig {
|
|
|
2781
2857
|
* Every `handle()` call validates the request's `pointTokenAddress`
|
|
2782
2858
|
* against this set BEFORE any chain read or signer call.
|
|
2783
2859
|
*
|
|
2784
|
-
* Audit PACI5-18 — the read surface (`IssuerApiHandlers.handleUser`,
|
|
2785
|
-
* `handleRedemptionEvaluate`) enforces this allowlist, but the
|
|
2786
2860
|
* write surface (claim/redeem) previously skipped it. The asymmetry
|
|
2787
2861
|
* meant an issuer signer whitelisted as minter on PointTokens
|
|
2788
2862
|
* outside the configured indexer set could be coerced into signing
|
|
@@ -3159,6 +3233,30 @@ interface IssuerServiceConfig {
|
|
|
3159
3233
|
issuerId: string;
|
|
3160
3234
|
apiKey: string;
|
|
3161
3235
|
historyStore: IRedemptionHistoryStore;
|
|
3236
|
+
/**
|
|
3237
|
+
* Optional override for the PAFI issuer-api base URL. Audit
|
|
3238
|
+
* PACI5-17 — production issuer backends should set this from an
|
|
3239
|
+
* env var (e.g. `PAFI_ISSUER_API_URL`) so policy fetch hits the
|
|
3240
|
+
* canonical environment for the deploy. Undefined → SDK
|
|
3241
|
+
* ship-default per chainId.
|
|
3242
|
+
*/
|
|
3243
|
+
baseUrl?: string;
|
|
3244
|
+
/**
|
|
3245
|
+
* Behavior khi settlement-api fetch fail. Default `'fail-closed'`
|
|
3246
|
+
* (audit PACI5-17 — pre-flight redeem limit is the sole gate; do
|
|
3247
|
+
* not silently degrade to a permissive default).
|
|
3248
|
+
*
|
|
3249
|
+
* Opt in to `'permissive-default'` ONLY when paired with an alert
|
|
3250
|
+
* on the `policy_provider_fallback` event surfaced via
|
|
3251
|
+
* `onPolicyWarning`.
|
|
3252
|
+
*/
|
|
3253
|
+
onFetchFailure?: "fail-closed" | "permissive-default";
|
|
3254
|
+
/**
|
|
3255
|
+
* Observability hook for `policy_provider_fallback` events. Wire
|
|
3256
|
+
* to your logger / Sentry / Datadog so the on-call dashboard sees
|
|
3257
|
+
* settlement-api degradation.
|
|
3258
|
+
*/
|
|
3259
|
+
onPolicyWarning?: (msg: string, ctx: Record<string, unknown>) => void;
|
|
3162
3260
|
/** Override fetch (testing). */
|
|
3163
3261
|
fetchImpl?: typeof fetch;
|
|
3164
3262
|
/** Per-fetch timeout in ms. Default 1000. */
|
|
@@ -3324,6 +3422,12 @@ interface MobilePrepareDto {
|
|
|
3324
3422
|
interface RedeemPrepareDto extends MobilePrepareDto {
|
|
3325
3423
|
netCreditAmount: string;
|
|
3326
3424
|
netCreditAmountFallback?: string;
|
|
3425
|
+
/**
|
|
3426
|
+
* Lock id reserved for the FALLBACK redeem path (= full `amount`).
|
|
3427
|
+
* Mobile FE polls `/redeem/status/:lockIdFallback` when it submits
|
|
3428
|
+
* the fallback variant (`variant: 'fallback'` on `/redeem/submit`).
|
|
3429
|
+
*/
|
|
3430
|
+
lockIdFallback?: string;
|
|
3327
3431
|
}
|
|
3328
3432
|
interface MobileSubmitDto {
|
|
3329
3433
|
userOpHash: Hex;
|
package/dist/index.js
CHANGED
|
@@ -2190,11 +2190,23 @@ var PTRedeemHandler = class {
|
|
|
2190
2190
|
);
|
|
2191
2191
|
}
|
|
2192
2192
|
if (this.redemptionService) {
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2193
|
+
let decision;
|
|
2194
|
+
try {
|
|
2195
|
+
decision = await this.redemptionService.evaluate(
|
|
2196
|
+
request.userAddress,
|
|
2197
|
+
request.amount,
|
|
2198
|
+
pointTokenAddress
|
|
2199
|
+
);
|
|
2200
|
+
} catch (err) {
|
|
2201
|
+
const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
|
|
2202
|
+
if (code === "POLICY_PROVIDER_UNAVAILABLE") {
|
|
2203
|
+
throw new PTRedeemError(
|
|
2204
|
+
"REDEMPTION_POLICY_UNAVAILABLE",
|
|
2205
|
+
"Redemption policy temporarily unavailable \u2014 please try again shortly."
|
|
2206
|
+
);
|
|
2207
|
+
}
|
|
2208
|
+
throw err;
|
|
2209
|
+
}
|
|
2198
2210
|
if (!decision.allowed) {
|
|
2199
2211
|
const denial = decision.denial;
|
|
2200
2212
|
throw new PTRedeemError(
|
|
@@ -2685,7 +2697,10 @@ async function prepareMobileUserOp(params) {
|
|
|
2685
2697
|
callGasLimit: fallback.userOp.callGasLimit.toString(),
|
|
2686
2698
|
verificationGasLimit: fallback.userOp.verificationGasLimit.toString(),
|
|
2687
2699
|
preVerificationGas: fallback.userOp.preVerificationGas.toString(),
|
|
2688
|
-
userOpHash: fallback.userOpHash
|
|
2700
|
+
userOpHash: fallback.userOpHash,
|
|
2701
|
+
// Audit PACI5-21 — carry the fallback-specific lockId so submit
|
|
2702
|
+
// can bind the fallback userOpHash to the correct ledger row.
|
|
2703
|
+
lockId: params.lockIdFallback
|
|
2689
2704
|
};
|
|
2690
2705
|
}
|
|
2691
2706
|
const entry = {
|
|
@@ -2878,6 +2893,7 @@ async function handleMobilePrepare(params) {
|
|
|
2878
2893
|
lockId: params.lockId,
|
|
2879
2894
|
partialUserOp: sponsoredOp,
|
|
2880
2895
|
partialUserOpFallback: params.partialUserOpFallback,
|
|
2896
|
+
lockIdFallback: params.lockIdFallback,
|
|
2881
2897
|
paymasterFields,
|
|
2882
2898
|
chainId: params.chainId,
|
|
2883
2899
|
store: params.store,
|
|
@@ -2906,7 +2922,8 @@ async function handleMobileSubmit(params) {
|
|
|
2906
2922
|
entryPoint: params.entryPoint ?? ENTRY_POINT_V08,
|
|
2907
2923
|
eip7702Auth: entry.eip7702Auth
|
|
2908
2924
|
});
|
|
2909
|
-
|
|
2925
|
+
const targetLockId = variant === "fallback" && entry.fallback?.lockId ? entry.fallback.lockId : params.lockId;
|
|
2926
|
+
await params.bindUserOpHash(targetLockId, result.userOpHash);
|
|
2910
2927
|
await params.store.delete(params.lockId);
|
|
2911
2928
|
return { userOpHash: result.userOpHash };
|
|
2912
2929
|
}
|
|
@@ -3676,10 +3693,12 @@ var IssuerApiAdapter = class {
|
|
|
3676
3693
|
"burn",
|
|
3677
3694
|
pointTokenAddress,
|
|
3678
3695
|
redeemResponse.expiresInSeconds,
|
|
3679
|
-
input.eip7702Auth
|
|
3696
|
+
input.eip7702Auth,
|
|
3697
|
+
redeemResponse.fallback?.lockId
|
|
3680
3698
|
);
|
|
3681
3699
|
return {
|
|
3682
3700
|
lockId: redeemResponse.lockId,
|
|
3701
|
+
lockIdFallback: redeemResponse.fallback?.lockId,
|
|
3683
3702
|
userOpHash: prepared.sponsored.userOpHash,
|
|
3684
3703
|
typedData: prepared.sponsored.typedData,
|
|
3685
3704
|
userOpHashFallback: prepared.fallback?.userOpHash,
|
|
@@ -3810,13 +3829,14 @@ var IssuerApiAdapter = class {
|
|
|
3810
3829
|
issuerSignerWallet: this.cfg.issuerSignerWallet
|
|
3811
3830
|
});
|
|
3812
3831
|
}
|
|
3813
|
-
async runMobilePrepare(authenticatedAddress, chainId, lockId, partialUserOp, partialUserOpFallback, scenario, pointTokenAddress, ttlSeconds, eip7702Auth) {
|
|
3832
|
+
async runMobilePrepare(authenticatedAddress, chainId, lockId, partialUserOp, partialUserOpFallback, scenario, pointTokenAddress, ttlSeconds, eip7702Auth, lockIdFallback) {
|
|
3814
3833
|
return await handleMobilePrepare({
|
|
3815
3834
|
userAddress: authenticatedAddress,
|
|
3816
3835
|
chainId,
|
|
3817
3836
|
lockId,
|
|
3818
3837
|
partialUserOp,
|
|
3819
3838
|
partialUserOpFallback,
|
|
3839
|
+
lockIdFallback,
|
|
3820
3840
|
scenario,
|
|
3821
3841
|
pointTokenAddress,
|
|
3822
3842
|
ttlSeconds,
|
|
@@ -4252,7 +4272,9 @@ var PafiBackendClient = class {
|
|
|
4252
4272
|
if (!config.issuerId) throw new Error("PafiBackendClient: issuerId is required");
|
|
4253
4273
|
if (!config.apiKey) throw new Error("PafiBackendClient: apiKey is required");
|
|
4254
4274
|
this.config = config;
|
|
4255
|
-
this.baseUrl = getPafiServiceUrls(config.chainId
|
|
4275
|
+
this.baseUrl = getPafiServiceUrls(config.chainId, {
|
|
4276
|
+
sponsorRelayer: config.baseUrl
|
|
4277
|
+
}).sponsorRelayer;
|
|
4256
4278
|
}
|
|
4257
4279
|
async requestSponsorship(request) {
|
|
4258
4280
|
const maxAttempts = this.config.retry?.maxAttempts ?? 1;
|
|
@@ -4517,6 +4539,9 @@ function nextBlackoutEndAfter(windows, nowUnixSec) {
|
|
|
4517
4539
|
}
|
|
4518
4540
|
var REDEMPTION_HISTORY_WINDOW_SEC = SECONDS_PER_DAY;
|
|
4519
4541
|
|
|
4542
|
+
// src/redemption/policyProvider.ts
|
|
4543
|
+
import { PafiSdkError as PafiSdkError2 } from "@pafi-dev/core";
|
|
4544
|
+
|
|
4520
4545
|
// src/redemption/settlementClient.ts
|
|
4521
4546
|
import {
|
|
4522
4547
|
getPafiServiceUrls as getPafiServiceUrls2
|
|
@@ -4529,7 +4554,12 @@ var SettlementClient = class {
|
|
|
4529
4554
|
if (!config.issuerId) throw new Error("SettlementClient: issuerId is required");
|
|
4530
4555
|
if (!config.apiKey) throw new Error("SettlementClient: apiKey is required");
|
|
4531
4556
|
this.config = {
|
|
4532
|
-
baseUrl
|
|
4557
|
+
// Audit PACI5-17 — honor optional baseUrl override wired from
|
|
4558
|
+
// an env var (e.g. PAFI_ISSUER_API_URL). Empty / undefined →
|
|
4559
|
+
// ship-default for chainId.
|
|
4560
|
+
baseUrl: getPafiServiceUrls2(config.chainId, {
|
|
4561
|
+
issuerApi: config.baseUrl
|
|
4562
|
+
}).issuerApi.replace(/\/+$/, ""),
|
|
4533
4563
|
issuerId: config.issuerId,
|
|
4534
4564
|
apiKey: config.apiKey,
|
|
4535
4565
|
fetchTimeoutMs: config.fetchTimeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
@@ -4633,10 +4663,23 @@ function defaultPolicyFor(issuerId) {
|
|
|
4633
4663
|
|
|
4634
4664
|
// src/redemption/policyProvider.ts
|
|
4635
4665
|
var DEFAULT_CACHE_TTL_MS3 = 5 * 60 * 1e3;
|
|
4666
|
+
var PolicyProviderUnavailableError = class extends PafiSdkError2 {
|
|
4667
|
+
code = "POLICY_PROVIDER_UNAVAILABLE";
|
|
4668
|
+
httpStatus = "service_unavailable";
|
|
4669
|
+
details;
|
|
4670
|
+
constructor(issuerId, reason) {
|
|
4671
|
+
super(
|
|
4672
|
+
`Redemption policy provider unavailable for issuer ${issuerId}: ${reason}. Pre-flight redeem limit cannot be enforced \u2014 refusing to sign BurnRequest. Mobile FE: surface "try again shortly" and retry with backoff.`
|
|
4673
|
+
);
|
|
4674
|
+
this.details = { issuerId, reason };
|
|
4675
|
+
}
|
|
4676
|
+
};
|
|
4636
4677
|
var PolicyProvider = class {
|
|
4637
4678
|
client;
|
|
4638
4679
|
issuerId;
|
|
4639
4680
|
cacheTtlMs;
|
|
4681
|
+
onFetchFailure;
|
|
4682
|
+
onWarning;
|
|
4640
4683
|
now;
|
|
4641
4684
|
cache = null;
|
|
4642
4685
|
inflight = null;
|
|
@@ -4644,6 +4687,8 @@ var PolicyProvider = class {
|
|
|
4644
4687
|
this.client = new SettlementClient(config);
|
|
4645
4688
|
this.issuerId = config.issuerId;
|
|
4646
4689
|
this.cacheTtlMs = config.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS3;
|
|
4690
|
+
this.onFetchFailure = config.onFetchFailure ?? "fail-closed";
|
|
4691
|
+
this.onWarning = config.onWarning;
|
|
4647
4692
|
this.now = config.now ?? (() => Date.now());
|
|
4648
4693
|
}
|
|
4649
4694
|
async getPolicy() {
|
|
@@ -4676,7 +4721,19 @@ var PolicyProvider = class {
|
|
|
4676
4721
|
};
|
|
4677
4722
|
return { policy: result.policy, source: "settlement" };
|
|
4678
4723
|
}
|
|
4679
|
-
|
|
4724
|
+
const reason = "reason" in result && typeof result.reason === "string" ? result.reason : "unknown";
|
|
4725
|
+
if (this.onFetchFailure === "permissive-default") {
|
|
4726
|
+
this.onWarning?.(
|
|
4727
|
+
"PolicyProvider: settlement-api unreachable, falling back to permissive default. Pre-flight redeem limit is DEGRADED until settlement-api recovers.",
|
|
4728
|
+
{
|
|
4729
|
+
event: "policy_provider_fallback",
|
|
4730
|
+
issuerId: this.issuerId,
|
|
4731
|
+
reason
|
|
4732
|
+
}
|
|
4733
|
+
);
|
|
4734
|
+
return { policy: defaultPolicyFor(this.issuerId), source: "default" };
|
|
4735
|
+
}
|
|
4736
|
+
throw new PolicyProviderUnavailableError(this.issuerId, reason);
|
|
4680
4737
|
}
|
|
4681
4738
|
};
|
|
4682
4739
|
|
|
@@ -4823,6 +4880,15 @@ async function createIssuerService(config) {
|
|
|
4823
4880
|
issuerId: config.redemption.issuerId,
|
|
4824
4881
|
apiKey: config.redemption.apiKey
|
|
4825
4882
|
};
|
|
4883
|
+
if (config.redemption.baseUrl) {
|
|
4884
|
+
policyConfig.baseUrl = config.redemption.baseUrl;
|
|
4885
|
+
}
|
|
4886
|
+
if (config.redemption.onFetchFailure) {
|
|
4887
|
+
policyConfig.onFetchFailure = config.redemption.onFetchFailure;
|
|
4888
|
+
}
|
|
4889
|
+
if (config.redemption.onPolicyWarning) {
|
|
4890
|
+
policyConfig.onWarning = config.redemption.onPolicyWarning;
|
|
4891
|
+
}
|
|
4826
4892
|
if (config.redemption.fetchImpl) policyConfig.fetchImpl = config.redemption.fetchImpl;
|
|
4827
4893
|
if (config.redemption.fetchTimeoutMs !== void 0) {
|
|
4828
4894
|
policyConfig.fetchTimeoutMs = config.redemption.fetchTimeoutMs;
|
|
@@ -5087,7 +5153,7 @@ var MemoryRedemptionHistoryStore = class {
|
|
|
5087
5153
|
};
|
|
5088
5154
|
|
|
5089
5155
|
// src/index.ts
|
|
5090
|
-
var PAFI_ISSUER_SDK_VERSION = true ? "0.
|
|
5156
|
+
var PAFI_ISSUER_SDK_VERSION = true ? "0.32.0" : "dev";
|
|
5091
5157
|
export {
|
|
5092
5158
|
AdapterMisconfiguredError,
|
|
5093
5159
|
AuthError,
|