@pafi-dev/issuer 0.3.0-beta.2 → 0.3.0-beta.4
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 +141 -114
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +126 -82
- package/dist/index.d.ts +126 -82
- package/dist/index.js +148 -118
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -582,15 +582,18 @@ var RelayError = class extends Error {
|
|
|
582
582
|
};
|
|
583
583
|
|
|
584
584
|
// src/relay/relayService.ts
|
|
585
|
-
import {
|
|
585
|
+
import {
|
|
586
|
+
encodeFunctionData,
|
|
587
|
+
erc20Abi
|
|
588
|
+
} from "viem";
|
|
586
589
|
import {
|
|
587
590
|
relayAbi,
|
|
588
591
|
encodeMintAndSwap,
|
|
589
592
|
simulateMintAndSwap as coreSimulateMintAndSwap,
|
|
590
593
|
SimulationError,
|
|
591
|
-
RELAYER_V2_ABI,
|
|
592
594
|
POINT_TOKEN_V2_ABI,
|
|
593
|
-
buildPartialUserOperation
|
|
595
|
+
buildPartialUserOperation,
|
|
596
|
+
signMintRequest as signMintRequest2
|
|
594
597
|
} from "@pafi-dev/core";
|
|
595
598
|
var DEFAULT_CONFIRMATION_TIMEOUT_MS = 6e4;
|
|
596
599
|
var RelayService = class {
|
|
@@ -633,21 +636,11 @@ var RelayService = class {
|
|
|
633
636
|
}
|
|
634
637
|
}
|
|
635
638
|
/**
|
|
636
|
-
* Submit a `mintAndSwap` transaction.
|
|
637
|
-
*
|
|
638
|
-
* 1. (optional) pre-flight simulate via provider
|
|
639
|
-
* 2. writeContract through the operator wallet
|
|
640
|
-
* 3. (optional) wait for the receipt and surface gasUsed / status
|
|
639
|
+
* Submit a `mintAndSwap` transaction (legacy v0.2 `/claim-and-swap`).
|
|
641
640
|
*
|
|
642
|
-
*
|
|
643
|
-
*
|
|
644
|
-
*
|
|
645
|
-
* need manual review because the tx may still land).
|
|
646
|
-
*
|
|
647
|
-
* @deprecated Since 0.3.0 — will be replaced by `prepareMint()` +
|
|
648
|
-
* `prepareBurn()` in the v1.4 sponsored-UserOp flow. The SC team
|
|
649
|
-
* still needs to finalize Relayer v2 ABI before the replacements
|
|
650
|
-
* can ship (blocker B1). Kept for v0.2.x consumers. Removed in 2.0.
|
|
641
|
+
* @deprecated Since 0.3.0 — replaced by `prepareMint` / `prepareBurn`
|
|
642
|
+
* in the v1.4 sponsored-UserOp flow. Kept for v0.2.x consumers;
|
|
643
|
+
* scheduled removal in 2.0.
|
|
651
644
|
*/
|
|
652
645
|
async submitMintAndSwap(params) {
|
|
653
646
|
if (this.simulateBeforeSubmit && this.provider) {
|
|
@@ -717,35 +710,24 @@ var RelayService = class {
|
|
|
717
710
|
}
|
|
718
711
|
}
|
|
719
712
|
// ==========================================================================
|
|
720
|
-
// v1.4 — Sponsored UserOp preparation (
|
|
721
|
-
// ==========================================================================
|
|
722
|
-
//
|
|
723
|
-
// These two methods build unsigned `PartialUserOperation` payloads for
|
|
724
|
-
// the Frontend to sign (via Privy) and submit to the Bundler. The
|
|
725
|
-
// Issuer Backend no longer broadcasts — that's the Frontend's job.
|
|
726
|
-
//
|
|
727
|
-
// Uses mocked Relayer v2 + PointToken ABIs from `@pafi-dev/core/contracts`.
|
|
728
|
-
// When SC delivers real ABIs, the imports swap but these method bodies
|
|
729
|
-
// stay the same (calldata encoder is ABI-driven).
|
|
713
|
+
// v1.4 — Sponsored UserOp preparation (sig-gated mint + burn)
|
|
730
714
|
// ==========================================================================
|
|
731
715
|
/**
|
|
732
|
-
* Build an unsigned UserOp for Scenario 1 (Mint)
|
|
716
|
+
* Build an unsigned UserOp for Scenario 1 (Mint) — sig-gated
|
|
717
|
+
* `PointToken.mint(to, amount, deadline, minterSig)`.
|
|
733
718
|
*
|
|
734
719
|
* Flow:
|
|
735
|
-
* 1.
|
|
736
|
-
*
|
|
737
|
-
*
|
|
738
|
-
*
|
|
739
|
-
*
|
|
740
|
-
*
|
|
741
|
-
*
|
|
742
|
-
*
|
|
743
|
-
*
|
|
720
|
+
* 1. Issuer backend signs `MintRequest(to=user, amount, nonce, deadline)`
|
|
721
|
+
* with its minter signer (HSM/KMS) → `minterSig`.
|
|
722
|
+
* 2. Encode `PointToken.mint(user, amount, deadline, minterSig)`.
|
|
723
|
+
* On-chain, `msg.sender` must equal `to` — satisfied by EIP-7702
|
|
724
|
+
* delegating the user EOA to BatchExecutor.
|
|
725
|
+
* 3. Optional PT fee transfer appended after mint (application-level
|
|
726
|
+
* fee recovery since Relayer v2 no longer exists).
|
|
727
|
+
* 4. Return `PartialUserOperation` ready for Bundler gas estimate +
|
|
728
|
+
* Paymaster sponsorship + user signature.
|
|
744
729
|
*/
|
|
745
|
-
prepareMint(params) {
|
|
746
|
-
if (!params.relayerAddress) {
|
|
747
|
-
throw new RelayError("ENCODE_FAILED", "prepareMint: relayerAddress required");
|
|
748
|
-
}
|
|
730
|
+
async prepareMint(params) {
|
|
749
731
|
if (!params.batchExecutorAddress) {
|
|
750
732
|
throw new RelayError(
|
|
751
733
|
"ENCODE_FAILED",
|
|
@@ -755,36 +737,79 @@ var RelayService = class {
|
|
|
755
737
|
if (!params.userAddress) {
|
|
756
738
|
throw new RelayError("ENCODE_FAILED", "prepareMint: userAddress required");
|
|
757
739
|
}
|
|
740
|
+
if (!params.pointTokenAddress) {
|
|
741
|
+
throw new RelayError(
|
|
742
|
+
"ENCODE_FAILED",
|
|
743
|
+
"prepareMint: pointTokenAddress required"
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
if (params.amount <= 0n) {
|
|
747
|
+
throw new RelayError("ENCODE_FAILED", "prepareMint: amount must be positive");
|
|
748
|
+
}
|
|
749
|
+
if (!params.issuerSignerWallet) {
|
|
750
|
+
throw new RelayError(
|
|
751
|
+
"ENCODE_FAILED",
|
|
752
|
+
"prepareMint: issuerSignerWallet required (for MintRequest EIP-712 signature)"
|
|
753
|
+
);
|
|
754
|
+
}
|
|
755
|
+
if (params.deadline <= 0n) {
|
|
756
|
+
throw new RelayError("ENCODE_FAILED", "prepareMint: deadline must be positive");
|
|
757
|
+
}
|
|
758
|
+
let minterSig;
|
|
759
|
+
try {
|
|
760
|
+
const sig = await signMintRequest2(
|
|
761
|
+
params.issuerSignerWallet,
|
|
762
|
+
params.domain,
|
|
763
|
+
{
|
|
764
|
+
to: params.userAddress,
|
|
765
|
+
amount: params.amount,
|
|
766
|
+
nonce: params.mintRequestNonce,
|
|
767
|
+
deadline: params.deadline
|
|
768
|
+
}
|
|
769
|
+
);
|
|
770
|
+
minterSig = sig.serialized;
|
|
771
|
+
} catch (err) {
|
|
772
|
+
throw new RelayError(
|
|
773
|
+
"ENCODE_FAILED",
|
|
774
|
+
`prepareMint: failed to sign MintRequest: ${errorMessage(err)}`,
|
|
775
|
+
err
|
|
776
|
+
);
|
|
777
|
+
}
|
|
758
778
|
let mintCallData;
|
|
759
779
|
try {
|
|
760
780
|
mintCallData = encodeFunctionData({
|
|
761
|
-
abi:
|
|
781
|
+
abi: POINT_TOKEN_V2_ABI,
|
|
762
782
|
functionName: "mint",
|
|
763
|
-
args: [params.
|
|
783
|
+
args: [params.userAddress, params.amount, params.deadline, minterSig]
|
|
764
784
|
});
|
|
765
785
|
} catch (err) {
|
|
766
786
|
throw new RelayError(
|
|
767
787
|
"ENCODE_FAILED",
|
|
768
|
-
`prepareMint: failed to encode
|
|
788
|
+
`prepareMint: failed to encode PointToken.mint: ${errorMessage(err)}`,
|
|
769
789
|
err
|
|
770
790
|
);
|
|
771
791
|
}
|
|
772
792
|
const operations = [
|
|
773
793
|
{
|
|
774
|
-
target: params.
|
|
794
|
+
target: params.pointTokenAddress,
|
|
775
795
|
value: 0n,
|
|
776
796
|
data: mintCallData
|
|
777
797
|
}
|
|
778
798
|
];
|
|
779
|
-
if (params.
|
|
799
|
+
if (params.feeAmount && params.feeAmount > 0n) {
|
|
800
|
+
if (!params.feeRecipient) {
|
|
801
|
+
throw new RelayError(
|
|
802
|
+
"ENCODE_FAILED",
|
|
803
|
+
"prepareMint: feeRecipient required when feeAmount > 0"
|
|
804
|
+
);
|
|
805
|
+
}
|
|
780
806
|
operations.push({
|
|
781
807
|
target: params.pointTokenAddress,
|
|
782
808
|
value: 0n,
|
|
783
809
|
data: encodeFunctionData({
|
|
784
|
-
abi:
|
|
785
|
-
functionName: "
|
|
786
|
-
|
|
787
|
-
args: [params.mintRequest.feeRecipient]
|
|
810
|
+
abi: erc20Abi,
|
|
811
|
+
functionName: "transfer",
|
|
812
|
+
args: [params.feeRecipient, params.feeAmount]
|
|
788
813
|
})
|
|
789
814
|
});
|
|
790
815
|
}
|
|
@@ -793,7 +818,7 @@ var RelayService = class {
|
|
|
793
818
|
nonce: params.aaNonce,
|
|
794
819
|
operations,
|
|
795
820
|
gasLimits: {
|
|
796
|
-
callGasLimit: params.callGasLimit ??
|
|
821
|
+
callGasLimit: params.callGasLimit ?? 300000n,
|
|
797
822
|
verificationGasLimit: params.verificationGasLimit ?? 150000n,
|
|
798
823
|
preVerificationGas: params.preVerificationGas ?? 50000n
|
|
799
824
|
}
|
|
@@ -803,13 +828,13 @@ var RelayService = class {
|
|
|
803
828
|
* Build an unsigned UserOp for Scenario 2 (Burn/Redeem).
|
|
804
829
|
*
|
|
805
830
|
* Two modes:
|
|
806
|
-
* - `mode: 'burn'` — direct `PointToken.burn(amount)`;
|
|
807
|
-
*
|
|
808
|
-
*
|
|
809
|
-
*
|
|
810
|
-
*
|
|
811
|
-
*
|
|
812
|
-
*
|
|
831
|
+
* - `mode: 'burn'` — direct `PointToken.burn(from, amount)`; only
|
|
832
|
+
* usable if the user is a whitelisted burner. Not the typical
|
|
833
|
+
* v1.4 path (users aren't burners); kept for admin/operator tools.
|
|
834
|
+
* - `mode: 'burnWithSig'` — `PointToken.burn(from, amount, deadline,
|
|
835
|
+
* burnerSig)`. Issuer signs `BurnRequest` off-chain; user submits
|
|
836
|
+
* via EIP-7702. `msg.sender == from` enforced on-chain. This is
|
|
837
|
+
* the user-initiated redeem path in v1.4.
|
|
813
838
|
*/
|
|
814
839
|
prepareBurn(params) {
|
|
815
840
|
if (!params.pointTokenAddress) {
|
|
@@ -824,19 +849,24 @@ var RelayService = class {
|
|
|
824
849
|
let burnCallData;
|
|
825
850
|
try {
|
|
826
851
|
if (params.mode === "burnWithSig") {
|
|
827
|
-
if (!params.
|
|
828
|
-
throw new Error("burnWithSig requires
|
|
852
|
+
if (!params.burnRequest || !params.burnerSignature) {
|
|
853
|
+
throw new Error("burnWithSig requires burnRequest + burnerSignature");
|
|
829
854
|
}
|
|
830
855
|
burnCallData = encodeFunctionData({
|
|
831
856
|
abi: POINT_TOKEN_V2_ABI,
|
|
832
|
-
functionName: "
|
|
833
|
-
args: [
|
|
857
|
+
functionName: "burn",
|
|
858
|
+
args: [
|
|
859
|
+
params.burnRequest.from,
|
|
860
|
+
params.burnRequest.amount,
|
|
861
|
+
params.burnRequest.deadline,
|
|
862
|
+
params.burnerSignature
|
|
863
|
+
]
|
|
834
864
|
});
|
|
835
865
|
} else {
|
|
836
866
|
burnCallData = encodeFunctionData({
|
|
837
867
|
abi: POINT_TOKEN_V2_ABI,
|
|
838
868
|
functionName: "burn",
|
|
839
|
-
args: [params.amount]
|
|
869
|
+
args: [params.userAddress, params.amount]
|
|
840
870
|
});
|
|
841
871
|
}
|
|
842
872
|
} catch (err) {
|
|
@@ -1757,8 +1787,9 @@ var IssuerApiHandlers = class {
|
|
|
1757
1787
|
|
|
1758
1788
|
// src/api/handlers/ptRedeemHandler.ts
|
|
1759
1789
|
import { getAddress as getAddress7 } from "viem";
|
|
1760
|
-
import {
|
|
1790
|
+
import { signBurnRequest, POINT_TOKEN_V2_ABI as POINT_TOKEN_V2_ABI2 } from "@pafi-dev/core";
|
|
1761
1791
|
var DEFAULT_REDEEM_LOCK_MS = 15 * 60 * 1e3;
|
|
1792
|
+
var DEFAULT_SIG_DEADLINE_SEC = 15 * 60;
|
|
1762
1793
|
var PTRedeemError = class extends Error {
|
|
1763
1794
|
constructor(code, message) {
|
|
1764
1795
|
super(message);
|
|
@@ -1770,11 +1801,14 @@ var PTRedeemError = class extends Error {
|
|
|
1770
1801
|
var PTRedeemHandler = class {
|
|
1771
1802
|
ledger;
|
|
1772
1803
|
relayService;
|
|
1804
|
+
provider;
|
|
1773
1805
|
pointTokenAddress;
|
|
1774
1806
|
batchExecutorAddress;
|
|
1775
1807
|
chainId;
|
|
1776
1808
|
domain;
|
|
1809
|
+
burnerSignerWallet;
|
|
1777
1810
|
redeemLockDurationMs;
|
|
1811
|
+
signatureDeadlineSeconds;
|
|
1778
1812
|
now;
|
|
1779
1813
|
constructor(config) {
|
|
1780
1814
|
if (!config.ledger.reservePendingCredit) {
|
|
@@ -1783,46 +1817,68 @@ var PTRedeemHandler = class {
|
|
|
1783
1817
|
"PTRedeemHandler requires a ledger that implements reservePendingCredit() (v0.3.0+)"
|
|
1784
1818
|
);
|
|
1785
1819
|
}
|
|
1820
|
+
if (!config.burnerSignerWallet) {
|
|
1821
|
+
throw new PTRedeemError(
|
|
1822
|
+
"SIGNING_FAILED",
|
|
1823
|
+
"PTRedeemHandler requires burnerSignerWallet (issuer burner signer)"
|
|
1824
|
+
);
|
|
1825
|
+
}
|
|
1786
1826
|
this.ledger = config.ledger;
|
|
1787
1827
|
this.relayService = config.relayService;
|
|
1828
|
+
this.provider = config.provider;
|
|
1788
1829
|
this.pointTokenAddress = getAddress7(config.pointTokenAddress);
|
|
1789
1830
|
this.batchExecutorAddress = getAddress7(config.batchExecutorAddress);
|
|
1790
1831
|
this.chainId = config.chainId;
|
|
1791
1832
|
this.domain = config.domain;
|
|
1833
|
+
this.burnerSignerWallet = config.burnerSignerWallet;
|
|
1792
1834
|
this.redeemLockDurationMs = config.redeemLockDurationMs ?? DEFAULT_REDEEM_LOCK_MS;
|
|
1835
|
+
this.signatureDeadlineSeconds = config.signatureDeadlineSeconds ?? DEFAULT_SIG_DEADLINE_SEC;
|
|
1793
1836
|
this.now = config.now ?? (() => Date.now());
|
|
1794
1837
|
}
|
|
1795
1838
|
async handle(request) {
|
|
1796
1839
|
if (request.amount <= 0n) {
|
|
1797
|
-
throw new PTRedeemError("
|
|
1840
|
+
throw new PTRedeemError("INVALID_AMOUNT", "redeem amount must be positive");
|
|
1798
1841
|
}
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1842
|
+
let burnNonce;
|
|
1843
|
+
try {
|
|
1844
|
+
burnNonce = await this.provider.readContract({
|
|
1845
|
+
address: this.pointTokenAddress,
|
|
1846
|
+
abi: POINT_TOKEN_V2_ABI2,
|
|
1847
|
+
functionName: "burnRequestNonces",
|
|
1848
|
+
args: [request.userAddress]
|
|
1849
|
+
});
|
|
1850
|
+
} catch (err) {
|
|
1807
1851
|
throw new PTRedeemError(
|
|
1808
|
-
"
|
|
1809
|
-
`
|
|
1852
|
+
"NONCE_READ_FAILED",
|
|
1853
|
+
`failed to read burnRequestNonces(${request.userAddress}): ${err instanceof Error ? err.message : String(err)}`
|
|
1810
1854
|
);
|
|
1811
1855
|
}
|
|
1812
|
-
const
|
|
1813
|
-
|
|
1814
|
-
name: this.domain.name,
|
|
1815
|
-
chainId: this.chainId,
|
|
1816
|
-
verifyingContract: this.domain.verifyingContract ?? this.pointTokenAddress
|
|
1817
|
-
},
|
|
1818
|
-
request.consent,
|
|
1819
|
-
request.consentSignature,
|
|
1820
|
-
request.userAddress
|
|
1856
|
+
const deadline = BigInt(
|
|
1857
|
+
Math.floor(this.now() / 1e3) + this.signatureDeadlineSeconds
|
|
1821
1858
|
);
|
|
1822
|
-
|
|
1859
|
+
const domain = {
|
|
1860
|
+
name: this.domain.name,
|
|
1861
|
+
chainId: this.chainId,
|
|
1862
|
+
verifyingContract: this.domain.verifyingContract ?? this.pointTokenAddress
|
|
1863
|
+
};
|
|
1864
|
+
const burnRequest = {
|
|
1865
|
+
from: request.userAddress,
|
|
1866
|
+
amount: request.amount,
|
|
1867
|
+
nonce: burnNonce,
|
|
1868
|
+
deadline
|
|
1869
|
+
};
|
|
1870
|
+
let burnerSignature;
|
|
1871
|
+
try {
|
|
1872
|
+
const sig = await signBurnRequest(
|
|
1873
|
+
this.burnerSignerWallet,
|
|
1874
|
+
domain,
|
|
1875
|
+
burnRequest
|
|
1876
|
+
);
|
|
1877
|
+
burnerSignature = sig.serialized;
|
|
1878
|
+
} catch (err) {
|
|
1823
1879
|
throw new PTRedeemError(
|
|
1824
|
-
"
|
|
1825
|
-
`
|
|
1880
|
+
"SIGNING_FAILED",
|
|
1881
|
+
`failed to sign BurnRequest: ${err instanceof Error ? err.message : String(err)}`
|
|
1826
1882
|
);
|
|
1827
1883
|
}
|
|
1828
1884
|
const lockId = await this.ledger.reservePendingCredit(
|
|
@@ -1837,29 +1893,17 @@ var PTRedeemHandler = class {
|
|
|
1837
1893
|
aaNonce: request.aaNonce,
|
|
1838
1894
|
pointTokenAddress: this.pointTokenAddress,
|
|
1839
1895
|
batchExecutorAddress: this.batchExecutorAddress,
|
|
1840
|
-
|
|
1841
|
-
|
|
1896
|
+
burnRequest,
|
|
1897
|
+
burnerSignature
|
|
1842
1898
|
});
|
|
1843
1899
|
return {
|
|
1844
1900
|
lockId,
|
|
1845
1901
|
userOp,
|
|
1846
|
-
expiresInSeconds: Math.floor(this.redeemLockDurationMs / 1e3)
|
|
1902
|
+
expiresInSeconds: Math.floor(this.redeemLockDurationMs / 1e3),
|
|
1903
|
+
signatureDeadline: deadline
|
|
1847
1904
|
};
|
|
1848
1905
|
}
|
|
1849
1906
|
};
|
|
1850
|
-
function parseSigStruct(serialized) {
|
|
1851
|
-
const raw = serialized.slice(2);
|
|
1852
|
-
if (raw.length !== 130) {
|
|
1853
|
-
throw new PTRedeemError(
|
|
1854
|
-
"INVALID_CONSENT",
|
|
1855
|
-
`signature must be 65 bytes, got ${raw.length / 2}`
|
|
1856
|
-
);
|
|
1857
|
-
}
|
|
1858
|
-
const r = `0x${raw.slice(0, 64)}`;
|
|
1859
|
-
const s = `0x${raw.slice(64, 128)}`;
|
|
1860
|
-
const v = parseInt(raw.slice(128, 130), 16);
|
|
1861
|
-
return { v, r, s };
|
|
1862
|
-
}
|
|
1863
1907
|
|
|
1864
1908
|
// src/api/handlers/topUpRedemptionHandler.ts
|
|
1865
1909
|
import { getAddress as getAddress8 } from "viem";
|
|
@@ -1905,24 +1949,10 @@ var TopUpRedemptionHandler = class {
|
|
|
1905
1949
|
shortfall
|
|
1906
1950
|
};
|
|
1907
1951
|
}
|
|
1908
|
-
if (request.redeemRequest.consent.amount < shortfall) {
|
|
1909
|
-
throw new TopUpRedemptionError(
|
|
1910
|
-
"CONSENT_AMOUNT_TOO_LOW",
|
|
1911
|
-
`consent.amount (${request.redeemRequest.consent.amount}) must cover shortfall (${shortfall})`
|
|
1912
|
-
);
|
|
1913
|
-
}
|
|
1914
|
-
if (request.redeemRequest.consent.amount !== shortfall) {
|
|
1915
|
-
throw new TopUpRedemptionError(
|
|
1916
|
-
"CONSENT_AMOUNT_TOO_LOW",
|
|
1917
|
-
`consent.amount (${request.redeemRequest.consent.amount}) must equal shortfall (${shortfall}) exactly \u2014 re-sign with correct amount`
|
|
1918
|
-
);
|
|
1919
|
-
}
|
|
1920
1952
|
const redeem = await this.ptRedeemHandler.handle({
|
|
1921
1953
|
userAddress: request.userAddress,
|
|
1922
1954
|
amount: shortfall,
|
|
1923
|
-
|
|
1924
|
-
consentSignature: request.redeemRequest.consentSignature,
|
|
1925
|
-
aaNonce: request.redeemRequest.aaNonce
|
|
1955
|
+
aaNonce: request.aaNonce
|
|
1926
1956
|
});
|
|
1927
1957
|
return {
|
|
1928
1958
|
action: "TOP_UP_STARTED",
|