@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.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,45 +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
|
-
*
|
|
788
|
-
*/
|
|
789
|
-
/**
|
|
790
|
-
* Build an unsigned UserOp for Scenario 1 (Mint) — direct
|
|
791
|
-
* `PointToken.mint(amount)` flow (v1.4 post-Relayer-removal).
|
|
792
|
-
*
|
|
793
|
-
* `msg.sender` inside the batch = user EOA via EIP-7702 delegation.
|
|
794
|
-
* `PointToken.mint` checks the caller is on its `minters` allowlist
|
|
795
|
-
* (gg56 BE pre-validates this off-chain to avoid wasted gas).
|
|
796
|
-
*
|
|
797
|
-
* Optional PT fee transfer is appended after the mint when
|
|
798
|
-
* `feeAmount > 0` — this is application-level fee recovery (no
|
|
799
|
-
* Relayer doing it for us). The user must end up with `amount - fee`
|
|
800
|
-
* net PT after the batch executes.
|
|
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.
|
|
801
770
|
*/
|
|
802
|
-
prepareMint(params) {
|
|
771
|
+
async prepareMint(params) {
|
|
803
772
|
if (!params.batchExecutorAddress) {
|
|
804
773
|
throw new RelayError(
|
|
805
774
|
"ENCODE_FAILED",
|
|
@@ -818,12 +787,41 @@ var RelayService = class {
|
|
|
818
787
|
if (params.amount <= 0n) {
|
|
819
788
|
throw new RelayError("ENCODE_FAILED", "prepareMint: amount must be positive");
|
|
820
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
|
+
}
|
|
821
819
|
let mintCallData;
|
|
822
820
|
try {
|
|
823
821
|
mintCallData = (0, import_viem5.encodeFunctionData)({
|
|
824
822
|
abi: import_core3.POINT_TOKEN_V2_ABI,
|
|
825
823
|
functionName: "mint",
|
|
826
|
-
args: [params.amount]
|
|
824
|
+
args: [params.userAddress, params.amount, params.deadline, minterSig]
|
|
827
825
|
});
|
|
828
826
|
} catch (err) {
|
|
829
827
|
throw new RelayError(
|
|
@@ -871,13 +869,13 @@ var RelayService = class {
|
|
|
871
869
|
* Build an unsigned UserOp for Scenario 2 (Burn/Redeem).
|
|
872
870
|
*
|
|
873
871
|
* Two modes:
|
|
874
|
-
* - `mode: 'burn'` — direct `PointToken.burn(amount)`;
|
|
875
|
-
*
|
|
876
|
-
*
|
|
877
|
-
*
|
|
878
|
-
*
|
|
879
|
-
*
|
|
880
|
-
*
|
|
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.
|
|
881
879
|
*/
|
|
882
880
|
prepareBurn(params) {
|
|
883
881
|
if (!params.pointTokenAddress) {
|
|
@@ -892,19 +890,24 @@ var RelayService = class {
|
|
|
892
890
|
let burnCallData;
|
|
893
891
|
try {
|
|
894
892
|
if (params.mode === "burnWithSig") {
|
|
895
|
-
if (!params.
|
|
896
|
-
throw new Error("burnWithSig requires
|
|
893
|
+
if (!params.burnRequest || !params.burnerSignature) {
|
|
894
|
+
throw new Error("burnWithSig requires burnRequest + burnerSignature");
|
|
897
895
|
}
|
|
898
896
|
burnCallData = (0, import_viem5.encodeFunctionData)({
|
|
899
897
|
abi: import_core3.POINT_TOKEN_V2_ABI,
|
|
900
|
-
functionName: "
|
|
901
|
-
args: [
|
|
898
|
+
functionName: "burn",
|
|
899
|
+
args: [
|
|
900
|
+
params.burnRequest.from,
|
|
901
|
+
params.burnRequest.amount,
|
|
902
|
+
params.burnRequest.deadline,
|
|
903
|
+
params.burnerSignature
|
|
904
|
+
]
|
|
902
905
|
});
|
|
903
906
|
} else {
|
|
904
907
|
burnCallData = (0, import_viem5.encodeFunctionData)({
|
|
905
908
|
abi: import_core3.POINT_TOKEN_V2_ABI,
|
|
906
909
|
functionName: "burn",
|
|
907
|
-
args: [params.amount]
|
|
910
|
+
args: [params.userAddress, params.amount]
|
|
908
911
|
});
|
|
909
912
|
}
|
|
910
913
|
} catch (err) {
|
|
@@ -1817,6 +1820,7 @@ var IssuerApiHandlers = class {
|
|
|
1817
1820
|
var import_viem9 = require("viem");
|
|
1818
1821
|
var import_core6 = require("@pafi-dev/core");
|
|
1819
1822
|
var DEFAULT_REDEEM_LOCK_MS = 15 * 60 * 1e3;
|
|
1823
|
+
var DEFAULT_SIG_DEADLINE_SEC = 15 * 60;
|
|
1820
1824
|
var PTRedeemError = class extends Error {
|
|
1821
1825
|
constructor(code, message) {
|
|
1822
1826
|
super(message);
|
|
@@ -1828,11 +1832,14 @@ var PTRedeemError = class extends Error {
|
|
|
1828
1832
|
var PTRedeemHandler = class {
|
|
1829
1833
|
ledger;
|
|
1830
1834
|
relayService;
|
|
1835
|
+
provider;
|
|
1831
1836
|
pointTokenAddress;
|
|
1832
1837
|
batchExecutorAddress;
|
|
1833
1838
|
chainId;
|
|
1834
1839
|
domain;
|
|
1840
|
+
burnerSignerWallet;
|
|
1835
1841
|
redeemLockDurationMs;
|
|
1842
|
+
signatureDeadlineSeconds;
|
|
1836
1843
|
now;
|
|
1837
1844
|
constructor(config) {
|
|
1838
1845
|
if (!config.ledger.reservePendingCredit) {
|
|
@@ -1841,46 +1848,68 @@ var PTRedeemHandler = class {
|
|
|
1841
1848
|
"PTRedeemHandler requires a ledger that implements reservePendingCredit() (v0.3.0+)"
|
|
1842
1849
|
);
|
|
1843
1850
|
}
|
|
1851
|
+
if (!config.burnerSignerWallet) {
|
|
1852
|
+
throw new PTRedeemError(
|
|
1853
|
+
"SIGNING_FAILED",
|
|
1854
|
+
"PTRedeemHandler requires burnerSignerWallet (issuer burner signer)"
|
|
1855
|
+
);
|
|
1856
|
+
}
|
|
1844
1857
|
this.ledger = config.ledger;
|
|
1845
1858
|
this.relayService = config.relayService;
|
|
1859
|
+
this.provider = config.provider;
|
|
1846
1860
|
this.pointTokenAddress = (0, import_viem9.getAddress)(config.pointTokenAddress);
|
|
1847
1861
|
this.batchExecutorAddress = (0, import_viem9.getAddress)(config.batchExecutorAddress);
|
|
1848
1862
|
this.chainId = config.chainId;
|
|
1849
1863
|
this.domain = config.domain;
|
|
1864
|
+
this.burnerSignerWallet = config.burnerSignerWallet;
|
|
1850
1865
|
this.redeemLockDurationMs = config.redeemLockDurationMs ?? DEFAULT_REDEEM_LOCK_MS;
|
|
1866
|
+
this.signatureDeadlineSeconds = config.signatureDeadlineSeconds ?? DEFAULT_SIG_DEADLINE_SEC;
|
|
1851
1867
|
this.now = config.now ?? (() => Date.now());
|
|
1852
1868
|
}
|
|
1853
1869
|
async handle(request) {
|
|
1854
1870
|
if (request.amount <= 0n) {
|
|
1855
|
-
throw new PTRedeemError("
|
|
1871
|
+
throw new PTRedeemError("INVALID_AMOUNT", "redeem amount must be positive");
|
|
1856
1872
|
}
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
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) {
|
|
1865
1882
|
throw new PTRedeemError(
|
|
1866
|
-
"
|
|
1867
|
-
`
|
|
1883
|
+
"NONCE_READ_FAILED",
|
|
1884
|
+
`failed to read burnRequestNonces(${request.userAddress}): ${err instanceof Error ? err.message : String(err)}`
|
|
1868
1885
|
);
|
|
1869
1886
|
}
|
|
1870
|
-
const
|
|
1871
|
-
|
|
1872
|
-
name: this.domain.name,
|
|
1873
|
-
chainId: this.chainId,
|
|
1874
|
-
verifyingContract: this.domain.verifyingContract ?? this.pointTokenAddress
|
|
1875
|
-
},
|
|
1876
|
-
request.consent,
|
|
1877
|
-
request.consentSignature,
|
|
1878
|
-
request.userAddress
|
|
1887
|
+
const deadline = BigInt(
|
|
1888
|
+
Math.floor(this.now() / 1e3) + this.signatureDeadlineSeconds
|
|
1879
1889
|
);
|
|
1880
|
-
|
|
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) {
|
|
1881
1910
|
throw new PTRedeemError(
|
|
1882
|
-
"
|
|
1883
|
-
`
|
|
1911
|
+
"SIGNING_FAILED",
|
|
1912
|
+
`failed to sign BurnRequest: ${err instanceof Error ? err.message : String(err)}`
|
|
1884
1913
|
);
|
|
1885
1914
|
}
|
|
1886
1915
|
const lockId = await this.ledger.reservePendingCredit(
|
|
@@ -1895,29 +1924,17 @@ var PTRedeemHandler = class {
|
|
|
1895
1924
|
aaNonce: request.aaNonce,
|
|
1896
1925
|
pointTokenAddress: this.pointTokenAddress,
|
|
1897
1926
|
batchExecutorAddress: this.batchExecutorAddress,
|
|
1898
|
-
|
|
1899
|
-
|
|
1927
|
+
burnRequest,
|
|
1928
|
+
burnerSignature
|
|
1900
1929
|
});
|
|
1901
1930
|
return {
|
|
1902
1931
|
lockId,
|
|
1903
1932
|
userOp,
|
|
1904
|
-
expiresInSeconds: Math.floor(this.redeemLockDurationMs / 1e3)
|
|
1933
|
+
expiresInSeconds: Math.floor(this.redeemLockDurationMs / 1e3),
|
|
1934
|
+
signatureDeadline: deadline
|
|
1905
1935
|
};
|
|
1906
1936
|
}
|
|
1907
1937
|
};
|
|
1908
|
-
function parseSigStruct(serialized) {
|
|
1909
|
-
const raw = serialized.slice(2);
|
|
1910
|
-
if (raw.length !== 130) {
|
|
1911
|
-
throw new PTRedeemError(
|
|
1912
|
-
"INVALID_CONSENT",
|
|
1913
|
-
`signature must be 65 bytes, got ${raw.length / 2}`
|
|
1914
|
-
);
|
|
1915
|
-
}
|
|
1916
|
-
const r = `0x${raw.slice(0, 64)}`;
|
|
1917
|
-
const s = `0x${raw.slice(64, 128)}`;
|
|
1918
|
-
const v = parseInt(raw.slice(128, 130), 16);
|
|
1919
|
-
return { v, r, s };
|
|
1920
|
-
}
|
|
1921
1938
|
|
|
1922
1939
|
// src/api/handlers/topUpRedemptionHandler.ts
|
|
1923
1940
|
var import_viem10 = require("viem");
|
|
@@ -1963,24 +1980,10 @@ var TopUpRedemptionHandler = class {
|
|
|
1963
1980
|
shortfall
|
|
1964
1981
|
};
|
|
1965
1982
|
}
|
|
1966
|
-
if (request.redeemRequest.consent.amount < shortfall) {
|
|
1967
|
-
throw new TopUpRedemptionError(
|
|
1968
|
-
"CONSENT_AMOUNT_TOO_LOW",
|
|
1969
|
-
`consent.amount (${request.redeemRequest.consent.amount}) must cover shortfall (${shortfall})`
|
|
1970
|
-
);
|
|
1971
|
-
}
|
|
1972
|
-
if (request.redeemRequest.consent.amount !== shortfall) {
|
|
1973
|
-
throw new TopUpRedemptionError(
|
|
1974
|
-
"CONSENT_AMOUNT_TOO_LOW",
|
|
1975
|
-
`consent.amount (${request.redeemRequest.consent.amount}) must equal shortfall (${shortfall}) exactly \u2014 re-sign with correct amount`
|
|
1976
|
-
);
|
|
1977
|
-
}
|
|
1978
1983
|
const redeem = await this.ptRedeemHandler.handle({
|
|
1979
1984
|
userAddress: request.userAddress,
|
|
1980
1985
|
amount: shortfall,
|
|
1981
|
-
|
|
1982
|
-
consentSignature: request.redeemRequest.consentSignature,
|
|
1983
|
-
aaNonce: request.redeemRequest.aaNonce
|
|
1986
|
+
aaNonce: request.aaNonce
|
|
1984
1987
|
});
|
|
1985
1988
|
return {
|
|
1986
1989
|
action: "TOP_UP_STARTED",
|