@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.cjs
CHANGED
|
@@ -677,21 +677,11 @@ var RelayService = class {
|
|
|
677
677
|
}
|
|
678
678
|
}
|
|
679
679
|
/**
|
|
680
|
-
* Submit a `mintAndSwap` transaction.
|
|
680
|
+
* Submit a `mintAndSwap` transaction (legacy v0.2 `/claim-and-swap`).
|
|
681
681
|
*
|
|
682
|
-
*
|
|
683
|
-
*
|
|
684
|
-
*
|
|
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.
|
|
682
|
+
* @deprecated Since 0.3.0 — replaced by `prepareMint` / `prepareBurn`
|
|
683
|
+
* in the v1.4 sponsored-UserOp flow. Kept for v0.2.x consumers;
|
|
684
|
+
* scheduled removal in 2.0.
|
|
695
685
|
*/
|
|
696
686
|
async submitMintAndSwap(params) {
|
|
697
687
|
if (this.simulateBeforeSubmit && this.provider) {
|
|
@@ -761,35 +751,24 @@ var RelayService = class {
|
|
|
761
751
|
}
|
|
762
752
|
}
|
|
763
753
|
// ==========================================================================
|
|
764
|
-
// v1.4 — Sponsored UserOp preparation (
|
|
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).
|
|
754
|
+
// v1.4 — Sponsored UserOp preparation (sig-gated mint + burn)
|
|
774
755
|
// ==========================================================================
|
|
775
756
|
/**
|
|
776
|
-
* Build an unsigned UserOp for Scenario 1 (Mint)
|
|
757
|
+
* Build an unsigned UserOp for Scenario 1 (Mint) — sig-gated
|
|
758
|
+
* `PointToken.mint(to, amount, deadline, minterSig)`.
|
|
777
759
|
*
|
|
778
760
|
* Flow:
|
|
779
|
-
* 1.
|
|
780
|
-
*
|
|
781
|
-
*
|
|
782
|
-
*
|
|
783
|
-
*
|
|
784
|
-
*
|
|
785
|
-
*
|
|
786
|
-
*
|
|
787
|
-
*
|
|
761
|
+
* 1. Issuer backend signs `MintRequest(to=user, amount, nonce, deadline)`
|
|
762
|
+
* with its minter signer (HSM/KMS) → `minterSig`.
|
|
763
|
+
* 2. Encode `PointToken.mint(user, amount, deadline, minterSig)`.
|
|
764
|
+
* On-chain, `msg.sender` must equal `to` — satisfied by EIP-7702
|
|
765
|
+
* delegating the user EOA to BatchExecutor.
|
|
766
|
+
* 3. Optional PT fee transfer appended after mint (application-level
|
|
767
|
+
* fee recovery since Relayer v2 no longer exists).
|
|
768
|
+
* 4. Return `PartialUserOperation` ready for Bundler gas estimate +
|
|
769
|
+
* Paymaster sponsorship + user signature.
|
|
788
770
|
*/
|
|
789
|
-
prepareMint(params) {
|
|
790
|
-
if (!params.relayerAddress) {
|
|
791
|
-
throw new RelayError("ENCODE_FAILED", "prepareMint: relayerAddress required");
|
|
792
|
-
}
|
|
771
|
+
async prepareMint(params) {
|
|
793
772
|
if (!params.batchExecutorAddress) {
|
|
794
773
|
throw new RelayError(
|
|
795
774
|
"ENCODE_FAILED",
|
|
@@ -799,36 +778,79 @@ var RelayService = class {
|
|
|
799
778
|
if (!params.userAddress) {
|
|
800
779
|
throw new RelayError("ENCODE_FAILED", "prepareMint: userAddress required");
|
|
801
780
|
}
|
|
781
|
+
if (!params.pointTokenAddress) {
|
|
782
|
+
throw new RelayError(
|
|
783
|
+
"ENCODE_FAILED",
|
|
784
|
+
"prepareMint: pointTokenAddress required"
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
if (params.amount <= 0n) {
|
|
788
|
+
throw new RelayError("ENCODE_FAILED", "prepareMint: amount must be positive");
|
|
789
|
+
}
|
|
790
|
+
if (!params.issuerSignerWallet) {
|
|
791
|
+
throw new RelayError(
|
|
792
|
+
"ENCODE_FAILED",
|
|
793
|
+
"prepareMint: issuerSignerWallet required (for MintRequest EIP-712 signature)"
|
|
794
|
+
);
|
|
795
|
+
}
|
|
796
|
+
if (params.deadline <= 0n) {
|
|
797
|
+
throw new RelayError("ENCODE_FAILED", "prepareMint: deadline must be positive");
|
|
798
|
+
}
|
|
799
|
+
let minterSig;
|
|
800
|
+
try {
|
|
801
|
+
const sig = await (0, import_core3.signMintRequest)(
|
|
802
|
+
params.issuerSignerWallet,
|
|
803
|
+
params.domain,
|
|
804
|
+
{
|
|
805
|
+
to: params.userAddress,
|
|
806
|
+
amount: params.amount,
|
|
807
|
+
nonce: params.mintRequestNonce,
|
|
808
|
+
deadline: params.deadline
|
|
809
|
+
}
|
|
810
|
+
);
|
|
811
|
+
minterSig = sig.serialized;
|
|
812
|
+
} catch (err) {
|
|
813
|
+
throw new RelayError(
|
|
814
|
+
"ENCODE_FAILED",
|
|
815
|
+
`prepareMint: failed to sign MintRequest: ${errorMessage(err)}`,
|
|
816
|
+
err
|
|
817
|
+
);
|
|
818
|
+
}
|
|
802
819
|
let mintCallData;
|
|
803
820
|
try {
|
|
804
821
|
mintCallData = (0, import_viem5.encodeFunctionData)({
|
|
805
|
-
abi: import_core3.
|
|
822
|
+
abi: import_core3.POINT_TOKEN_V2_ABI,
|
|
806
823
|
functionName: "mint",
|
|
807
|
-
args: [params.
|
|
824
|
+
args: [params.userAddress, params.amount, params.deadline, minterSig]
|
|
808
825
|
});
|
|
809
826
|
} catch (err) {
|
|
810
827
|
throw new RelayError(
|
|
811
828
|
"ENCODE_FAILED",
|
|
812
|
-
`prepareMint: failed to encode
|
|
829
|
+
`prepareMint: failed to encode PointToken.mint: ${errorMessage(err)}`,
|
|
813
830
|
err
|
|
814
831
|
);
|
|
815
832
|
}
|
|
816
833
|
const operations = [
|
|
817
834
|
{
|
|
818
|
-
target: params.
|
|
835
|
+
target: params.pointTokenAddress,
|
|
819
836
|
value: 0n,
|
|
820
837
|
data: mintCallData
|
|
821
838
|
}
|
|
822
839
|
];
|
|
823
|
-
if (params.
|
|
840
|
+
if (params.feeAmount && params.feeAmount > 0n) {
|
|
841
|
+
if (!params.feeRecipient) {
|
|
842
|
+
throw new RelayError(
|
|
843
|
+
"ENCODE_FAILED",
|
|
844
|
+
"prepareMint: feeRecipient required when feeAmount > 0"
|
|
845
|
+
);
|
|
846
|
+
}
|
|
824
847
|
operations.push({
|
|
825
848
|
target: params.pointTokenAddress,
|
|
826
849
|
value: 0n,
|
|
827
850
|
data: (0, import_viem5.encodeFunctionData)({
|
|
828
|
-
abi:
|
|
829
|
-
functionName: "
|
|
830
|
-
|
|
831
|
-
args: [params.mintRequest.feeRecipient]
|
|
851
|
+
abi: import_viem5.erc20Abi,
|
|
852
|
+
functionName: "transfer",
|
|
853
|
+
args: [params.feeRecipient, params.feeAmount]
|
|
832
854
|
})
|
|
833
855
|
});
|
|
834
856
|
}
|
|
@@ -837,7 +859,7 @@ var RelayService = class {
|
|
|
837
859
|
nonce: params.aaNonce,
|
|
838
860
|
operations,
|
|
839
861
|
gasLimits: {
|
|
840
|
-
callGasLimit: params.callGasLimit ??
|
|
862
|
+
callGasLimit: params.callGasLimit ?? 300000n,
|
|
841
863
|
verificationGasLimit: params.verificationGasLimit ?? 150000n,
|
|
842
864
|
preVerificationGas: params.preVerificationGas ?? 50000n
|
|
843
865
|
}
|
|
@@ -847,13 +869,13 @@ var RelayService = class {
|
|
|
847
869
|
* Build an unsigned UserOp for Scenario 2 (Burn/Redeem).
|
|
848
870
|
*
|
|
849
871
|
* Two modes:
|
|
850
|
-
* - `mode: 'burn'` — direct `PointToken.burn(amount)`;
|
|
851
|
-
*
|
|
852
|
-
*
|
|
853
|
-
*
|
|
854
|
-
*
|
|
855
|
-
*
|
|
856
|
-
*
|
|
872
|
+
* - `mode: 'burn'` — direct `PointToken.burn(from, amount)`; only
|
|
873
|
+
* usable if the user is a whitelisted burner. Not the typical
|
|
874
|
+
* v1.4 path (users aren't burners); kept for admin/operator tools.
|
|
875
|
+
* - `mode: 'burnWithSig'` — `PointToken.burn(from, amount, deadline,
|
|
876
|
+
* burnerSig)`. Issuer signs `BurnRequest` off-chain; user submits
|
|
877
|
+
* via EIP-7702. `msg.sender == from` enforced on-chain. This is
|
|
878
|
+
* the user-initiated redeem path in v1.4.
|
|
857
879
|
*/
|
|
858
880
|
prepareBurn(params) {
|
|
859
881
|
if (!params.pointTokenAddress) {
|
|
@@ -868,19 +890,24 @@ var RelayService = class {
|
|
|
868
890
|
let burnCallData;
|
|
869
891
|
try {
|
|
870
892
|
if (params.mode === "burnWithSig") {
|
|
871
|
-
if (!params.
|
|
872
|
-
throw new Error("burnWithSig requires
|
|
893
|
+
if (!params.burnRequest || !params.burnerSignature) {
|
|
894
|
+
throw new Error("burnWithSig requires burnRequest + burnerSignature");
|
|
873
895
|
}
|
|
874
896
|
burnCallData = (0, import_viem5.encodeFunctionData)({
|
|
875
897
|
abi: import_core3.POINT_TOKEN_V2_ABI,
|
|
876
|
-
functionName: "
|
|
877
|
-
args: [
|
|
898
|
+
functionName: "burn",
|
|
899
|
+
args: [
|
|
900
|
+
params.burnRequest.from,
|
|
901
|
+
params.burnRequest.amount,
|
|
902
|
+
params.burnRequest.deadline,
|
|
903
|
+
params.burnerSignature
|
|
904
|
+
]
|
|
878
905
|
});
|
|
879
906
|
} else {
|
|
880
907
|
burnCallData = (0, import_viem5.encodeFunctionData)({
|
|
881
908
|
abi: import_core3.POINT_TOKEN_V2_ABI,
|
|
882
909
|
functionName: "burn",
|
|
883
|
-
args: [params.amount]
|
|
910
|
+
args: [params.userAddress, params.amount]
|
|
884
911
|
});
|
|
885
912
|
}
|
|
886
913
|
} catch (err) {
|
|
@@ -1793,6 +1820,7 @@ var IssuerApiHandlers = class {
|
|
|
1793
1820
|
var import_viem9 = require("viem");
|
|
1794
1821
|
var import_core6 = require("@pafi-dev/core");
|
|
1795
1822
|
var DEFAULT_REDEEM_LOCK_MS = 15 * 60 * 1e3;
|
|
1823
|
+
var DEFAULT_SIG_DEADLINE_SEC = 15 * 60;
|
|
1796
1824
|
var PTRedeemError = class extends Error {
|
|
1797
1825
|
constructor(code, message) {
|
|
1798
1826
|
super(message);
|
|
@@ -1804,11 +1832,14 @@ var PTRedeemError = class extends Error {
|
|
|
1804
1832
|
var PTRedeemHandler = class {
|
|
1805
1833
|
ledger;
|
|
1806
1834
|
relayService;
|
|
1835
|
+
provider;
|
|
1807
1836
|
pointTokenAddress;
|
|
1808
1837
|
batchExecutorAddress;
|
|
1809
1838
|
chainId;
|
|
1810
1839
|
domain;
|
|
1840
|
+
burnerSignerWallet;
|
|
1811
1841
|
redeemLockDurationMs;
|
|
1842
|
+
signatureDeadlineSeconds;
|
|
1812
1843
|
now;
|
|
1813
1844
|
constructor(config) {
|
|
1814
1845
|
if (!config.ledger.reservePendingCredit) {
|
|
@@ -1817,46 +1848,68 @@ var PTRedeemHandler = class {
|
|
|
1817
1848
|
"PTRedeemHandler requires a ledger that implements reservePendingCredit() (v0.3.0+)"
|
|
1818
1849
|
);
|
|
1819
1850
|
}
|
|
1851
|
+
if (!config.burnerSignerWallet) {
|
|
1852
|
+
throw new PTRedeemError(
|
|
1853
|
+
"SIGNING_FAILED",
|
|
1854
|
+
"PTRedeemHandler requires burnerSignerWallet (issuer burner signer)"
|
|
1855
|
+
);
|
|
1856
|
+
}
|
|
1820
1857
|
this.ledger = config.ledger;
|
|
1821
1858
|
this.relayService = config.relayService;
|
|
1859
|
+
this.provider = config.provider;
|
|
1822
1860
|
this.pointTokenAddress = (0, import_viem9.getAddress)(config.pointTokenAddress);
|
|
1823
1861
|
this.batchExecutorAddress = (0, import_viem9.getAddress)(config.batchExecutorAddress);
|
|
1824
1862
|
this.chainId = config.chainId;
|
|
1825
1863
|
this.domain = config.domain;
|
|
1864
|
+
this.burnerSignerWallet = config.burnerSignerWallet;
|
|
1826
1865
|
this.redeemLockDurationMs = config.redeemLockDurationMs ?? DEFAULT_REDEEM_LOCK_MS;
|
|
1866
|
+
this.signatureDeadlineSeconds = config.signatureDeadlineSeconds ?? DEFAULT_SIG_DEADLINE_SEC;
|
|
1827
1867
|
this.now = config.now ?? (() => Date.now());
|
|
1828
1868
|
}
|
|
1829
1869
|
async handle(request) {
|
|
1830
1870
|
if (request.amount <= 0n) {
|
|
1831
|
-
throw new PTRedeemError("
|
|
1871
|
+
throw new PTRedeemError("INVALID_AMOUNT", "redeem amount must be positive");
|
|
1832
1872
|
}
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1873
|
+
let burnNonce;
|
|
1874
|
+
try {
|
|
1875
|
+
burnNonce = await this.provider.readContract({
|
|
1876
|
+
address: this.pointTokenAddress,
|
|
1877
|
+
abi: import_core6.POINT_TOKEN_V2_ABI,
|
|
1878
|
+
functionName: "burnRequestNonces",
|
|
1879
|
+
args: [request.userAddress]
|
|
1880
|
+
});
|
|
1881
|
+
} catch (err) {
|
|
1841
1882
|
throw new PTRedeemError(
|
|
1842
|
-
"
|
|
1843
|
-
`
|
|
1883
|
+
"NONCE_READ_FAILED",
|
|
1884
|
+
`failed to read burnRequestNonces(${request.userAddress}): ${err instanceof Error ? err.message : String(err)}`
|
|
1844
1885
|
);
|
|
1845
1886
|
}
|
|
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
|
|
1887
|
+
const deadline = BigInt(
|
|
1888
|
+
Math.floor(this.now() / 1e3) + this.signatureDeadlineSeconds
|
|
1855
1889
|
);
|
|
1856
|
-
|
|
1890
|
+
const domain = {
|
|
1891
|
+
name: this.domain.name,
|
|
1892
|
+
chainId: this.chainId,
|
|
1893
|
+
verifyingContract: this.domain.verifyingContract ?? this.pointTokenAddress
|
|
1894
|
+
};
|
|
1895
|
+
const burnRequest = {
|
|
1896
|
+
from: request.userAddress,
|
|
1897
|
+
amount: request.amount,
|
|
1898
|
+
nonce: burnNonce,
|
|
1899
|
+
deadline
|
|
1900
|
+
};
|
|
1901
|
+
let burnerSignature;
|
|
1902
|
+
try {
|
|
1903
|
+
const sig = await (0, import_core6.signBurnRequest)(
|
|
1904
|
+
this.burnerSignerWallet,
|
|
1905
|
+
domain,
|
|
1906
|
+
burnRequest
|
|
1907
|
+
);
|
|
1908
|
+
burnerSignature = sig.serialized;
|
|
1909
|
+
} catch (err) {
|
|
1857
1910
|
throw new PTRedeemError(
|
|
1858
|
-
"
|
|
1859
|
-
`
|
|
1911
|
+
"SIGNING_FAILED",
|
|
1912
|
+
`failed to sign BurnRequest: ${err instanceof Error ? err.message : String(err)}`
|
|
1860
1913
|
);
|
|
1861
1914
|
}
|
|
1862
1915
|
const lockId = await this.ledger.reservePendingCredit(
|
|
@@ -1871,29 +1924,17 @@ var PTRedeemHandler = class {
|
|
|
1871
1924
|
aaNonce: request.aaNonce,
|
|
1872
1925
|
pointTokenAddress: this.pointTokenAddress,
|
|
1873
1926
|
batchExecutorAddress: this.batchExecutorAddress,
|
|
1874
|
-
|
|
1875
|
-
|
|
1927
|
+
burnRequest,
|
|
1928
|
+
burnerSignature
|
|
1876
1929
|
});
|
|
1877
1930
|
return {
|
|
1878
1931
|
lockId,
|
|
1879
1932
|
userOp,
|
|
1880
|
-
expiresInSeconds: Math.floor(this.redeemLockDurationMs / 1e3)
|
|
1933
|
+
expiresInSeconds: Math.floor(this.redeemLockDurationMs / 1e3),
|
|
1934
|
+
signatureDeadline: deadline
|
|
1881
1935
|
};
|
|
1882
1936
|
}
|
|
1883
1937
|
};
|
|
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
1938
|
|
|
1898
1939
|
// src/api/handlers/topUpRedemptionHandler.ts
|
|
1899
1940
|
var import_viem10 = require("viem");
|
|
@@ -1939,24 +1980,10 @@ var TopUpRedemptionHandler = class {
|
|
|
1939
1980
|
shortfall
|
|
1940
1981
|
};
|
|
1941
1982
|
}
|
|
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
1983
|
const redeem = await this.ptRedeemHandler.handle({
|
|
1955
1984
|
userAddress: request.userAddress,
|
|
1956
1985
|
amount: shortfall,
|
|
1957
|
-
|
|
1958
|
-
consentSignature: request.redeemRequest.consentSignature,
|
|
1959
|
-
aaNonce: request.redeemRequest.aaNonce
|
|
1986
|
+
aaNonce: request.aaNonce
|
|
1960
1987
|
});
|
|
1961
1988
|
return {
|
|
1962
1989
|
action: "TOP_UP_STARTED",
|