@pafi-dev/issuer 0.3.0-beta.3 → 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 +118 -115
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +116 -98
- package/dist/index.d.ts +116 -98
- package/dist/index.js +121 -117
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -592,7 +592,8 @@ import {
|
|
|
592
592
|
simulateMintAndSwap as coreSimulateMintAndSwap,
|
|
593
593
|
SimulationError,
|
|
594
594
|
POINT_TOKEN_V2_ABI,
|
|
595
|
-
buildPartialUserOperation
|
|
595
|
+
buildPartialUserOperation,
|
|
596
|
+
signMintRequest as signMintRequest2
|
|
596
597
|
} from "@pafi-dev/core";
|
|
597
598
|
var DEFAULT_CONFIRMATION_TIMEOUT_MS = 6e4;
|
|
598
599
|
var RelayService = class {
|
|
@@ -635,21 +636,11 @@ var RelayService = class {
|
|
|
635
636
|
}
|
|
636
637
|
}
|
|
637
638
|
/**
|
|
638
|
-
* Submit a `mintAndSwap` transaction.
|
|
639
|
+
* Submit a `mintAndSwap` transaction (legacy v0.2 `/claim-and-swap`).
|
|
639
640
|
*
|
|
640
|
-
*
|
|
641
|
-
*
|
|
642
|
-
*
|
|
643
|
-
*
|
|
644
|
-
* Throws a typed `RelayError` on any failure so the MintingGateway can
|
|
645
|
-
* decide whether to release the ledger lock (`SUBMIT_FAILED` and
|
|
646
|
-
* `SIMULATION_FAILED` are safe to release; `TX_REVERTED` and `TIMEOUT`
|
|
647
|
-
* need manual review because the tx may still land).
|
|
648
|
-
*
|
|
649
|
-
* @deprecated Since 0.3.0 — will be replaced by `prepareMint()` +
|
|
650
|
-
* `prepareBurn()` in the v1.4 sponsored-UserOp flow. The SC team
|
|
651
|
-
* still needs to finalize Relayer v2 ABI before the replacements
|
|
652
|
-
* 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.
|
|
653
644
|
*/
|
|
654
645
|
async submitMintAndSwap(params) {
|
|
655
646
|
if (this.simulateBeforeSubmit && this.provider) {
|
|
@@ -719,45 +710,24 @@ var RelayService = class {
|
|
|
719
710
|
}
|
|
720
711
|
}
|
|
721
712
|
// ==========================================================================
|
|
722
|
-
// v1.4 — Sponsored UserOp preparation (
|
|
723
|
-
// ==========================================================================
|
|
724
|
-
//
|
|
725
|
-
// These two methods build unsigned `PartialUserOperation` payloads for
|
|
726
|
-
// the Frontend to sign (via Privy) and submit to the Bundler. The
|
|
727
|
-
// Issuer Backend no longer broadcasts — that's the Frontend's job.
|
|
728
|
-
//
|
|
729
|
-
// Uses mocked Relayer v2 + PointToken ABIs from `@pafi-dev/core/contracts`.
|
|
730
|
-
// When SC delivers real ABIs, the imports swap but these method bodies
|
|
731
|
-
// stay the same (calldata encoder is ABI-driven).
|
|
713
|
+
// v1.4 — Sponsored UserOp preparation (sig-gated mint + burn)
|
|
732
714
|
// ==========================================================================
|
|
733
715
|
/**
|
|
734
|
-
* 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)`.
|
|
735
718
|
*
|
|
736
719
|
* Flow:
|
|
737
|
-
* 1.
|
|
738
|
-
*
|
|
739
|
-
*
|
|
740
|
-
*
|
|
741
|
-
*
|
|
742
|
-
*
|
|
743
|
-
*
|
|
744
|
-
*
|
|
745
|
-
*
|
|
746
|
-
*/
|
|
747
|
-
/**
|
|
748
|
-
* Build an unsigned UserOp for Scenario 1 (Mint) — direct
|
|
749
|
-
* `PointToken.mint(amount)` flow (v1.4 post-Relayer-removal).
|
|
750
|
-
*
|
|
751
|
-
* `msg.sender` inside the batch = user EOA via EIP-7702 delegation.
|
|
752
|
-
* `PointToken.mint` checks the caller is on its `minters` allowlist
|
|
753
|
-
* (gg56 BE pre-validates this off-chain to avoid wasted gas).
|
|
754
|
-
*
|
|
755
|
-
* Optional PT fee transfer is appended after the mint when
|
|
756
|
-
* `feeAmount > 0` — this is application-level fee recovery (no
|
|
757
|
-
* Relayer doing it for us). The user must end up with `amount - fee`
|
|
758
|
-
* net PT after the batch executes.
|
|
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.
|
|
759
729
|
*/
|
|
760
|
-
prepareMint(params) {
|
|
730
|
+
async prepareMint(params) {
|
|
761
731
|
if (!params.batchExecutorAddress) {
|
|
762
732
|
throw new RelayError(
|
|
763
733
|
"ENCODE_FAILED",
|
|
@@ -776,12 +746,41 @@ var RelayService = class {
|
|
|
776
746
|
if (params.amount <= 0n) {
|
|
777
747
|
throw new RelayError("ENCODE_FAILED", "prepareMint: amount must be positive");
|
|
778
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
|
+
}
|
|
779
778
|
let mintCallData;
|
|
780
779
|
try {
|
|
781
780
|
mintCallData = encodeFunctionData({
|
|
782
781
|
abi: POINT_TOKEN_V2_ABI,
|
|
783
782
|
functionName: "mint",
|
|
784
|
-
args: [params.amount]
|
|
783
|
+
args: [params.userAddress, params.amount, params.deadline, minterSig]
|
|
785
784
|
});
|
|
786
785
|
} catch (err) {
|
|
787
786
|
throw new RelayError(
|
|
@@ -829,13 +828,13 @@ var RelayService = class {
|
|
|
829
828
|
* Build an unsigned UserOp for Scenario 2 (Burn/Redeem).
|
|
830
829
|
*
|
|
831
830
|
* Two modes:
|
|
832
|
-
* - `mode: 'burn'` — direct `PointToken.burn(amount)`;
|
|
833
|
-
*
|
|
834
|
-
*
|
|
835
|
-
*
|
|
836
|
-
*
|
|
837
|
-
*
|
|
838
|
-
*
|
|
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.
|
|
839
838
|
*/
|
|
840
839
|
prepareBurn(params) {
|
|
841
840
|
if (!params.pointTokenAddress) {
|
|
@@ -850,19 +849,24 @@ var RelayService = class {
|
|
|
850
849
|
let burnCallData;
|
|
851
850
|
try {
|
|
852
851
|
if (params.mode === "burnWithSig") {
|
|
853
|
-
if (!params.
|
|
854
|
-
throw new Error("burnWithSig requires
|
|
852
|
+
if (!params.burnRequest || !params.burnerSignature) {
|
|
853
|
+
throw new Error("burnWithSig requires burnRequest + burnerSignature");
|
|
855
854
|
}
|
|
856
855
|
burnCallData = encodeFunctionData({
|
|
857
856
|
abi: POINT_TOKEN_V2_ABI,
|
|
858
|
-
functionName: "
|
|
859
|
-
args: [
|
|
857
|
+
functionName: "burn",
|
|
858
|
+
args: [
|
|
859
|
+
params.burnRequest.from,
|
|
860
|
+
params.burnRequest.amount,
|
|
861
|
+
params.burnRequest.deadline,
|
|
862
|
+
params.burnerSignature
|
|
863
|
+
]
|
|
860
864
|
});
|
|
861
865
|
} else {
|
|
862
866
|
burnCallData = encodeFunctionData({
|
|
863
867
|
abi: POINT_TOKEN_V2_ABI,
|
|
864
868
|
functionName: "burn",
|
|
865
|
-
args: [params.amount]
|
|
869
|
+
args: [params.userAddress, params.amount]
|
|
866
870
|
});
|
|
867
871
|
}
|
|
868
872
|
} catch (err) {
|
|
@@ -1783,8 +1787,9 @@ var IssuerApiHandlers = class {
|
|
|
1783
1787
|
|
|
1784
1788
|
// src/api/handlers/ptRedeemHandler.ts
|
|
1785
1789
|
import { getAddress as getAddress7 } from "viem";
|
|
1786
|
-
import {
|
|
1790
|
+
import { signBurnRequest, POINT_TOKEN_V2_ABI as POINT_TOKEN_V2_ABI2 } from "@pafi-dev/core";
|
|
1787
1791
|
var DEFAULT_REDEEM_LOCK_MS = 15 * 60 * 1e3;
|
|
1792
|
+
var DEFAULT_SIG_DEADLINE_SEC = 15 * 60;
|
|
1788
1793
|
var PTRedeemError = class extends Error {
|
|
1789
1794
|
constructor(code, message) {
|
|
1790
1795
|
super(message);
|
|
@@ -1796,11 +1801,14 @@ var PTRedeemError = class extends Error {
|
|
|
1796
1801
|
var PTRedeemHandler = class {
|
|
1797
1802
|
ledger;
|
|
1798
1803
|
relayService;
|
|
1804
|
+
provider;
|
|
1799
1805
|
pointTokenAddress;
|
|
1800
1806
|
batchExecutorAddress;
|
|
1801
1807
|
chainId;
|
|
1802
1808
|
domain;
|
|
1809
|
+
burnerSignerWallet;
|
|
1803
1810
|
redeemLockDurationMs;
|
|
1811
|
+
signatureDeadlineSeconds;
|
|
1804
1812
|
now;
|
|
1805
1813
|
constructor(config) {
|
|
1806
1814
|
if (!config.ledger.reservePendingCredit) {
|
|
@@ -1809,46 +1817,68 @@ var PTRedeemHandler = class {
|
|
|
1809
1817
|
"PTRedeemHandler requires a ledger that implements reservePendingCredit() (v0.3.0+)"
|
|
1810
1818
|
);
|
|
1811
1819
|
}
|
|
1820
|
+
if (!config.burnerSignerWallet) {
|
|
1821
|
+
throw new PTRedeemError(
|
|
1822
|
+
"SIGNING_FAILED",
|
|
1823
|
+
"PTRedeemHandler requires burnerSignerWallet (issuer burner signer)"
|
|
1824
|
+
);
|
|
1825
|
+
}
|
|
1812
1826
|
this.ledger = config.ledger;
|
|
1813
1827
|
this.relayService = config.relayService;
|
|
1828
|
+
this.provider = config.provider;
|
|
1814
1829
|
this.pointTokenAddress = getAddress7(config.pointTokenAddress);
|
|
1815
1830
|
this.batchExecutorAddress = getAddress7(config.batchExecutorAddress);
|
|
1816
1831
|
this.chainId = config.chainId;
|
|
1817
1832
|
this.domain = config.domain;
|
|
1833
|
+
this.burnerSignerWallet = config.burnerSignerWallet;
|
|
1818
1834
|
this.redeemLockDurationMs = config.redeemLockDurationMs ?? DEFAULT_REDEEM_LOCK_MS;
|
|
1835
|
+
this.signatureDeadlineSeconds = config.signatureDeadlineSeconds ?? DEFAULT_SIG_DEADLINE_SEC;
|
|
1819
1836
|
this.now = config.now ?? (() => Date.now());
|
|
1820
1837
|
}
|
|
1821
1838
|
async handle(request) {
|
|
1822
1839
|
if (request.amount <= 0n) {
|
|
1823
|
-
throw new PTRedeemError("
|
|
1840
|
+
throw new PTRedeemError("INVALID_AMOUNT", "redeem amount must be positive");
|
|
1824
1841
|
}
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
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) {
|
|
1833
1851
|
throw new PTRedeemError(
|
|
1834
|
-
"
|
|
1835
|
-
`
|
|
1852
|
+
"NONCE_READ_FAILED",
|
|
1853
|
+
`failed to read burnRequestNonces(${request.userAddress}): ${err instanceof Error ? err.message : String(err)}`
|
|
1836
1854
|
);
|
|
1837
1855
|
}
|
|
1838
|
-
const
|
|
1839
|
-
|
|
1840
|
-
name: this.domain.name,
|
|
1841
|
-
chainId: this.chainId,
|
|
1842
|
-
verifyingContract: this.domain.verifyingContract ?? this.pointTokenAddress
|
|
1843
|
-
},
|
|
1844
|
-
request.consent,
|
|
1845
|
-
request.consentSignature,
|
|
1846
|
-
request.userAddress
|
|
1856
|
+
const deadline = BigInt(
|
|
1857
|
+
Math.floor(this.now() / 1e3) + this.signatureDeadlineSeconds
|
|
1847
1858
|
);
|
|
1848
|
-
|
|
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) {
|
|
1849
1879
|
throw new PTRedeemError(
|
|
1850
|
-
"
|
|
1851
|
-
`
|
|
1880
|
+
"SIGNING_FAILED",
|
|
1881
|
+
`failed to sign BurnRequest: ${err instanceof Error ? err.message : String(err)}`
|
|
1852
1882
|
);
|
|
1853
1883
|
}
|
|
1854
1884
|
const lockId = await this.ledger.reservePendingCredit(
|
|
@@ -1863,29 +1893,17 @@ var PTRedeemHandler = class {
|
|
|
1863
1893
|
aaNonce: request.aaNonce,
|
|
1864
1894
|
pointTokenAddress: this.pointTokenAddress,
|
|
1865
1895
|
batchExecutorAddress: this.batchExecutorAddress,
|
|
1866
|
-
|
|
1867
|
-
|
|
1896
|
+
burnRequest,
|
|
1897
|
+
burnerSignature
|
|
1868
1898
|
});
|
|
1869
1899
|
return {
|
|
1870
1900
|
lockId,
|
|
1871
1901
|
userOp,
|
|
1872
|
-
expiresInSeconds: Math.floor(this.redeemLockDurationMs / 1e3)
|
|
1902
|
+
expiresInSeconds: Math.floor(this.redeemLockDurationMs / 1e3),
|
|
1903
|
+
signatureDeadline: deadline
|
|
1873
1904
|
};
|
|
1874
1905
|
}
|
|
1875
1906
|
};
|
|
1876
|
-
function parseSigStruct(serialized) {
|
|
1877
|
-
const raw = serialized.slice(2);
|
|
1878
|
-
if (raw.length !== 130) {
|
|
1879
|
-
throw new PTRedeemError(
|
|
1880
|
-
"INVALID_CONSENT",
|
|
1881
|
-
`signature must be 65 bytes, got ${raw.length / 2}`
|
|
1882
|
-
);
|
|
1883
|
-
}
|
|
1884
|
-
const r = `0x${raw.slice(0, 64)}`;
|
|
1885
|
-
const s = `0x${raw.slice(64, 128)}`;
|
|
1886
|
-
const v = parseInt(raw.slice(128, 130), 16);
|
|
1887
|
-
return { v, r, s };
|
|
1888
|
-
}
|
|
1889
1907
|
|
|
1890
1908
|
// src/api/handlers/topUpRedemptionHandler.ts
|
|
1891
1909
|
import { getAddress as getAddress8 } from "viem";
|
|
@@ -1931,24 +1949,10 @@ var TopUpRedemptionHandler = class {
|
|
|
1931
1949
|
shortfall
|
|
1932
1950
|
};
|
|
1933
1951
|
}
|
|
1934
|
-
if (request.redeemRequest.consent.amount < shortfall) {
|
|
1935
|
-
throw new TopUpRedemptionError(
|
|
1936
|
-
"CONSENT_AMOUNT_TOO_LOW",
|
|
1937
|
-
`consent.amount (${request.redeemRequest.consent.amount}) must cover shortfall (${shortfall})`
|
|
1938
|
-
);
|
|
1939
|
-
}
|
|
1940
|
-
if (request.redeemRequest.consent.amount !== shortfall) {
|
|
1941
|
-
throw new TopUpRedemptionError(
|
|
1942
|
-
"CONSENT_AMOUNT_TOO_LOW",
|
|
1943
|
-
`consent.amount (${request.redeemRequest.consent.amount}) must equal shortfall (${shortfall}) exactly \u2014 re-sign with correct amount`
|
|
1944
|
-
);
|
|
1945
|
-
}
|
|
1946
1952
|
const redeem = await this.ptRedeemHandler.handle({
|
|
1947
1953
|
userAddress: request.userAddress,
|
|
1948
1954
|
amount: shortfall,
|
|
1949
|
-
|
|
1950
|
-
consentSignature: request.redeemRequest.consentSignature,
|
|
1951
|
-
aaNonce: request.redeemRequest.aaNonce
|
|
1955
|
+
aaNonce: request.aaNonce
|
|
1952
1956
|
});
|
|
1953
1957
|
return {
|
|
1954
1958
|
action: "TOP_UP_STARTED",
|