@pafi-dev/issuer 0.3.0-beta.0 → 0.3.0-beta.10
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 +134 -752
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +147 -508
- package/dist/index.d.ts +147 -508
- package/dist/index.js +128 -746
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -30,13 +30,10 @@ __export(index_exports, {
|
|
|
30
30
|
IssuerApiHandlers: () => IssuerApiHandlers,
|
|
31
31
|
MemoryPointLedger: () => MemoryPointLedger,
|
|
32
32
|
MemorySessionStore: () => MemorySessionStore,
|
|
33
|
-
MintingGateway: () => MintingGateway,
|
|
34
|
-
MintingGatewayError: () => MintingGatewayError,
|
|
35
33
|
NonceManager: () => NonceManager,
|
|
36
34
|
PAFI_ISSUER_SDK_VERSION: () => PAFI_ISSUER_SDK_VERSION,
|
|
37
35
|
PTRedeemError: () => PTRedeemError,
|
|
38
36
|
PTRedeemHandler: () => PTRedeemHandler,
|
|
39
|
-
PafiBackendClient: () => PafiBackendClient,
|
|
40
37
|
PafiBackendError: () => PafiBackendError,
|
|
41
38
|
PointIndexer: () => PointIndexer,
|
|
42
39
|
PrivateKeySigner: () => PrivateKeySigner,
|
|
@@ -47,8 +44,7 @@ __export(index_exports, {
|
|
|
47
44
|
authenticateRequest: () => authenticateRequest,
|
|
48
45
|
createIssuerService: () => createIssuerService,
|
|
49
46
|
createSubgraphNativeUsdtQuoter: () => createSubgraphNativeUsdtQuoter,
|
|
50
|
-
createSubgraphPoolsProvider: () => createSubgraphPoolsProvider
|
|
51
|
-
encodeExtData: () => import_core4.encodeExtData
|
|
47
|
+
createSubgraphPoolsProvider: () => createSubgraphPoolsProvider
|
|
52
48
|
});
|
|
53
49
|
module.exports = __toCommonJS(index_exports);
|
|
54
50
|
|
|
@@ -636,199 +632,94 @@ var RelayError = class extends Error {
|
|
|
636
632
|
// src/relay/relayService.ts
|
|
637
633
|
var import_viem5 = require("viem");
|
|
638
634
|
var import_core3 = require("@pafi-dev/core");
|
|
639
|
-
var DEFAULT_CONFIRMATION_TIMEOUT_MS = 6e4;
|
|
640
635
|
var RelayService = class {
|
|
641
|
-
relayAddress;
|
|
642
|
-
operatorWallet;
|
|
643
|
-
provider;
|
|
644
|
-
confirmationTimeoutMs;
|
|
645
|
-
simulateBeforeSubmit;
|
|
646
|
-
constructor(config) {
|
|
647
|
-
if (!config.relayAddress) {
|
|
648
|
-
throw new Error("RelayService: relayAddress is required");
|
|
649
|
-
}
|
|
650
|
-
if (!config.operatorWallet) {
|
|
651
|
-
throw new Error("RelayService: operatorWallet is required");
|
|
652
|
-
}
|
|
653
|
-
this.relayAddress = config.relayAddress;
|
|
654
|
-
this.operatorWallet = config.operatorWallet;
|
|
655
|
-
if (config.provider) this.provider = config.provider;
|
|
656
|
-
this.confirmationTimeoutMs = config.confirmationTimeoutMs ?? DEFAULT_CONFIRMATION_TIMEOUT_MS;
|
|
657
|
-
this.simulateBeforeSubmit = config.simulateBeforeSubmit ?? config.provider !== void 0;
|
|
658
|
-
}
|
|
659
|
-
/** Address the operator wallet is broadcasting from (for logging). */
|
|
660
|
-
operatorAddress() {
|
|
661
|
-
return this.operatorWallet.account?.address;
|
|
662
|
-
}
|
|
663
636
|
/**
|
|
664
|
-
* Build
|
|
665
|
-
*
|
|
666
|
-
* for audit before broadcasting.
|
|
637
|
+
* Build an unsigned UserOp for Scenario 1 (Mint) — sig-gated
|
|
638
|
+
* `PointToken.mint(to, amount, deadline, minterSig)`.
|
|
667
639
|
*/
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
return (0, import_core3.encodeMintAndSwap)(params.mint, params.swap);
|
|
671
|
-
} catch (err) {
|
|
640
|
+
async prepareMint(params) {
|
|
641
|
+
if (!params.batchExecutorAddress) {
|
|
672
642
|
throw new RelayError(
|
|
673
643
|
"ENCODE_FAILED",
|
|
674
|
-
|
|
675
|
-
err
|
|
644
|
+
"prepareMint: batchExecutorAddress required"
|
|
676
645
|
);
|
|
677
646
|
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
* Submit a `mintAndSwap` transaction. Flow:
|
|
681
|
-
*
|
|
682
|
-
* 1. (optional) pre-flight simulate via provider
|
|
683
|
-
* 2. writeContract through the operator wallet
|
|
684
|
-
* 3. (optional) wait for the receipt and surface gasUsed / status
|
|
685
|
-
*
|
|
686
|
-
* Throws a typed `RelayError` on any failure so the MintingGateway can
|
|
687
|
-
* decide whether to release the ledger lock (`SUBMIT_FAILED` and
|
|
688
|
-
* `SIMULATION_FAILED` are safe to release; `TX_REVERTED` and `TIMEOUT`
|
|
689
|
-
* need manual review because the tx may still land).
|
|
690
|
-
*
|
|
691
|
-
* @deprecated Since 0.3.0 — will be replaced by `prepareMint()` +
|
|
692
|
-
* `prepareBurn()` in the v1.4 sponsored-UserOp flow. The SC team
|
|
693
|
-
* still needs to finalize Relayer v2 ABI before the replacements
|
|
694
|
-
* can ship (blocker B1). Kept for v0.2.x consumers. Removed in 2.0.
|
|
695
|
-
*/
|
|
696
|
-
async submitMintAndSwap(params) {
|
|
697
|
-
if (this.simulateBeforeSubmit && this.provider) {
|
|
698
|
-
const operatorAddr = this.operatorWallet.account?.address;
|
|
699
|
-
if (operatorAddr) {
|
|
700
|
-
try {
|
|
701
|
-
await (0, import_core3.simulateMintAndSwap)(
|
|
702
|
-
this.provider,
|
|
703
|
-
this.relayAddress,
|
|
704
|
-
params.mint,
|
|
705
|
-
params.swap,
|
|
706
|
-
operatorAddr
|
|
707
|
-
);
|
|
708
|
-
} catch (err) {
|
|
709
|
-
const reason = err instanceof import_core3.SimulationError ? err.reason : errorMessage(err);
|
|
710
|
-
throw new RelayError(
|
|
711
|
-
"SIMULATION_FAILED",
|
|
712
|
-
`mintAndSwap would revert: ${reason}`,
|
|
713
|
-
err
|
|
714
|
-
);
|
|
715
|
-
}
|
|
716
|
-
}
|
|
647
|
+
if (!params.userAddress) {
|
|
648
|
+
throw new RelayError("ENCODE_FAILED", "prepareMint: userAddress required");
|
|
717
649
|
}
|
|
718
|
-
|
|
719
|
-
try {
|
|
720
|
-
txHash = await this.operatorWallet.writeContract({
|
|
721
|
-
address: this.relayAddress,
|
|
722
|
-
abi: import_core3.relayAbi,
|
|
723
|
-
functionName: "mintAndSwap",
|
|
724
|
-
args: [params.mint, params.swap],
|
|
725
|
-
...this.operatorWallet.account ? { account: this.operatorWallet.account } : {}
|
|
726
|
-
});
|
|
727
|
-
} catch (err) {
|
|
650
|
+
if (!params.pointTokenAddress) {
|
|
728
651
|
throw new RelayError(
|
|
729
|
-
"
|
|
730
|
-
|
|
731
|
-
err
|
|
652
|
+
"ENCODE_FAILED",
|
|
653
|
+
"prepareMint: pointTokenAddress required"
|
|
732
654
|
);
|
|
733
655
|
}
|
|
734
|
-
if (
|
|
735
|
-
|
|
656
|
+
if (params.amount <= 0n) {
|
|
657
|
+
throw new RelayError("ENCODE_FAILED", "prepareMint: amount must be positive");
|
|
736
658
|
}
|
|
737
|
-
|
|
738
|
-
const receipt = await this.provider.waitForTransactionReceipt({
|
|
739
|
-
hash: txHash,
|
|
740
|
-
timeout: this.confirmationTimeoutMs
|
|
741
|
-
});
|
|
742
|
-
if (receipt.status !== "success") {
|
|
743
|
-
throw new RelayError(
|
|
744
|
-
"TX_REVERTED",
|
|
745
|
-
`mintAndSwap reverted on-chain (tx=${txHash})`
|
|
746
|
-
);
|
|
747
|
-
}
|
|
748
|
-
return {
|
|
749
|
-
txHash,
|
|
750
|
-
blockNumber: receipt.blockNumber,
|
|
751
|
-
gasUsed: receipt.gasUsed,
|
|
752
|
-
status: receipt.status
|
|
753
|
-
};
|
|
754
|
-
} catch (err) {
|
|
755
|
-
if (err instanceof RelayError) throw err;
|
|
659
|
+
if (!params.issuerSignerWallet) {
|
|
756
660
|
throw new RelayError(
|
|
757
|
-
"
|
|
758
|
-
|
|
759
|
-
err
|
|
661
|
+
"ENCODE_FAILED",
|
|
662
|
+
"prepareMint: issuerSignerWallet required (for MintRequest EIP-712 signature)"
|
|
760
663
|
);
|
|
761
664
|
}
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
// v1.4 — Sponsored UserOp preparation (beta with mocked SC contracts)
|
|
765
|
-
// ==========================================================================
|
|
766
|
-
//
|
|
767
|
-
// These two methods build unsigned `PartialUserOperation` payloads for
|
|
768
|
-
// the Frontend to sign (via Privy) and submit to the Bundler. The
|
|
769
|
-
// Issuer Backend no longer broadcasts — that's the Frontend's job.
|
|
770
|
-
//
|
|
771
|
-
// Uses mocked Relayer v2 + PointToken ABIs from `@pafi-dev/core/contracts`.
|
|
772
|
-
// When SC delivers real ABIs, the imports swap but these method bodies
|
|
773
|
-
// stay the same (calldata encoder is ABI-driven).
|
|
774
|
-
// ==========================================================================
|
|
775
|
-
/**
|
|
776
|
-
* Build an unsigned UserOp for Scenario 1 (Mint).
|
|
777
|
-
*
|
|
778
|
-
* Flow:
|
|
779
|
-
* 1. Encode `Relayer.mint(request, userSig, issuerSig)` as the inner call
|
|
780
|
-
* 2. Optionally append a PT fee transfer from user → feeRecipient
|
|
781
|
-
* (fee recovery happens on-chain via BatchExecutor, not via an
|
|
782
|
-
* operator wallet)
|
|
783
|
-
* 3. Wrap all inner calls into `BatchExecutor.execute(calls[])`
|
|
784
|
-
* 4. Return a `PartialUserOperation` ready for:
|
|
785
|
-
* - gas estimation (Bundler)
|
|
786
|
-
* - paymaster sponsorship (PAFI Backend)
|
|
787
|
-
* - user signature (Privy)
|
|
788
|
-
*/
|
|
789
|
-
prepareMint(params) {
|
|
790
|
-
if (!params.relayerAddress) {
|
|
791
|
-
throw new RelayError("ENCODE_FAILED", "prepareMint: relayerAddress required");
|
|
665
|
+
if (params.deadline <= 0n) {
|
|
666
|
+
throw new RelayError("ENCODE_FAILED", "prepareMint: deadline must be positive");
|
|
792
667
|
}
|
|
793
|
-
|
|
668
|
+
let minterSig;
|
|
669
|
+
try {
|
|
670
|
+
const sig = await (0, import_core3.signMintRequest)(
|
|
671
|
+
params.issuerSignerWallet,
|
|
672
|
+
params.domain,
|
|
673
|
+
{
|
|
674
|
+
to: params.userAddress,
|
|
675
|
+
amount: params.amount,
|
|
676
|
+
nonce: params.mintRequestNonce,
|
|
677
|
+
deadline: params.deadline
|
|
678
|
+
}
|
|
679
|
+
);
|
|
680
|
+
minterSig = sig.serialized;
|
|
681
|
+
} catch (err) {
|
|
794
682
|
throw new RelayError(
|
|
795
683
|
"ENCODE_FAILED",
|
|
796
|
-
|
|
684
|
+
`prepareMint: failed to sign MintRequest: ${errorMessage(err)}`,
|
|
685
|
+
err
|
|
797
686
|
);
|
|
798
687
|
}
|
|
799
|
-
if (!params.userAddress) {
|
|
800
|
-
throw new RelayError("ENCODE_FAILED", "prepareMint: userAddress required");
|
|
801
|
-
}
|
|
802
688
|
let mintCallData;
|
|
803
689
|
try {
|
|
804
690
|
mintCallData = (0, import_viem5.encodeFunctionData)({
|
|
805
|
-
abi: import_core3.
|
|
691
|
+
abi: import_core3.POINT_TOKEN_V2_ABI,
|
|
806
692
|
functionName: "mint",
|
|
807
|
-
args: [params.
|
|
693
|
+
args: [params.userAddress, params.amount, params.deadline, minterSig]
|
|
808
694
|
});
|
|
809
695
|
} catch (err) {
|
|
810
696
|
throw new RelayError(
|
|
811
697
|
"ENCODE_FAILED",
|
|
812
|
-
`prepareMint: failed to encode
|
|
698
|
+
`prepareMint: failed to encode PointToken.mint: ${errorMessage(err)}`,
|
|
813
699
|
err
|
|
814
700
|
);
|
|
815
701
|
}
|
|
816
702
|
const operations = [
|
|
817
703
|
{
|
|
818
|
-
target: params.
|
|
704
|
+
target: params.pointTokenAddress,
|
|
819
705
|
value: 0n,
|
|
820
706
|
data: mintCallData
|
|
821
707
|
}
|
|
822
708
|
];
|
|
823
|
-
if (params.
|
|
709
|
+
if (params.feeAmount && params.feeAmount > 0n) {
|
|
710
|
+
if (!params.feeRecipient) {
|
|
711
|
+
throw new RelayError(
|
|
712
|
+
"ENCODE_FAILED",
|
|
713
|
+
"prepareMint: feeRecipient required when feeAmount > 0"
|
|
714
|
+
);
|
|
715
|
+
}
|
|
824
716
|
operations.push({
|
|
825
717
|
target: params.pointTokenAddress,
|
|
826
718
|
value: 0n,
|
|
827
719
|
data: (0, import_viem5.encodeFunctionData)({
|
|
828
|
-
abi:
|
|
829
|
-
functionName: "
|
|
830
|
-
|
|
831
|
-
args: [params.mintRequest.feeRecipient]
|
|
720
|
+
abi: import_viem5.erc20Abi,
|
|
721
|
+
functionName: "transfer",
|
|
722
|
+
args: [params.feeRecipient, params.feeAmount]
|
|
832
723
|
})
|
|
833
724
|
});
|
|
834
725
|
}
|
|
@@ -837,7 +728,7 @@ var RelayService = class {
|
|
|
837
728
|
nonce: params.aaNonce,
|
|
838
729
|
operations,
|
|
839
730
|
gasLimits: {
|
|
840
|
-
callGasLimit: params.callGasLimit ??
|
|
731
|
+
callGasLimit: params.callGasLimit ?? 300000n,
|
|
841
732
|
verificationGasLimit: params.verificationGasLimit ?? 150000n,
|
|
842
733
|
preVerificationGas: params.preVerificationGas ?? 50000n
|
|
843
734
|
}
|
|
@@ -847,13 +738,12 @@ var RelayService = class {
|
|
|
847
738
|
* Build an unsigned UserOp for Scenario 2 (Burn/Redeem).
|
|
848
739
|
*
|
|
849
740
|
* Two modes:
|
|
850
|
-
* - `mode: 'burn'` — direct `PointToken.burn(amount)`;
|
|
851
|
-
* via EIP-7702
|
|
852
|
-
*
|
|
853
|
-
*
|
|
854
|
-
*
|
|
855
|
-
*
|
|
856
|
-
* contract has to do it on-chain
|
|
741
|
+
* - `mode: 'burn'` — direct `PointToken.burn(from, amount)`; only
|
|
742
|
+
* usable if the caller (via EIP-7702) is whitelisted as a burner.
|
|
743
|
+
* Rare in v1.4; kept for admin/operator tools.
|
|
744
|
+
* - `mode: 'burnWithSig'` — `PointToken.burn(from, amount, deadline,
|
|
745
|
+
* burnerSig)`. Caller provides a pre-signed `BurnRequest` + sig
|
|
746
|
+
* bytes (typically from `PTRedeemHandler`).
|
|
857
747
|
*/
|
|
858
748
|
prepareBurn(params) {
|
|
859
749
|
if (!params.pointTokenAddress) {
|
|
@@ -868,19 +758,24 @@ var RelayService = class {
|
|
|
868
758
|
let burnCallData;
|
|
869
759
|
try {
|
|
870
760
|
if (params.mode === "burnWithSig") {
|
|
871
|
-
if (!params.
|
|
872
|
-
throw new Error("burnWithSig requires
|
|
761
|
+
if (!params.burnRequest || !params.burnerSignature) {
|
|
762
|
+
throw new Error("burnWithSig requires burnRequest + burnerSignature");
|
|
873
763
|
}
|
|
874
764
|
burnCallData = (0, import_viem5.encodeFunctionData)({
|
|
875
765
|
abi: import_core3.POINT_TOKEN_V2_ABI,
|
|
876
|
-
functionName: "
|
|
877
|
-
args: [
|
|
766
|
+
functionName: "burn",
|
|
767
|
+
args: [
|
|
768
|
+
params.burnRequest.from,
|
|
769
|
+
params.burnRequest.amount,
|
|
770
|
+
params.burnRequest.deadline,
|
|
771
|
+
params.burnerSignature
|
|
772
|
+
]
|
|
878
773
|
});
|
|
879
774
|
} else {
|
|
880
775
|
burnCallData = (0, import_viem5.encodeFunctionData)({
|
|
881
776
|
abi: import_core3.POINT_TOKEN_V2_ABI,
|
|
882
777
|
functionName: "burn",
|
|
883
|
-
args: [params.amount]
|
|
778
|
+
args: [params.userAddress, params.amount]
|
|
884
779
|
});
|
|
885
780
|
}
|
|
886
781
|
} catch (err) {
|
|
@@ -950,252 +845,6 @@ var FeeManager = class {
|
|
|
950
845
|
}
|
|
951
846
|
};
|
|
952
847
|
|
|
953
|
-
// src/gateway/types.ts
|
|
954
|
-
var MintingGatewayError = class extends Error {
|
|
955
|
-
code;
|
|
956
|
-
/**
|
|
957
|
-
* True if the ledger lock was released before this error was thrown,
|
|
958
|
-
* meaning the user can safely retry. False means the funds are still
|
|
959
|
-
* locked (e.g. tx may still land on-chain) and retry would double-spend.
|
|
960
|
-
*/
|
|
961
|
-
safeToRetry;
|
|
962
|
-
cause;
|
|
963
|
-
constructor(code, message, opts) {
|
|
964
|
-
super(message);
|
|
965
|
-
this.name = "MintingGatewayError";
|
|
966
|
-
this.code = code;
|
|
967
|
-
this.safeToRetry = opts.safeToRetry;
|
|
968
|
-
if (opts.cause !== void 0) this.cause = opts.cause;
|
|
969
|
-
}
|
|
970
|
-
};
|
|
971
|
-
|
|
972
|
-
// src/gateway/mintingGateway.ts
|
|
973
|
-
var import_core4 = require("@pafi-dev/core");
|
|
974
|
-
var DEFAULT_LOCK_BUFFER_MS = 6e4;
|
|
975
|
-
var MintingGateway = class {
|
|
976
|
-
ledger;
|
|
977
|
-
policy;
|
|
978
|
-
signer;
|
|
979
|
-
relayService;
|
|
980
|
-
now;
|
|
981
|
-
defaultLockBufferMs;
|
|
982
|
-
constructor(config) {
|
|
983
|
-
if (!config.ledger) throw new Error("MintingGateway: ledger required");
|
|
984
|
-
if (!config.policy) throw new Error("MintingGateway: policy required");
|
|
985
|
-
if (!config.signer) throw new Error("MintingGateway: signer required");
|
|
986
|
-
if (!config.relayService)
|
|
987
|
-
throw new Error("MintingGateway: relayService required");
|
|
988
|
-
this.ledger = config.ledger;
|
|
989
|
-
this.policy = config.policy;
|
|
990
|
-
this.signer = config.signer;
|
|
991
|
-
this.relayService = config.relayService;
|
|
992
|
-
this.now = config.now ?? (() => Date.now());
|
|
993
|
-
this.defaultLockBufferMs = config.defaultLockBufferMs ?? DEFAULT_LOCK_BUFFER_MS;
|
|
994
|
-
}
|
|
995
|
-
/**
|
|
996
|
-
* @deprecated Since 0.3.0 — will be renamed to `processMint()` once
|
|
997
|
-
* the SC team finalizes Relayer v2 ABI. The new flow drops the
|
|
998
|
-
* swap steps entirely (no more single-call mint+swap); users swap
|
|
999
|
-
* separately on PAFI Web. Kept here for v0.2.x consumers. Removed in 2.0.
|
|
1000
|
-
*/
|
|
1001
|
-
async processMintAndCashOut(request) {
|
|
1002
|
-
const { receiverConsent, receiverSignature } = request;
|
|
1003
|
-
if (!receiverConsent || !receiverSignature) {
|
|
1004
|
-
throw new MintingGatewayError(
|
|
1005
|
-
"INVALID_REQUEST",
|
|
1006
|
-
"receiverConsent and receiverSignature are required",
|
|
1007
|
-
{ safeToRetry: true }
|
|
1008
|
-
);
|
|
1009
|
-
}
|
|
1010
|
-
if (receiverConsent.amount <= 0n) {
|
|
1011
|
-
throw new MintingGatewayError(
|
|
1012
|
-
"INVALID_REQUEST",
|
|
1013
|
-
"consent amount must be positive",
|
|
1014
|
-
{ safeToRetry: true }
|
|
1015
|
-
);
|
|
1016
|
-
}
|
|
1017
|
-
if (receiverConsent.originalReceiver !== request.userAddress) {
|
|
1018
|
-
throw new MintingGatewayError(
|
|
1019
|
-
"INVALID_REQUEST",
|
|
1020
|
-
"consent.originalReceiver must equal request.userAddress",
|
|
1021
|
-
{ safeToRetry: true }
|
|
1022
|
-
);
|
|
1023
|
-
}
|
|
1024
|
-
const nowSec = BigInt(Math.floor(this.now() / 1e3));
|
|
1025
|
-
if (receiverConsent.deadline <= nowSec) {
|
|
1026
|
-
throw new MintingGatewayError(
|
|
1027
|
-
"CONSENT_EXPIRED",
|
|
1028
|
-
"ReceiverConsent deadline has already passed",
|
|
1029
|
-
{ safeToRetry: true }
|
|
1030
|
-
);
|
|
1031
|
-
}
|
|
1032
|
-
const consentResult = await (0, import_core4.verifyReceiverConsent)(
|
|
1033
|
-
request.domain,
|
|
1034
|
-
receiverConsent,
|
|
1035
|
-
receiverSignature,
|
|
1036
|
-
request.userAddress
|
|
1037
|
-
);
|
|
1038
|
-
if (!consentResult.isValid) {
|
|
1039
|
-
throw new MintingGatewayError(
|
|
1040
|
-
"INVALID_CONSENT_SIGNATURE",
|
|
1041
|
-
`ReceiverConsent signature did not recover to ${request.userAddress}`,
|
|
1042
|
-
{ safeToRetry: true }
|
|
1043
|
-
);
|
|
1044
|
-
}
|
|
1045
|
-
const policyDecision = await this.policy.evaluate({
|
|
1046
|
-
userAddress: request.userAddress,
|
|
1047
|
-
amount: receiverConsent.amount,
|
|
1048
|
-
pointTokenAddress: request.pointTokenAddress,
|
|
1049
|
-
chainId: request.chainId
|
|
1050
|
-
});
|
|
1051
|
-
if (!policyDecision.approved) {
|
|
1052
|
-
const code = policyDecision.reason?.toLowerCase().includes("insufficient") ? "INSUFFICIENT_BALANCE" : "POLICY_REJECTED";
|
|
1053
|
-
throw new MintingGatewayError(
|
|
1054
|
-
code,
|
|
1055
|
-
policyDecision.reason ?? "Minting request rejected by policy engine",
|
|
1056
|
-
{ safeToRetry: true }
|
|
1057
|
-
);
|
|
1058
|
-
}
|
|
1059
|
-
const lockDurationMs = request.lockDurationMs ?? this.computeLockDurationMs(receiverConsent.deadline);
|
|
1060
|
-
let lockId;
|
|
1061
|
-
try {
|
|
1062
|
-
lockId = await this.ledger.lockForMinting(
|
|
1063
|
-
request.userAddress,
|
|
1064
|
-
receiverConsent.amount,
|
|
1065
|
-
lockDurationMs,
|
|
1066
|
-
request.pointTokenAddress
|
|
1067
|
-
);
|
|
1068
|
-
} catch (err) {
|
|
1069
|
-
throw new MintingGatewayError(
|
|
1070
|
-
"INSUFFICIENT_BALANCE",
|
|
1071
|
-
`Failed to lock ledger balance: ${errorMessage2(err)}`,
|
|
1072
|
-
{ safeToRetry: true, cause: err }
|
|
1073
|
-
);
|
|
1074
|
-
}
|
|
1075
|
-
try {
|
|
1076
|
-
let minterSignature;
|
|
1077
|
-
try {
|
|
1078
|
-
minterSignature = await this.signer.signMintRequest(request.domain, {
|
|
1079
|
-
to: request.userAddress,
|
|
1080
|
-
amount: receiverConsent.amount,
|
|
1081
|
-
nonce: receiverConsent.nonce,
|
|
1082
|
-
deadline: receiverConsent.deadline
|
|
1083
|
-
});
|
|
1084
|
-
} catch (err) {
|
|
1085
|
-
await this.releaseLockSafely(lockId);
|
|
1086
|
-
throw new MintingGatewayError(
|
|
1087
|
-
"SIGNER_FAILED",
|
|
1088
|
-
`Issuer signer failed: ${errorMessage2(err)}`,
|
|
1089
|
-
{ safeToRetry: true, cause: err }
|
|
1090
|
-
);
|
|
1091
|
-
}
|
|
1092
|
-
const mintParams = {
|
|
1093
|
-
pointToken: request.pointTokenAddress,
|
|
1094
|
-
receiver: request.userAddress,
|
|
1095
|
-
amount: receiverConsent.amount,
|
|
1096
|
-
deadline: receiverConsent.deadline,
|
|
1097
|
-
minterSig: minterSignature.serialized,
|
|
1098
|
-
receiverSig: receiverSignature,
|
|
1099
|
-
extData: receiverConsent.extData
|
|
1100
|
-
};
|
|
1101
|
-
const swapParams = {
|
|
1102
|
-
path: request.swapPath,
|
|
1103
|
-
deadline: request.swapDeadline
|
|
1104
|
-
};
|
|
1105
|
-
let relayResult;
|
|
1106
|
-
try {
|
|
1107
|
-
relayResult = await this.relayService.submitMintAndSwap({
|
|
1108
|
-
mint: mintParams,
|
|
1109
|
-
swap: swapParams
|
|
1110
|
-
});
|
|
1111
|
-
} catch (err) {
|
|
1112
|
-
await this.handleRelayFailure(err, lockId);
|
|
1113
|
-
}
|
|
1114
|
-
const result = {
|
|
1115
|
-
txHash: relayResult.txHash,
|
|
1116
|
-
lockId
|
|
1117
|
-
};
|
|
1118
|
-
if (relayResult.blockNumber !== void 0) {
|
|
1119
|
-
result.blockNumber = relayResult.blockNumber;
|
|
1120
|
-
}
|
|
1121
|
-
if (relayResult.gasUsed !== void 0) {
|
|
1122
|
-
result.gasUsed = relayResult.gasUsed;
|
|
1123
|
-
}
|
|
1124
|
-
return result;
|
|
1125
|
-
} catch (err) {
|
|
1126
|
-
if (err instanceof MintingGatewayError) throw err;
|
|
1127
|
-
await this.releaseLockSafely(lockId);
|
|
1128
|
-
throw new MintingGatewayError(
|
|
1129
|
-
"RELAY_SUBMIT_FAILED",
|
|
1130
|
-
`Unexpected error: ${errorMessage2(err)}`,
|
|
1131
|
-
{ safeToRetry: true, cause: err }
|
|
1132
|
-
);
|
|
1133
|
-
}
|
|
1134
|
-
}
|
|
1135
|
-
// ---------------------------------------------------------------------------
|
|
1136
|
-
// Internals
|
|
1137
|
-
// ---------------------------------------------------------------------------
|
|
1138
|
-
computeLockDurationMs(consentDeadlineSec) {
|
|
1139
|
-
const nowMs = this.now();
|
|
1140
|
-
const deadlineMs = Number(consentDeadlineSec) * 1e3;
|
|
1141
|
-
const remaining = Math.max(0, deadlineMs - nowMs);
|
|
1142
|
-
return remaining + this.defaultLockBufferMs;
|
|
1143
|
-
}
|
|
1144
|
-
/**
|
|
1145
|
-
* Map a RelayError to a MintingGatewayError, releasing the lock only
|
|
1146
|
-
* when the tx definitely did not land. `TX_REVERTED` and `TIMEOUT`
|
|
1147
|
-
* leave the lock in place because the tx may still be in the mempool
|
|
1148
|
-
* or already mined — releasing would enable a double-spend on retry.
|
|
1149
|
-
* Always throws.
|
|
1150
|
-
*/
|
|
1151
|
-
async handleRelayFailure(err, lockId) {
|
|
1152
|
-
if (err instanceof RelayError) {
|
|
1153
|
-
switch (err.code) {
|
|
1154
|
-
case "ENCODE_FAILED":
|
|
1155
|
-
case "SIMULATION_FAILED":
|
|
1156
|
-
case "SUBMIT_FAILED":
|
|
1157
|
-
case "NOT_CONFIGURED":
|
|
1158
|
-
await this.releaseLockSafely(lockId);
|
|
1159
|
-
throw new MintingGatewayError(
|
|
1160
|
-
err.code === "SIMULATION_FAILED" ? "RELAY_SIMULATION_FAILED" : "RELAY_SUBMIT_FAILED",
|
|
1161
|
-
err.message,
|
|
1162
|
-
{ safeToRetry: true, cause: err }
|
|
1163
|
-
);
|
|
1164
|
-
case "TX_REVERTED":
|
|
1165
|
-
throw new MintingGatewayError("RELAY_REVERTED", err.message, {
|
|
1166
|
-
safeToRetry: false,
|
|
1167
|
-
cause: err
|
|
1168
|
-
});
|
|
1169
|
-
case "TIMEOUT":
|
|
1170
|
-
throw new MintingGatewayError("RELAY_TIMEOUT", err.message, {
|
|
1171
|
-
safeToRetry: false,
|
|
1172
|
-
cause: err
|
|
1173
|
-
});
|
|
1174
|
-
}
|
|
1175
|
-
}
|
|
1176
|
-
await this.releaseLockSafely(lockId);
|
|
1177
|
-
throw new MintingGatewayError(
|
|
1178
|
-
"RELAY_SUBMIT_FAILED",
|
|
1179
|
-
`Unexpected relay error: ${errorMessage2(err)}`,
|
|
1180
|
-
{ safeToRetry: true, cause: err }
|
|
1181
|
-
);
|
|
1182
|
-
}
|
|
1183
|
-
/**
|
|
1184
|
-
* Release a lock, swallowing any secondary error. We never want a lock
|
|
1185
|
-
* release failure to mask the original error — the lock will auto-expire
|
|
1186
|
-
* via TTL anyway.
|
|
1187
|
-
*/
|
|
1188
|
-
async releaseLockSafely(lockId) {
|
|
1189
|
-
try {
|
|
1190
|
-
await this.ledger.releaseLock(lockId);
|
|
1191
|
-
} catch {
|
|
1192
|
-
}
|
|
1193
|
-
}
|
|
1194
|
-
};
|
|
1195
|
-
function errorMessage2(err) {
|
|
1196
|
-
return err instanceof Error ? err.message : String(err);
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
848
|
// src/indexer/types.ts
|
|
1200
849
|
var InMemoryCursorStore = class {
|
|
1201
850
|
cursor;
|
|
@@ -1540,10 +1189,9 @@ var BurnIndexer = class {
|
|
|
1540
1189
|
|
|
1541
1190
|
// src/api/handlers.ts
|
|
1542
1191
|
var import_viem8 = require("viem");
|
|
1543
|
-
var
|
|
1192
|
+
var import_core4 = require("@pafi-dev/core");
|
|
1544
1193
|
var IssuerApiHandlers = class {
|
|
1545
1194
|
authService;
|
|
1546
|
-
gateway;
|
|
1547
1195
|
ledger;
|
|
1548
1196
|
provider;
|
|
1549
1197
|
/**
|
|
@@ -1562,7 +1210,6 @@ var IssuerApiHandlers = class {
|
|
|
1562
1210
|
poolsProvider;
|
|
1563
1211
|
constructor(config) {
|
|
1564
1212
|
this.authService = config.authService;
|
|
1565
|
-
this.gateway = config.gateway;
|
|
1566
1213
|
this.ledger = config.ledger;
|
|
1567
1214
|
this.provider = config.provider;
|
|
1568
1215
|
const raw = config.pointTokenAddresses && config.pointTokenAddresses.length > 0 ? config.pointTokenAddresses : config.pointTokenAddress ? [config.pointTokenAddress] : [];
|
|
@@ -1686,11 +1333,11 @@ var IssuerApiHandlers = class {
|
|
|
1686
1333
|
);
|
|
1687
1334
|
}
|
|
1688
1335
|
const [mintRequestNonce, receiverConsentNonce, offChainBalance, onChainBalance, minter] = await Promise.all([
|
|
1689
|
-
(0,
|
|
1690
|
-
(0,
|
|
1336
|
+
(0, import_core4.getMintRequestNonce)(this.provider, pointToken, normalizedAuthed),
|
|
1337
|
+
(0, import_core4.getReceiverConsentNonce)(this.provider, pointToken, normalizedAuthed),
|
|
1691
1338
|
this.ledger.getBalance(normalizedAuthed, pointToken),
|
|
1692
|
-
(0,
|
|
1693
|
-
(0,
|
|
1339
|
+
(0, import_core4.getPointTokenBalance)(this.provider, pointToken, normalizedAuthed),
|
|
1340
|
+
(0, import_core4.isMinter)(this.provider, pointToken, normalizedAuthed)
|
|
1694
1341
|
]);
|
|
1695
1342
|
return {
|
|
1696
1343
|
mintRequestNonce,
|
|
@@ -1728,13 +1375,13 @@ var IssuerApiHandlers = class {
|
|
|
1728
1375
|
`handleBuildConsentTypedData: unsupported pointToken ${pointToken}`
|
|
1729
1376
|
);
|
|
1730
1377
|
}
|
|
1731
|
-
const name = await (0,
|
|
1378
|
+
const name = await (0, import_core4.getTokenName)(this.provider, pointToken);
|
|
1732
1379
|
const domain = {
|
|
1733
1380
|
name,
|
|
1734
1381
|
verifyingContract: pointToken,
|
|
1735
1382
|
chainId: this.chainId
|
|
1736
1383
|
};
|
|
1737
|
-
const typedData = (0,
|
|
1384
|
+
const typedData = (0, import_core4.buildReceiverConsentTypedData)(domain, request.receiverConsent);
|
|
1738
1385
|
return {
|
|
1739
1386
|
typedData: {
|
|
1740
1387
|
domain: typedData.domain,
|
|
@@ -1744,55 +1391,13 @@ var IssuerApiHandlers = class {
|
|
|
1744
1391
|
}
|
|
1745
1392
|
};
|
|
1746
1393
|
}
|
|
1747
|
-
/**
|
|
1748
|
-
* `POST /claim-and-swap`
|
|
1749
|
-
*
|
|
1750
|
-
* @deprecated Since 0.3.0 — the single-call mint-then-swap flow is
|
|
1751
|
-
* retired in v1.4. Use the new `handleClaim()` (mint only) and let
|
|
1752
|
-
* the user swap separately on PAFI Web. See
|
|
1753
|
-
* [V1.4_V1.5_OVERVIEW.md §4] for the new scenario model. Will be
|
|
1754
|
-
* removed in 2.0.
|
|
1755
|
-
*
|
|
1756
|
-
* Legacy behavior: the terminal handler forwards the verified
|
|
1757
|
-
* consent to the MintingGateway, which runs the 11-step flow.
|
|
1758
|
-
*/
|
|
1759
|
-
async handleClaimAndSwap(userAddress, request) {
|
|
1760
|
-
if (request.chainId !== this.chainId) {
|
|
1761
|
-
throw new Error(
|
|
1762
|
-
`handleClaimAndSwap: unsupported chainId ${request.chainId}`
|
|
1763
|
-
);
|
|
1764
|
-
}
|
|
1765
|
-
const pointToken = (0, import_viem8.getAddress)(request.pointTokenAddress);
|
|
1766
|
-
if (!this.supportedTokens.has(pointToken)) {
|
|
1767
|
-
throw new Error(
|
|
1768
|
-
`handleClaimAndSwap: unsupported pointToken ${pointToken}`
|
|
1769
|
-
);
|
|
1770
|
-
}
|
|
1771
|
-
const result = await this.gateway.processMintAndCashOut({
|
|
1772
|
-
userAddress: (0, import_viem8.getAddress)(userAddress),
|
|
1773
|
-
pointTokenAddress: pointToken,
|
|
1774
|
-
chainId: request.chainId,
|
|
1775
|
-
domain: request.domain,
|
|
1776
|
-
receiverConsent: request.receiverConsent,
|
|
1777
|
-
receiverSignature: request.receiverSignature,
|
|
1778
|
-
swapPath: request.swapPath,
|
|
1779
|
-
swapDeadline: request.swapDeadline
|
|
1780
|
-
});
|
|
1781
|
-
const response = {
|
|
1782
|
-
txHash: result.txHash,
|
|
1783
|
-
lockId: result.lockId
|
|
1784
|
-
};
|
|
1785
|
-
if (result.blockNumber !== void 0)
|
|
1786
|
-
response.blockNumber = result.blockNumber;
|
|
1787
|
-
if (result.gasUsed !== void 0) response.gasUsed = result.gasUsed;
|
|
1788
|
-
return response;
|
|
1789
|
-
}
|
|
1790
1394
|
};
|
|
1791
1395
|
|
|
1792
1396
|
// src/api/handlers/ptRedeemHandler.ts
|
|
1793
1397
|
var import_viem9 = require("viem");
|
|
1794
|
-
var
|
|
1398
|
+
var import_core5 = require("@pafi-dev/core");
|
|
1795
1399
|
var DEFAULT_REDEEM_LOCK_MS = 15 * 60 * 1e3;
|
|
1400
|
+
var DEFAULT_SIG_DEADLINE_SEC = 15 * 60;
|
|
1796
1401
|
var PTRedeemError = class extends Error {
|
|
1797
1402
|
constructor(code, message) {
|
|
1798
1403
|
super(message);
|
|
@@ -1804,11 +1409,14 @@ var PTRedeemError = class extends Error {
|
|
|
1804
1409
|
var PTRedeemHandler = class {
|
|
1805
1410
|
ledger;
|
|
1806
1411
|
relayService;
|
|
1412
|
+
provider;
|
|
1807
1413
|
pointTokenAddress;
|
|
1808
1414
|
batchExecutorAddress;
|
|
1809
1415
|
chainId;
|
|
1810
1416
|
domain;
|
|
1417
|
+
burnerSignerWallet;
|
|
1811
1418
|
redeemLockDurationMs;
|
|
1419
|
+
signatureDeadlineSeconds;
|
|
1812
1420
|
now;
|
|
1813
1421
|
constructor(config) {
|
|
1814
1422
|
if (!config.ledger.reservePendingCredit) {
|
|
@@ -1817,46 +1425,68 @@ var PTRedeemHandler = class {
|
|
|
1817
1425
|
"PTRedeemHandler requires a ledger that implements reservePendingCredit() (v0.3.0+)"
|
|
1818
1426
|
);
|
|
1819
1427
|
}
|
|
1428
|
+
if (!config.burnerSignerWallet) {
|
|
1429
|
+
throw new PTRedeemError(
|
|
1430
|
+
"SIGNING_FAILED",
|
|
1431
|
+
"PTRedeemHandler requires burnerSignerWallet (issuer burner signer)"
|
|
1432
|
+
);
|
|
1433
|
+
}
|
|
1820
1434
|
this.ledger = config.ledger;
|
|
1821
1435
|
this.relayService = config.relayService;
|
|
1436
|
+
this.provider = config.provider;
|
|
1822
1437
|
this.pointTokenAddress = (0, import_viem9.getAddress)(config.pointTokenAddress);
|
|
1823
1438
|
this.batchExecutorAddress = (0, import_viem9.getAddress)(config.batchExecutorAddress);
|
|
1824
1439
|
this.chainId = config.chainId;
|
|
1825
1440
|
this.domain = config.domain;
|
|
1441
|
+
this.burnerSignerWallet = config.burnerSignerWallet;
|
|
1826
1442
|
this.redeemLockDurationMs = config.redeemLockDurationMs ?? DEFAULT_REDEEM_LOCK_MS;
|
|
1443
|
+
this.signatureDeadlineSeconds = config.signatureDeadlineSeconds ?? DEFAULT_SIG_DEADLINE_SEC;
|
|
1827
1444
|
this.now = config.now ?? (() => Date.now());
|
|
1828
1445
|
}
|
|
1829
1446
|
async handle(request) {
|
|
1830
1447
|
if (request.amount <= 0n) {
|
|
1831
|
-
throw new PTRedeemError("
|
|
1448
|
+
throw new PTRedeemError("INVALID_AMOUNT", "redeem amount must be positive");
|
|
1832
1449
|
}
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1450
|
+
let burnNonce;
|
|
1451
|
+
try {
|
|
1452
|
+
burnNonce = await this.provider.readContract({
|
|
1453
|
+
address: this.pointTokenAddress,
|
|
1454
|
+
abi: import_core5.POINT_TOKEN_V2_ABI,
|
|
1455
|
+
functionName: "burnRequestNonces",
|
|
1456
|
+
args: [request.userAddress]
|
|
1457
|
+
});
|
|
1458
|
+
} catch (err) {
|
|
1841
1459
|
throw new PTRedeemError(
|
|
1842
|
-
"
|
|
1843
|
-
`
|
|
1460
|
+
"NONCE_READ_FAILED",
|
|
1461
|
+
`failed to read burnRequestNonces(${request.userAddress}): ${err instanceof Error ? err.message : String(err)}`
|
|
1844
1462
|
);
|
|
1845
1463
|
}
|
|
1846
|
-
const
|
|
1847
|
-
|
|
1848
|
-
name: this.domain.name,
|
|
1849
|
-
chainId: this.chainId,
|
|
1850
|
-
verifyingContract: this.domain.verifyingContract ?? this.pointTokenAddress
|
|
1851
|
-
},
|
|
1852
|
-
request.consent,
|
|
1853
|
-
request.consentSignature,
|
|
1854
|
-
request.userAddress
|
|
1464
|
+
const deadline = BigInt(
|
|
1465
|
+
Math.floor(this.now() / 1e3) + this.signatureDeadlineSeconds
|
|
1855
1466
|
);
|
|
1856
|
-
|
|
1467
|
+
const domain = {
|
|
1468
|
+
name: this.domain.name,
|
|
1469
|
+
chainId: this.chainId,
|
|
1470
|
+
verifyingContract: this.domain.verifyingContract ?? this.pointTokenAddress
|
|
1471
|
+
};
|
|
1472
|
+
const burnRequest = {
|
|
1473
|
+
from: request.userAddress,
|
|
1474
|
+
amount: request.amount,
|
|
1475
|
+
nonce: burnNonce,
|
|
1476
|
+
deadline
|
|
1477
|
+
};
|
|
1478
|
+
let burnerSignature;
|
|
1479
|
+
try {
|
|
1480
|
+
const sig = await (0, import_core5.signBurnRequest)(
|
|
1481
|
+
this.burnerSignerWallet,
|
|
1482
|
+
domain,
|
|
1483
|
+
burnRequest
|
|
1484
|
+
);
|
|
1485
|
+
burnerSignature = sig.serialized;
|
|
1486
|
+
} catch (err) {
|
|
1857
1487
|
throw new PTRedeemError(
|
|
1858
|
-
"
|
|
1859
|
-
`
|
|
1488
|
+
"SIGNING_FAILED",
|
|
1489
|
+
`failed to sign BurnRequest: ${err instanceof Error ? err.message : String(err)}`
|
|
1860
1490
|
);
|
|
1861
1491
|
}
|
|
1862
1492
|
const lockId = await this.ledger.reservePendingCredit(
|
|
@@ -1871,33 +1501,21 @@ var PTRedeemHandler = class {
|
|
|
1871
1501
|
aaNonce: request.aaNonce,
|
|
1872
1502
|
pointTokenAddress: this.pointTokenAddress,
|
|
1873
1503
|
batchExecutorAddress: this.batchExecutorAddress,
|
|
1874
|
-
|
|
1875
|
-
|
|
1504
|
+
burnRequest,
|
|
1505
|
+
burnerSignature
|
|
1876
1506
|
});
|
|
1877
1507
|
return {
|
|
1878
1508
|
lockId,
|
|
1879
1509
|
userOp,
|
|
1880
|
-
expiresInSeconds: Math.floor(this.redeemLockDurationMs / 1e3)
|
|
1510
|
+
expiresInSeconds: Math.floor(this.redeemLockDurationMs / 1e3),
|
|
1511
|
+
signatureDeadline: deadline
|
|
1881
1512
|
};
|
|
1882
1513
|
}
|
|
1883
1514
|
};
|
|
1884
|
-
function parseSigStruct(serialized) {
|
|
1885
|
-
const raw = serialized.slice(2);
|
|
1886
|
-
if (raw.length !== 130) {
|
|
1887
|
-
throw new PTRedeemError(
|
|
1888
|
-
"INVALID_CONSENT",
|
|
1889
|
-
`signature must be 65 bytes, got ${raw.length / 2}`
|
|
1890
|
-
);
|
|
1891
|
-
}
|
|
1892
|
-
const r = `0x${raw.slice(0, 64)}`;
|
|
1893
|
-
const s = `0x${raw.slice(64, 128)}`;
|
|
1894
|
-
const v = parseInt(raw.slice(128, 130), 16);
|
|
1895
|
-
return { v, r, s };
|
|
1896
|
-
}
|
|
1897
1515
|
|
|
1898
1516
|
// src/api/handlers/topUpRedemptionHandler.ts
|
|
1899
1517
|
var import_viem10 = require("viem");
|
|
1900
|
-
var
|
|
1518
|
+
var import_core6 = require("@pafi-dev/core");
|
|
1901
1519
|
var TopUpRedemptionError = class extends Error {
|
|
1902
1520
|
constructor(code, message) {
|
|
1903
1521
|
super(message);
|
|
@@ -1926,7 +1544,7 @@ var TopUpRedemptionHandler = class {
|
|
|
1926
1544
|
return { action: "NO_TOP_UP_NEEDED", offChainBalance };
|
|
1927
1545
|
}
|
|
1928
1546
|
const shortfall = request.requiredAmount - offChainBalance;
|
|
1929
|
-
const onChainBalance = await (0,
|
|
1547
|
+
const onChainBalance = await (0, import_core6.getPointTokenBalance)(
|
|
1930
1548
|
this.provider,
|
|
1931
1549
|
this.pointTokenAddress,
|
|
1932
1550
|
request.userAddress
|
|
@@ -1939,24 +1557,10 @@ var TopUpRedemptionHandler = class {
|
|
|
1939
1557
|
shortfall
|
|
1940
1558
|
};
|
|
1941
1559
|
}
|
|
1942
|
-
if (request.redeemRequest.consent.amount < shortfall) {
|
|
1943
|
-
throw new TopUpRedemptionError(
|
|
1944
|
-
"CONSENT_AMOUNT_TOO_LOW",
|
|
1945
|
-
`consent.amount (${request.redeemRequest.consent.amount}) must cover shortfall (${shortfall})`
|
|
1946
|
-
);
|
|
1947
|
-
}
|
|
1948
|
-
if (request.redeemRequest.consent.amount !== shortfall) {
|
|
1949
|
-
throw new TopUpRedemptionError(
|
|
1950
|
-
"CONSENT_AMOUNT_TOO_LOW",
|
|
1951
|
-
`consent.amount (${request.redeemRequest.consent.amount}) must equal shortfall (${shortfall}) exactly \u2014 re-sign with correct amount`
|
|
1952
|
-
);
|
|
1953
|
-
}
|
|
1954
1560
|
const redeem = await this.ptRedeemHandler.handle({
|
|
1955
1561
|
userAddress: request.userAddress,
|
|
1956
1562
|
amount: shortfall,
|
|
1957
|
-
|
|
1958
|
-
consentSignature: request.redeemRequest.consentSignature,
|
|
1959
|
-
aaNonce: request.redeemRequest.aaNonce
|
|
1563
|
+
aaNonce: request.aaNonce
|
|
1960
1564
|
});
|
|
1961
1565
|
return {
|
|
1962
1566
|
action: "TOP_UP_STARTED",
|
|
@@ -2178,7 +1782,7 @@ function toUsdtPerNative(priceFloat, usdtDecimals) {
|
|
|
2178
1782
|
}
|
|
2179
1783
|
|
|
2180
1784
|
// src/balance/balanceAggregator.ts
|
|
2181
|
-
var
|
|
1785
|
+
var import_core7 = require("@pafi-dev/core");
|
|
2182
1786
|
var BalanceAggregator = class {
|
|
2183
1787
|
provider;
|
|
2184
1788
|
ledger;
|
|
@@ -2199,7 +1803,7 @@ var BalanceAggregator = class {
|
|
|
2199
1803
|
async getCombinedBalance(user, pointToken) {
|
|
2200
1804
|
const [offChain, onChain] = await Promise.all([
|
|
2201
1805
|
this.ledger.getBalance(user, pointToken),
|
|
2202
|
-
(0,
|
|
1806
|
+
(0, import_core7.getPointTokenBalance)(this.provider, pointToken, user)
|
|
2203
1807
|
]);
|
|
2204
1808
|
return {
|
|
2205
1809
|
offChain,
|
|
@@ -2237,28 +1841,11 @@ var PafiBackendError = class extends Error {
|
|
|
2237
1841
|
code;
|
|
2238
1842
|
httpStatus;
|
|
2239
1843
|
details;
|
|
2240
|
-
/**
|
|
2241
|
-
* Seconds to wait before retry. Populated from the server body
|
|
2242
|
-
* (e.g. rate limit returns the number of seconds until UTC midnight).
|
|
2243
|
-
*/
|
|
2244
1844
|
retryAfter;
|
|
2245
|
-
/**
|
|
2246
|
-
* `safeToRetry` as reported by the server body. Prefer this over the
|
|
2247
|
-
* code-based heuristic when available — the server knows more about
|
|
2248
|
-
* whether the same request will succeed on retry.
|
|
2249
|
-
*/
|
|
2250
1845
|
serverSafeToRetry;
|
|
2251
|
-
/**
|
|
2252
|
-
* Whether the caller can safely retry the same request.
|
|
2253
|
-
*
|
|
2254
|
-
* If the server provided `safeToRetry` in the body, trust that.
|
|
2255
|
-
* Otherwise fall back to a code-based heuristic.
|
|
2256
|
-
*/
|
|
2257
1846
|
get safeToRetry() {
|
|
2258
1847
|
if (this.serverSafeToRetry !== void 0) return this.serverSafeToRetry;
|
|
2259
1848
|
switch (this.code) {
|
|
2260
|
-
case "PAYMASTER_UNAVAILABLE":
|
|
2261
|
-
case "PAYMASTER_TIMEOUT":
|
|
2262
1849
|
case "RATE_LIMITER_UNAVAILABLE":
|
|
2263
1850
|
case "INTERNAL_ERROR":
|
|
2264
1851
|
case "TIMEOUT":
|
|
@@ -2267,197 +1854,20 @@ var PafiBackendError = class extends Error {
|
|
|
2267
1854
|
case "RATE_LIMIT_EXCEEDED":
|
|
2268
1855
|
case "RATE_LIMIT_EXCEEDED_DAILY":
|
|
2269
1856
|
case "RATE_LIMIT_EXCEEDED_PER_USER":
|
|
1857
|
+
case "ISSUER_BUDGET_EXCEEDED":
|
|
2270
1858
|
return true;
|
|
2271
|
-
// after retryAfter
|
|
2272
1859
|
default:
|
|
2273
1860
|
return false;
|
|
2274
1861
|
}
|
|
2275
1862
|
}
|
|
2276
1863
|
};
|
|
2277
1864
|
|
|
2278
|
-
// src/pafi-backend/pafiBackendClient.ts
|
|
2279
|
-
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
2280
|
-
var RETRY_DEFAULTS = {
|
|
2281
|
-
maxAttempts: 1,
|
|
2282
|
-
initialDelayMs: 500,
|
|
2283
|
-
maxDelayMs: 1e4,
|
|
2284
|
-
maxRetryAfterMs: 3e4
|
|
2285
|
-
};
|
|
2286
|
-
var PafiBackendClient = class {
|
|
2287
|
-
url;
|
|
2288
|
-
issuerId;
|
|
2289
|
-
apiKey;
|
|
2290
|
-
fetchImpl;
|
|
2291
|
-
timeoutMs;
|
|
2292
|
-
retry;
|
|
2293
|
-
constructor(config) {
|
|
2294
|
-
if (!config.url) {
|
|
2295
|
-
throw new Error("PafiBackendClient: url is required");
|
|
2296
|
-
}
|
|
2297
|
-
if (!config.issuerId) {
|
|
2298
|
-
throw new Error("PafiBackendClient: issuerId is required");
|
|
2299
|
-
}
|
|
2300
|
-
if (!config.apiKey) {
|
|
2301
|
-
throw new Error("PafiBackendClient: apiKey is required");
|
|
2302
|
-
}
|
|
2303
|
-
this.url = config.url.replace(/\/+$/, "");
|
|
2304
|
-
this.issuerId = config.issuerId;
|
|
2305
|
-
this.apiKey = config.apiKey;
|
|
2306
|
-
this.fetchImpl = config.fetchImpl ?? globalThis.fetch;
|
|
2307
|
-
this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
2308
|
-
this.retry = { ...RETRY_DEFAULTS, ...config.retry ?? {} };
|
|
2309
|
-
if (!this.fetchImpl) {
|
|
2310
|
-
throw new Error(
|
|
2311
|
-
"PafiBackendClient: no fetch implementation available \u2014 pass `fetchImpl` or run on Node 18+"
|
|
2312
|
-
);
|
|
2313
|
-
}
|
|
2314
|
-
if (this.retry.maxAttempts < 1) {
|
|
2315
|
-
throw new Error("PafiBackendClient: retry.maxAttempts must be >= 1");
|
|
2316
|
-
}
|
|
2317
|
-
}
|
|
2318
|
-
/**
|
|
2319
|
-
* Request paymaster sponsorship for a pre-built UserOperation.
|
|
2320
|
-
* See [SPONSORED_PATH_FLOW.md §4.1] for the API contract.
|
|
2321
|
-
*
|
|
2322
|
-
* Retries automatically on transient failures (5xx, timeouts, network
|
|
2323
|
-
* errors, and errors the server flags with `safeToRetry: true`) up to
|
|
2324
|
-
* `retry.maxAttempts`. 4xx errors that are not `safeToRetry` fail fast.
|
|
2325
|
-
*
|
|
2326
|
-
* @throws PafiBackendError on final failure after exhausting retries
|
|
2327
|
-
*/
|
|
2328
|
-
async requestSponsorship(req) {
|
|
2329
|
-
return this.postWithRetry(
|
|
2330
|
-
"/paymaster/sponsor",
|
|
2331
|
-
req
|
|
2332
|
-
);
|
|
2333
|
-
}
|
|
2334
|
-
// -------------------------------------------------------------------------
|
|
2335
|
-
// Internals
|
|
2336
|
-
// -------------------------------------------------------------------------
|
|
2337
|
-
async postWithRetry(path, body) {
|
|
2338
|
-
let lastError;
|
|
2339
|
-
for (let attempt = 1; attempt <= this.retry.maxAttempts; attempt++) {
|
|
2340
|
-
try {
|
|
2341
|
-
return await this.post(path, body);
|
|
2342
|
-
} catch (err) {
|
|
2343
|
-
if (!(err instanceof PafiBackendError)) throw err;
|
|
2344
|
-
lastError = err;
|
|
2345
|
-
const isLastAttempt = attempt >= this.retry.maxAttempts;
|
|
2346
|
-
if (isLastAttempt || !err.safeToRetry) throw err;
|
|
2347
|
-
const delay = this.computeBackoff(attempt, err.retryAfter);
|
|
2348
|
-
if (delay === null) throw err;
|
|
2349
|
-
await this.sleep(delay);
|
|
2350
|
-
}
|
|
2351
|
-
}
|
|
2352
|
-
throw lastError;
|
|
2353
|
-
}
|
|
2354
|
-
/**
|
|
2355
|
-
* Pick the delay before the next retry.
|
|
2356
|
-
* - If the server sent `retryAfter` (seconds), honor it (capped by
|
|
2357
|
-
* `maxRetryAfterMs`) — returns null if the server wait exceeds the
|
|
2358
|
-
* cap, signalling the caller should give up.
|
|
2359
|
-
* - Otherwise: exponential backoff with ±20% jitter, capped at
|
|
2360
|
-
* `maxDelayMs`.
|
|
2361
|
-
*/
|
|
2362
|
-
computeBackoff(attempt, retryAfter) {
|
|
2363
|
-
if (retryAfter !== void 0) {
|
|
2364
|
-
const serverMs = retryAfter * 1e3;
|
|
2365
|
-
if (serverMs > this.retry.maxRetryAfterMs) return null;
|
|
2366
|
-
return serverMs;
|
|
2367
|
-
}
|
|
2368
|
-
const exp = this.retry.initialDelayMs * 2 ** (attempt - 1);
|
|
2369
|
-
const capped = Math.min(exp, this.retry.maxDelayMs);
|
|
2370
|
-
const jitter = capped * (0.8 + Math.random() * 0.4);
|
|
2371
|
-
return Math.round(jitter);
|
|
2372
|
-
}
|
|
2373
|
-
sleep(ms) {
|
|
2374
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2375
|
-
}
|
|
2376
|
-
async post(path, body) {
|
|
2377
|
-
const controller = new AbortController();
|
|
2378
|
-
const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
2379
|
-
let response;
|
|
2380
|
-
try {
|
|
2381
|
-
response = await this.fetchImpl(`${this.url}${path}`, {
|
|
2382
|
-
method: "POST",
|
|
2383
|
-
headers: {
|
|
2384
|
-
"Content-Type": "application/json",
|
|
2385
|
-
"Authorization": `Bearer ${this.apiKey}`,
|
|
2386
|
-
"X-Issuer-Id": this.issuerId
|
|
2387
|
-
},
|
|
2388
|
-
body: JSON.stringify(body, this.bigintReplacer),
|
|
2389
|
-
signal: controller.signal
|
|
2390
|
-
});
|
|
2391
|
-
} catch (err) {
|
|
2392
|
-
if (err.name === "AbortError") {
|
|
2393
|
-
throw new PafiBackendError(
|
|
2394
|
-
"TIMEOUT",
|
|
2395
|
-
`PAFI Backend request timed out after ${this.timeoutMs}ms`,
|
|
2396
|
-
0
|
|
2397
|
-
);
|
|
2398
|
-
}
|
|
2399
|
-
throw new PafiBackendError(
|
|
2400
|
-
"NETWORK_ERROR",
|
|
2401
|
-
`PAFI Backend unreachable: ${err.message}`,
|
|
2402
|
-
0
|
|
2403
|
-
);
|
|
2404
|
-
} finally {
|
|
2405
|
-
clearTimeout(timeoutId);
|
|
2406
|
-
}
|
|
2407
|
-
const text = await response.text();
|
|
2408
|
-
if (!response.ok) {
|
|
2409
|
-
let code = "INTERNAL_ERROR";
|
|
2410
|
-
let message = text || response.statusText;
|
|
2411
|
-
let details;
|
|
2412
|
-
let retryAfter;
|
|
2413
|
-
let serverSafeToRetry;
|
|
2414
|
-
try {
|
|
2415
|
-
const parsed = JSON.parse(text);
|
|
2416
|
-
code = parsed.code ?? code;
|
|
2417
|
-
message = parsed.message ?? message;
|
|
2418
|
-
details = parsed.details;
|
|
2419
|
-
if (typeof parsed.retryAfter === "number") retryAfter = parsed.retryAfter;
|
|
2420
|
-
if (typeof parsed.safeToRetry === "boolean") serverSafeToRetry = parsed.safeToRetry;
|
|
2421
|
-
} catch {
|
|
2422
|
-
}
|
|
2423
|
-
throw new PafiBackendError(code, message, response.status, details, {
|
|
2424
|
-
...retryAfter !== void 0 ? { retryAfter } : {},
|
|
2425
|
-
...serverSafeToRetry !== void 0 ? { safeToRetry: serverSafeToRetry } : {}
|
|
2426
|
-
});
|
|
2427
|
-
}
|
|
2428
|
-
return JSON.parse(text, this.bigintReviver);
|
|
2429
|
-
}
|
|
2430
|
-
/** JSON replacer that stringifies bigints. Paired with bigintReviver. */
|
|
2431
|
-
bigintReplacer = (_key, value) => {
|
|
2432
|
-
return typeof value === "bigint" ? value.toString() : value;
|
|
2433
|
-
};
|
|
2434
|
-
/**
|
|
2435
|
-
* JSON reviver that coerces specific numeric-string fields back to
|
|
2436
|
-
* bigint. The server must send these fields as decimal strings.
|
|
2437
|
-
*/
|
|
2438
|
-
bigintReviver = (key, value) => {
|
|
2439
|
-
if (typeof value === "string" && (key.endsWith("GasLimit") || key === "nonce" || key === "callGasLimit" || key === "verificationGasLimit" || key === "preVerificationGas" || key === "maxFeePerGas" || key === "maxPriorityFeePerGas" || key === "paymasterVerificationGasLimit" || key === "paymasterPostOpGasLimit") && /^\d+$/.test(value)) {
|
|
2440
|
-
return BigInt(value);
|
|
2441
|
-
}
|
|
2442
|
-
return value;
|
|
2443
|
-
};
|
|
2444
|
-
};
|
|
2445
|
-
|
|
2446
1865
|
// src/config.ts
|
|
2447
1866
|
var import_viem11 = require("viem");
|
|
2448
1867
|
function createIssuerService(config) {
|
|
2449
1868
|
if (!config.provider) {
|
|
2450
1869
|
throw new Error("createIssuerService: provider is required");
|
|
2451
1870
|
}
|
|
2452
|
-
if (!config.operatorWallet) {
|
|
2453
|
-
throw new Error("createIssuerService: operatorWallet is required");
|
|
2454
|
-
}
|
|
2455
|
-
if (!config.signer) {
|
|
2456
|
-
throw new Error("createIssuerService: signer is required");
|
|
2457
|
-
}
|
|
2458
|
-
if (!config.relayAddress) {
|
|
2459
|
-
throw new Error("createIssuerService: relayAddress is required");
|
|
2460
|
-
}
|
|
2461
1871
|
if (!config.auth?.jwtSecret) {
|
|
2462
1872
|
throw new Error("createIssuerService: auth.jwtSecret is required");
|
|
2463
1873
|
}
|
|
@@ -2484,18 +1894,7 @@ function createIssuerService(config) {
|
|
|
2484
1894
|
authServiceConfig.jwtExpiresIn = config.auth.jwtExpiresIn;
|
|
2485
1895
|
}
|
|
2486
1896
|
const authService = new AuthService(authServiceConfig);
|
|
2487
|
-
const
|
|
2488
|
-
relayAddress: config.relayAddress,
|
|
2489
|
-
operatorWallet: config.operatorWallet,
|
|
2490
|
-
provider: config.provider
|
|
2491
|
-
};
|
|
2492
|
-
if (config.relay?.simulateBeforeSubmit !== void 0) {
|
|
2493
|
-
relayServiceConfig.simulateBeforeSubmit = config.relay.simulateBeforeSubmit;
|
|
2494
|
-
}
|
|
2495
|
-
if (config.relay?.confirmationTimeoutMs !== void 0) {
|
|
2496
|
-
relayServiceConfig.confirmationTimeoutMs = config.relay.confirmationTimeoutMs;
|
|
2497
|
-
}
|
|
2498
|
-
const relayService = new RelayService(relayServiceConfig);
|
|
1897
|
+
const relayService = new RelayService();
|
|
2499
1898
|
let feeManager;
|
|
2500
1899
|
if (config.fee) {
|
|
2501
1900
|
feeManager = new FeeManager({
|
|
@@ -2503,16 +1902,6 @@ function createIssuerService(config) {
|
|
|
2503
1902
|
provider: config.provider
|
|
2504
1903
|
});
|
|
2505
1904
|
}
|
|
2506
|
-
const gatewayConfig = {
|
|
2507
|
-
ledger,
|
|
2508
|
-
policy,
|
|
2509
|
-
signer: config.signer,
|
|
2510
|
-
relayService
|
|
2511
|
-
};
|
|
2512
|
-
if (config.gateway?.defaultLockBufferMs !== void 0) {
|
|
2513
|
-
gatewayConfig.defaultLockBufferMs = config.gateway.defaultLockBufferMs;
|
|
2514
|
-
}
|
|
2515
|
-
const gateway = new MintingGateway(gatewayConfig);
|
|
2516
1905
|
const indexers = /* @__PURE__ */ new Map();
|
|
2517
1906
|
for (const tokenAddress of tokenAddresses) {
|
|
2518
1907
|
const indexerConfig = {
|
|
@@ -2540,7 +1929,6 @@ function createIssuerService(config) {
|
|
|
2540
1929
|
const firstIndexer = indexers.get(tokenAddresses[0]);
|
|
2541
1930
|
const handlersConfig = {
|
|
2542
1931
|
authService,
|
|
2543
|
-
gateway,
|
|
2544
1932
|
ledger,
|
|
2545
1933
|
provider: config.provider,
|
|
2546
1934
|
pointTokenAddresses: tokenAddresses,
|
|
@@ -2560,10 +1948,8 @@ function createIssuerService(config) {
|
|
|
2560
1948
|
sessionStore,
|
|
2561
1949
|
ledger,
|
|
2562
1950
|
policy,
|
|
2563
|
-
signer: config.signer,
|
|
2564
1951
|
relayService,
|
|
2565
1952
|
feeManager,
|
|
2566
|
-
gateway,
|
|
2567
1953
|
indexers,
|
|
2568
1954
|
indexer: firstIndexer,
|
|
2569
1955
|
handlers
|
|
@@ -2584,13 +1970,10 @@ var PAFI_ISSUER_SDK_VERSION = "0.1.0";
|
|
|
2584
1970
|
IssuerApiHandlers,
|
|
2585
1971
|
MemoryPointLedger,
|
|
2586
1972
|
MemorySessionStore,
|
|
2587
|
-
MintingGateway,
|
|
2588
|
-
MintingGatewayError,
|
|
2589
1973
|
NonceManager,
|
|
2590
1974
|
PAFI_ISSUER_SDK_VERSION,
|
|
2591
1975
|
PTRedeemError,
|
|
2592
1976
|
PTRedeemHandler,
|
|
2593
|
-
PafiBackendClient,
|
|
2594
1977
|
PafiBackendError,
|
|
2595
1978
|
PointIndexer,
|
|
2596
1979
|
PrivateKeySigner,
|
|
@@ -2601,7 +1984,6 @@ var PAFI_ISSUER_SDK_VERSION = "0.1.0";
|
|
|
2601
1984
|
authenticateRequest,
|
|
2602
1985
|
createIssuerService,
|
|
2603
1986
|
createSubgraphNativeUsdtQuoter,
|
|
2604
|
-
createSubgraphPoolsProvider
|
|
2605
|
-
encodeExtData
|
|
1987
|
+
createSubgraphPoolsProvider
|
|
2606
1988
|
});
|
|
2607
1989
|
//# sourceMappingURL=index.cjs.map
|