@pafi-dev/issuer 0.7.5 → 0.7.7

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.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { PafiSdkError, SdkErrorHttpStatus, PointTokenDomainConfig, PartialUserOperation, BurnRequest, PoolKey, UserOpTypedData, decodeBatchExecuteCalls, BROKER_HASHES, Eip7702AuthorizationJsonRpc, BuiltSponsorAuth, ENTRY_POINT_V08 } from '@pafi-dev/core';
1
+ import { PafiSdkError, SdkErrorHttpStatus, PointTokenDomainConfig, PartialUserOperation, BurnRequest, PoolKey, UserOpTypedData, decodeBatchExecuteCalls, BROKER_HASHES, BuiltSponsorAuth, ENTRY_POINT_V08 } from '@pafi-dev/core';
2
2
  export { PAFI_SUBGRAPH_URL, PafiSdkError, SdkErrorHttpStatus, ValidationError } from '@pafi-dev/core';
3
3
  import { Address, Hex, PublicClient, WalletClient } from 'viem';
4
4
 
@@ -1481,6 +1481,25 @@ interface PendingUserOpEntry {
1481
1481
  preVerificationGas: string;
1482
1482
  userOpHash: Hex;
1483
1483
  };
1484
+ /**
1485
+ * EIP-7702 authorization tuple — present only on the `delegate`
1486
+ * scenario where the UserOp anchors the EOA's one-time delegation
1487
+ * to a 7702 implementation. Embedded into the eth_sendUserOperation
1488
+ * payload at submit time so Pimlico bundler applies the bytecode
1489
+ * atomically with the UserOp execution. Pre-computed at prepare
1490
+ * time from the user's `signAuthorization` signature so submit
1491
+ * doesn't need to re-derive it.
1492
+ *
1493
+ * v0.7.7 — added per delegate-flow refactor (mobile prepare/submit).
1494
+ */
1495
+ eip7702Auth?: {
1496
+ chainId: string;
1497
+ address: string;
1498
+ nonce: string;
1499
+ r: string;
1500
+ s: string;
1501
+ yParity: string;
1502
+ };
1484
1503
  }
1485
1504
  /**
1486
1505
  * Storage backend for pending UserOps in the mobile prepare/submit pattern.
@@ -1969,55 +1988,30 @@ declare class PerpDepositHandler {
1969
1988
  handle(request: PerpDepositRequest): Promise<PerpDepositResponse>;
1970
1989
  }
1971
1990
 
1972
- /**
1973
- * Pure mechanics for the EIP-7702 delegation submit flow. Builds the
1974
- * delegation-anchor UserOp via `buildDelegationUserOp` (self-call EOA
1975
- * with empty calldata), attaches paymaster sponsorship, splits the
1976
- * user's authorization signature into the JSON-RPC tuple, and relays
1977
- * via the PAFI sponsor-relayer.
1978
- *
1979
- * The UserOp itself is a no-op — a self-call to the user's EOA with
1980
- * `data: "0x"`. The work is in the `eip7702Auth` object: the bundler
1981
- * picks it up, runs the EIP-7702 path, and installs the delegation on
1982
- * the user's EOA atomically with the (no-op) UserOp.
1983
- *
1984
- * v0.7.1 — switched from `encodeBatchExecute([])` (which throws
1985
- * "operations array must not be empty") to `buildDelegationUserOp`
1986
- * (self-call pattern). See SDK_CORE_TRADING_AUDIT.md C1.
1987
- *
1988
- * Throws the same `BundlerNotConfiguredError` / `BundlerRejectedError`
1989
- * as other relay paths so issuer controllers can use one error mapper.
1990
- */
1991
1991
  interface HandleDelegateSubmitParams {
1992
- userAddress: Address;
1993
- chainId: number;
1994
- /** Account nonce read at `delegate/prepare` time and signed by the user. */
1995
- delegationNonce: bigint;
1996
- /** ERC-4337 account nonce. Caller fetches via EntryPoint.getNonce. */
1997
- aaNonce: bigint;
1998
- /** 65-byte secp256k1 signature over the EIP-7702 authorization hash. */
1999
- authSig: Hex | string;
2000
- /** EIP-1559 fees from `provider.estimateFeesPerGas()` (or RPC equivalent). */
2001
- fees: {
2002
- maxFeePerGas?: bigint;
2003
- maxPriorityFeePerGas?: bigint;
2004
- };
1992
+ lockId: string;
1993
+ /** Authenticated user EOA — must match the entry's `sender`. */
1994
+ authenticatedAddress: Address;
1995
+ /** User signature over the persisted userOpHash. */
1996
+ userOpSig: Hex;
1997
+ store: IPendingUserOpStore;
2005
1998
  pafiBackendClient?: PafiBackendClient | null;
2006
- onWarning?: (msg: string) => void;
2007
- /** Override gas limits for the empty-batch UserOp. */
2008
- gasLimits?: {
2009
- callGasLimit?: bigint;
2010
- verificationGasLimit?: bigint;
2011
- preVerificationGas?: bigint;
2012
- };
1999
+ /** Defaults to `ENTRY_POINT_V08`. */
2000
+ entryPoint?: string;
2013
2001
  }
2014
2002
  interface HandleDelegateSubmitResult {
2015
2003
  userOpHash: Hex;
2016
- /** True when paymaster sponsorship was applied (gas is free). */
2017
- isSponsored: boolean;
2018
- /** The authorization tuple actually sent to the bundler. */
2019
- authorization: Eip7702AuthorizationJsonRpc;
2020
2004
  }
2005
+ /**
2006
+ * Retrieve the persisted delegate UserOp, embed the user's signature,
2007
+ * and submit to the bundler with the cached `eip7702Auth` field.
2008
+ *
2009
+ * Throws:
2010
+ * - `PendingUserOpNotFoundError` — entry expired or already submitted (404)
2011
+ * - `PendingUserOpForbiddenError` — sender mismatch (403)
2012
+ * - `BundlerNotConfiguredError` — `pafiBackendClient` missing (503)
2013
+ * - `BundlerRejectedError` — bundler rejected the UserOp (422)
2014
+ */
2021
2015
  declare function handleDelegateSubmit(params: HandleDelegateSubmitParams): Promise<HandleDelegateSubmitResult>;
2022
2016
 
2023
2017
  /**
@@ -2312,7 +2306,24 @@ interface DelegateStatusDto {
2312
2306
  batchExecutorAddress: Address;
2313
2307
  }
2314
2308
  interface DelegatePrepareDto {
2315
- authorizationHash: Hex;
2309
+ /**
2310
+ * v0.7.7 — refactored to mobile prepare/submit pattern. Mobile signs
2311
+ * the EIP-7702 authorization LOCALLY (Privy `signAuthorization`),
2312
+ * then signs `userOpHash` (or `typedData`) BEFORE submit. See
2313
+ * `handleDelegatePrepare` for rationale.
2314
+ *
2315
+ * Pre-v0.7.7 callers expected `{ authorizationHash, delegationNonce,
2316
+ * batchExecutorAddress, chainId }` — `delegationNonce` +
2317
+ * `batchExecutorAddress` retained for back-compat so mobile can
2318
+ * compute the authorization hash if needed; `authorizationHash`
2319
+ * removed (mobile's Privy hook computes it locally).
2320
+ */
2321
+ lockId: string;
2322
+ userOpHash: Hex;
2323
+ typedData: SerializedUserOpTypedData;
2324
+ expiresInSeconds: number;
2325
+ isSponsored: boolean;
2326
+ /** Echoed for mobile to recompute the authorization hash if desired. */
2316
2327
  delegationNonce: string;
2317
2328
  batchExecutorAddress: Address;
2318
2329
  chainId: number;
@@ -2379,13 +2390,32 @@ declare class IssuerApiAdapter {
2379
2390
  claimStatus(authenticatedAddress: Address, lockId: string): Promise<MintStatusResponse>;
2380
2391
  redeemStatus(authenticatedAddress: Address, lockId: string): Promise<BurnStatusResponse>;
2381
2392
  delegateStatus(authenticatedAddress: Address, chainId: number): Promise<DelegateStatusDto>;
2382
- delegatePrepare(authenticatedAddress: Address, chainId: number): Promise<DelegatePrepareDto>;
2383
- delegateSubmit(input: {
2384
- authenticatedAddress: Address;
2393
+ /**
2394
+ * Build the delegation-anchor UserOp + obtain paymaster sponsorship
2395
+ * + persist as a pending entry. Mobile must:
2396
+ *
2397
+ * 1. Sign EIP-7702 authorization LOCALLY (Privy `signAuthorization`
2398
+ * with `{contractAddress: batchExecutorAddress, chainId,
2399
+ * nonce: delegationNonce}`) → 65-byte authSig hex.
2400
+ * 2. POST `/delegate/prepare` with `{ chainId, delegationNonce,
2401
+ * authSig }` → this method.
2402
+ * 3. Sign returned `userOpHash` LOCALLY (`signTypedData(typedData)`).
2403
+ * 4. POST `/delegate/submit` with `{ lockId, userOpSig }`.
2404
+ *
2405
+ * v0.7.7 — replaces single-shot delegateSubmit that tried to relay
2406
+ * a UserOp with empty `signature: "0x"` (Simple7702Account's
2407
+ * validateUserOp reverts `ECDSAInvalidSignatureLength` 0xfce698f7).
2408
+ */
2409
+ delegatePrepare(authenticatedAddress: Address, input: {
2385
2410
  chainId: number;
2386
2411
  delegationNonce: bigint;
2387
- aaNonce: bigint;
2388
2412
  authSig: Hex | string;
2413
+ aaNonce: bigint;
2414
+ }): Promise<DelegatePrepareDto>;
2415
+ delegateSubmit(input: {
2416
+ authenticatedAddress: Address;
2417
+ lockId: string;
2418
+ userOpSig: Hex;
2389
2419
  }): Promise<MobileSubmitDto>;
2390
2420
  /**
2391
2421
  * Build + sign a SponsorAuth payload. Returns `undefined` when no
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { PafiSdkError, SdkErrorHttpStatus, PointTokenDomainConfig, PartialUserOperation, BurnRequest, PoolKey, UserOpTypedData, decodeBatchExecuteCalls, BROKER_HASHES, Eip7702AuthorizationJsonRpc, BuiltSponsorAuth, ENTRY_POINT_V08 } from '@pafi-dev/core';
1
+ import { PafiSdkError, SdkErrorHttpStatus, PointTokenDomainConfig, PartialUserOperation, BurnRequest, PoolKey, UserOpTypedData, decodeBatchExecuteCalls, BROKER_HASHES, BuiltSponsorAuth, ENTRY_POINT_V08 } from '@pafi-dev/core';
2
2
  export { PAFI_SUBGRAPH_URL, PafiSdkError, SdkErrorHttpStatus, ValidationError } from '@pafi-dev/core';
3
3
  import { Address, Hex, PublicClient, WalletClient } from 'viem';
4
4
 
@@ -1481,6 +1481,25 @@ interface PendingUserOpEntry {
1481
1481
  preVerificationGas: string;
1482
1482
  userOpHash: Hex;
1483
1483
  };
1484
+ /**
1485
+ * EIP-7702 authorization tuple — present only on the `delegate`
1486
+ * scenario where the UserOp anchors the EOA's one-time delegation
1487
+ * to a 7702 implementation. Embedded into the eth_sendUserOperation
1488
+ * payload at submit time so Pimlico bundler applies the bytecode
1489
+ * atomically with the UserOp execution. Pre-computed at prepare
1490
+ * time from the user's `signAuthorization` signature so submit
1491
+ * doesn't need to re-derive it.
1492
+ *
1493
+ * v0.7.7 — added per delegate-flow refactor (mobile prepare/submit).
1494
+ */
1495
+ eip7702Auth?: {
1496
+ chainId: string;
1497
+ address: string;
1498
+ nonce: string;
1499
+ r: string;
1500
+ s: string;
1501
+ yParity: string;
1502
+ };
1484
1503
  }
1485
1504
  /**
1486
1505
  * Storage backend for pending UserOps in the mobile prepare/submit pattern.
@@ -1969,55 +1988,30 @@ declare class PerpDepositHandler {
1969
1988
  handle(request: PerpDepositRequest): Promise<PerpDepositResponse>;
1970
1989
  }
1971
1990
 
1972
- /**
1973
- * Pure mechanics for the EIP-7702 delegation submit flow. Builds the
1974
- * delegation-anchor UserOp via `buildDelegationUserOp` (self-call EOA
1975
- * with empty calldata), attaches paymaster sponsorship, splits the
1976
- * user's authorization signature into the JSON-RPC tuple, and relays
1977
- * via the PAFI sponsor-relayer.
1978
- *
1979
- * The UserOp itself is a no-op — a self-call to the user's EOA with
1980
- * `data: "0x"`. The work is in the `eip7702Auth` object: the bundler
1981
- * picks it up, runs the EIP-7702 path, and installs the delegation on
1982
- * the user's EOA atomically with the (no-op) UserOp.
1983
- *
1984
- * v0.7.1 — switched from `encodeBatchExecute([])` (which throws
1985
- * "operations array must not be empty") to `buildDelegationUserOp`
1986
- * (self-call pattern). See SDK_CORE_TRADING_AUDIT.md C1.
1987
- *
1988
- * Throws the same `BundlerNotConfiguredError` / `BundlerRejectedError`
1989
- * as other relay paths so issuer controllers can use one error mapper.
1990
- */
1991
1991
  interface HandleDelegateSubmitParams {
1992
- userAddress: Address;
1993
- chainId: number;
1994
- /** Account nonce read at `delegate/prepare` time and signed by the user. */
1995
- delegationNonce: bigint;
1996
- /** ERC-4337 account nonce. Caller fetches via EntryPoint.getNonce. */
1997
- aaNonce: bigint;
1998
- /** 65-byte secp256k1 signature over the EIP-7702 authorization hash. */
1999
- authSig: Hex | string;
2000
- /** EIP-1559 fees from `provider.estimateFeesPerGas()` (or RPC equivalent). */
2001
- fees: {
2002
- maxFeePerGas?: bigint;
2003
- maxPriorityFeePerGas?: bigint;
2004
- };
1992
+ lockId: string;
1993
+ /** Authenticated user EOA — must match the entry's `sender`. */
1994
+ authenticatedAddress: Address;
1995
+ /** User signature over the persisted userOpHash. */
1996
+ userOpSig: Hex;
1997
+ store: IPendingUserOpStore;
2005
1998
  pafiBackendClient?: PafiBackendClient | null;
2006
- onWarning?: (msg: string) => void;
2007
- /** Override gas limits for the empty-batch UserOp. */
2008
- gasLimits?: {
2009
- callGasLimit?: bigint;
2010
- verificationGasLimit?: bigint;
2011
- preVerificationGas?: bigint;
2012
- };
1999
+ /** Defaults to `ENTRY_POINT_V08`. */
2000
+ entryPoint?: string;
2013
2001
  }
2014
2002
  interface HandleDelegateSubmitResult {
2015
2003
  userOpHash: Hex;
2016
- /** True when paymaster sponsorship was applied (gas is free). */
2017
- isSponsored: boolean;
2018
- /** The authorization tuple actually sent to the bundler. */
2019
- authorization: Eip7702AuthorizationJsonRpc;
2020
2004
  }
2005
+ /**
2006
+ * Retrieve the persisted delegate UserOp, embed the user's signature,
2007
+ * and submit to the bundler with the cached `eip7702Auth` field.
2008
+ *
2009
+ * Throws:
2010
+ * - `PendingUserOpNotFoundError` — entry expired or already submitted (404)
2011
+ * - `PendingUserOpForbiddenError` — sender mismatch (403)
2012
+ * - `BundlerNotConfiguredError` — `pafiBackendClient` missing (503)
2013
+ * - `BundlerRejectedError` — bundler rejected the UserOp (422)
2014
+ */
2021
2015
  declare function handleDelegateSubmit(params: HandleDelegateSubmitParams): Promise<HandleDelegateSubmitResult>;
2022
2016
 
2023
2017
  /**
@@ -2312,7 +2306,24 @@ interface DelegateStatusDto {
2312
2306
  batchExecutorAddress: Address;
2313
2307
  }
2314
2308
  interface DelegatePrepareDto {
2315
- authorizationHash: Hex;
2309
+ /**
2310
+ * v0.7.7 — refactored to mobile prepare/submit pattern. Mobile signs
2311
+ * the EIP-7702 authorization LOCALLY (Privy `signAuthorization`),
2312
+ * then signs `userOpHash` (or `typedData`) BEFORE submit. See
2313
+ * `handleDelegatePrepare` for rationale.
2314
+ *
2315
+ * Pre-v0.7.7 callers expected `{ authorizationHash, delegationNonce,
2316
+ * batchExecutorAddress, chainId }` — `delegationNonce` +
2317
+ * `batchExecutorAddress` retained for back-compat so mobile can
2318
+ * compute the authorization hash if needed; `authorizationHash`
2319
+ * removed (mobile's Privy hook computes it locally).
2320
+ */
2321
+ lockId: string;
2322
+ userOpHash: Hex;
2323
+ typedData: SerializedUserOpTypedData;
2324
+ expiresInSeconds: number;
2325
+ isSponsored: boolean;
2326
+ /** Echoed for mobile to recompute the authorization hash if desired. */
2316
2327
  delegationNonce: string;
2317
2328
  batchExecutorAddress: Address;
2318
2329
  chainId: number;
@@ -2379,13 +2390,32 @@ declare class IssuerApiAdapter {
2379
2390
  claimStatus(authenticatedAddress: Address, lockId: string): Promise<MintStatusResponse>;
2380
2391
  redeemStatus(authenticatedAddress: Address, lockId: string): Promise<BurnStatusResponse>;
2381
2392
  delegateStatus(authenticatedAddress: Address, chainId: number): Promise<DelegateStatusDto>;
2382
- delegatePrepare(authenticatedAddress: Address, chainId: number): Promise<DelegatePrepareDto>;
2383
- delegateSubmit(input: {
2384
- authenticatedAddress: Address;
2393
+ /**
2394
+ * Build the delegation-anchor UserOp + obtain paymaster sponsorship
2395
+ * + persist as a pending entry. Mobile must:
2396
+ *
2397
+ * 1. Sign EIP-7702 authorization LOCALLY (Privy `signAuthorization`
2398
+ * with `{contractAddress: batchExecutorAddress, chainId,
2399
+ * nonce: delegationNonce}`) → 65-byte authSig hex.
2400
+ * 2. POST `/delegate/prepare` with `{ chainId, delegationNonce,
2401
+ * authSig }` → this method.
2402
+ * 3. Sign returned `userOpHash` LOCALLY (`signTypedData(typedData)`).
2403
+ * 4. POST `/delegate/submit` with `{ lockId, userOpSig }`.
2404
+ *
2405
+ * v0.7.7 — replaces single-shot delegateSubmit that tried to relay
2406
+ * a UserOp with empty `signature: "0x"` (Simple7702Account's
2407
+ * validateUserOp reverts `ECDSAInvalidSignatureLength` 0xfce698f7).
2408
+ */
2409
+ delegatePrepare(authenticatedAddress: Address, input: {
2385
2410
  chainId: number;
2386
2411
  delegationNonce: bigint;
2387
- aaNonce: bigint;
2388
2412
  authSig: Hex | string;
2413
+ aaNonce: bigint;
2414
+ }): Promise<DelegatePrepareDto>;
2415
+ delegateSubmit(input: {
2416
+ authenticatedAddress: Address;
2417
+ lockId: string;
2418
+ userOpSig: Hex;
2389
2419
  }): Promise<MobileSubmitDto>;
2390
2420
  /**
2391
2421
  * Build + sign a SponsorAuth payload. Returns `undefined` when no
package/dist/index.js CHANGED
@@ -2283,15 +2283,18 @@ import {
2283
2283
  ENTRY_POINT_V08 as ENTRY_POINT_V082,
2284
2284
  buildDelegationUserOp,
2285
2285
  buildEip7702Authorization,
2286
+ computeUserOpHash as computeUserOpHash2,
2287
+ buildUserOpTypedData as buildUserOpTypedData2,
2286
2288
  getContractAddresses as getContractAddresses5,
2287
2289
  serializeUserOpToJsonRpc as serializeUserOpToJsonRpc2
2288
2290
  } from "@pafi-dev/core";
2291
+ import { getAddress as getAddress9 } from "viem";
2289
2292
  var DEFAULT_DELEGATE_GAS = {
2290
2293
  callGasLimit: 100000n,
2291
2294
  verificationGasLimit: 150000n,
2292
2295
  preVerificationGas: 50000n
2293
2296
  };
2294
- async function handleDelegateSubmit(params) {
2297
+ async function handleDelegatePrepare(params) {
2295
2298
  const { batchExecutor } = getContractAddresses5(params.chainId);
2296
2299
  const partial = buildDelegationUserOp({
2297
2300
  userAddress: params.userAddress,
@@ -2328,39 +2331,76 @@ async function handleDelegateSubmit(params) {
2328
2331
  onWarning: params.onWarning
2329
2332
  });
2330
2333
  const merged = {
2331
- ...userOp,
2332
- ...paymasterFields ?? {}
2334
+ sender: userOp.sender,
2335
+ nonce: userOp.nonce,
2336
+ callData: userOp.callData,
2337
+ callGasLimit: paymasterFields?.callGasLimit ?? userOp.callGasLimit,
2338
+ verificationGasLimit: paymasterFields?.verificationGasLimit ?? userOp.verificationGasLimit,
2339
+ preVerificationGas: paymasterFields?.preVerificationGas ?? userOp.preVerificationGas,
2340
+ maxFeePerGas: paymasterFields?.maxFeePerGas ?? userOp.maxFeePerGas,
2341
+ maxPriorityFeePerGas: paymasterFields?.maxPriorityFeePerGas ?? userOp.maxPriorityFeePerGas,
2342
+ paymaster: paymasterFields?.paymaster,
2343
+ paymasterVerificationGasLimit: paymasterFields?.paymasterVerificationGasLimit,
2344
+ paymasterPostOpGasLimit: paymasterFields?.paymasterPostOpGasLimit,
2345
+ paymasterData: paymasterFields?.paymasterData
2333
2346
  };
2334
- const userOpJson = serializeUserOpToJsonRpc2(
2347
+ const userOpHash = computeUserOpHash2(merged, params.chainId);
2348
+ const typed = buildUserOpTypedData2(merged, params.chainId);
2349
+ await params.store.save(
2350
+ params.lockId,
2335
2351
  {
2336
2352
  sender: merged.sender,
2337
- nonce: merged.nonce,
2353
+ nonce: merged.nonce.toString(10),
2338
2354
  callData: merged.callData,
2339
- callGasLimit: merged.callGasLimit,
2340
- verificationGasLimit: merged.verificationGasLimit,
2341
- preVerificationGas: merged.preVerificationGas,
2342
- maxFeePerGas: merged.maxFeePerGas,
2343
- maxPriorityFeePerGas: merged.maxPriorityFeePerGas,
2344
- paymaster: paymasterFields?.paymaster,
2345
- paymasterVerificationGasLimit: paymasterFields?.paymasterVerificationGasLimit,
2346
- paymasterPostOpGasLimit: paymasterFields?.paymasterPostOpGasLimit,
2347
- paymasterData: paymasterFields?.paymasterData
2355
+ callGasLimit: merged.callGasLimit.toString(10),
2356
+ verificationGasLimit: merged.verificationGasLimit.toString(10),
2357
+ preVerificationGas: merged.preVerificationGas.toString(10),
2358
+ maxFeePerGas: merged.maxFeePerGas.toString(10),
2359
+ maxPriorityFeePerGas: merged.maxPriorityFeePerGas.toString(10),
2360
+ ...merged.paymaster ? { paymaster: merged.paymaster } : {},
2361
+ ...merged.paymasterVerificationGasLimit ? {
2362
+ paymasterVerificationGasLimit: merged.paymasterVerificationGasLimit.toString(10)
2363
+ } : {},
2364
+ ...merged.paymasterPostOpGasLimit ? {
2365
+ paymasterPostOpGasLimit: merged.paymasterPostOpGasLimit.toString(10)
2366
+ } : {},
2367
+ ...merged.paymasterData ? { paymasterData: merged.paymasterData } : {},
2368
+ chainId: params.chainId,
2369
+ userOpHash,
2370
+ eip7702Auth: authorization
2348
2371
  },
2349
- // Delegation UserOp is submitted unsigned — the EIP-7702 authorization
2350
- // is the user's "consent"; no separate AA signature is needed.
2351
- "0x"
2372
+ params.ttlSeconds
2352
2373
  );
2374
+ return {
2375
+ lockId: params.lockId,
2376
+ userOpHash,
2377
+ typedData: serializeUserOpTypedData(typed),
2378
+ expiresInSeconds: params.ttlSeconds,
2379
+ isSponsored: !!paymasterFields
2380
+ };
2381
+ }
2382
+ async function handleDelegateSubmit(params) {
2383
+ const entry = await params.store.get(params.lockId);
2384
+ if (!entry) {
2385
+ throw new PendingUserOpNotFoundError(params.lockId);
2386
+ }
2387
+ if (getAddress9(entry.sender) !== getAddress9(params.authenticatedAddress)) {
2388
+ throw new PendingUserOpForbiddenError(params.lockId);
2389
+ }
2390
+ if (!entry.eip7702Auth) {
2391
+ throw new Error(
2392
+ `delegate entry ${params.lockId} missing eip7702Auth \u2014 prepare step did not run correctly`
2393
+ );
2394
+ }
2395
+ const userOpJson = serializeEntryToJsonRpc(entry, params.userOpSig, "sponsored");
2353
2396
  const result = await relayUserOp({
2354
2397
  client: params.pafiBackendClient,
2355
2398
  userOp: userOpJson,
2356
- entryPoint: ENTRY_POINT_V082,
2357
- eip7702Auth: authorization
2399
+ entryPoint: params.entryPoint ?? ENTRY_POINT_V082,
2400
+ eip7702Auth: entry.eip7702Auth
2358
2401
  });
2359
- return {
2360
- userOpHash: result.userOpHash,
2361
- isSponsored: !!paymasterFields,
2362
- authorization
2363
- };
2402
+ await params.store.delete(params.lockId);
2403
+ return { userOpHash: result.userOpHash };
2364
2404
  }
2365
2405
 
2366
2406
  // src/api/errorMapper.ts
@@ -2389,10 +2429,10 @@ function createSdkErrorMapper(factories) {
2389
2429
  }
2390
2430
 
2391
2431
  // src/api/issuerApiAdapter.ts
2392
- import { getAddress as getAddress9 } from "viem";
2432
+ import { randomUUID } from "crypto";
2433
+ import { getAddress as getAddress10 } from "viem";
2393
2434
  import {
2394
2435
  buildAndSignSponsorAuth,
2395
- computeAuthorizationHash,
2396
2436
  decodeBatchExecuteCalls as decodeBatchExecuteCalls3,
2397
2437
  encodeBatchExecute,
2398
2438
  ENTRY_POINT_V08 as ENTRY_POINT_V083,
@@ -2455,7 +2495,7 @@ var IssuerApiAdapter = class {
2455
2495
  async pools(authenticatedAddress, chainId, pointTokenAddress) {
2456
2496
  const result = await this.cfg.issuerService.api.handlePools(
2457
2497
  authenticatedAddress,
2458
- { chainId, pointTokenAddress: getAddress9(pointTokenAddress) }
2498
+ { chainId, pointTokenAddress: getAddress10(pointTokenAddress) }
2459
2499
  );
2460
2500
  return { pools: result.pools };
2461
2501
  }
@@ -2464,8 +2504,8 @@ var IssuerApiAdapter = class {
2464
2504
  authenticatedAddress,
2465
2505
  {
2466
2506
  chainId,
2467
- userAddress: getAddress9(userAddress),
2468
- pointTokenAddress: getAddress9(pointTokenAddress)
2507
+ userAddress: getAddress10(userAddress),
2508
+ pointTokenAddress: getAddress10(pointTokenAddress)
2469
2509
  }
2470
2510
  );
2471
2511
  return {
@@ -2487,7 +2527,7 @@ var IssuerApiAdapter = class {
2487
2527
  "ptClaimHandler",
2488
2528
  "claim"
2489
2529
  );
2490
- const pointTokenAddress = getAddress9(input.pointTokenAddress);
2530
+ const pointTokenAddress = getAddress10(input.pointTokenAddress);
2491
2531
  const result = await ptClaimHandler.handle({
2492
2532
  authenticatedAddress: input.authenticatedAddress,
2493
2533
  userAddress: input.authenticatedAddress,
@@ -2582,7 +2622,7 @@ var IssuerApiAdapter = class {
2582
2622
  "ptClaimHandler",
2583
2623
  "claimPrepare"
2584
2624
  );
2585
- const pointTokenAddress = getAddress9(input.pointTokenAddress);
2625
+ const pointTokenAddress = getAddress10(input.pointTokenAddress);
2586
2626
  const claimResult = await ptClaimHandler.handle({
2587
2627
  authenticatedAddress: input.authenticatedAddress,
2588
2628
  userAddress: input.authenticatedAddress,
@@ -2628,7 +2668,7 @@ var IssuerApiAdapter = class {
2628
2668
  }
2629
2669
  async redeemPrepare(input) {
2630
2670
  this.assertRedeemHandler();
2631
- const pointTokenAddress = getAddress9(input.pointTokenAddress);
2671
+ const pointTokenAddress = getAddress10(input.pointTokenAddress);
2632
2672
  const redeemResponse = await this.cfg.ptRedeemHandler.handle({
2633
2673
  userAddress: input.authenticatedAddress,
2634
2674
  authenticatedAddress: input.authenticatedAddress,
@@ -2701,37 +2741,59 @@ var IssuerApiAdapter = class {
2701
2741
  batchExecutorAddress: batchExecutor
2702
2742
  };
2703
2743
  }
2704
- async delegatePrepare(authenticatedAddress, chainId) {
2705
- const { batchExecutor } = getContractAddresses6(chainId);
2706
- const accountNonce = BigInt(
2707
- await this.cfg.provider.getTransactionCount({
2708
- address: authenticatedAddress
2709
- })
2710
- );
2711
- const authorizationHash = computeAuthorizationHash(
2712
- chainId,
2713
- batchExecutor,
2714
- accountNonce
2715
- );
2716
- return {
2717
- authorizationHash,
2718
- delegationNonce: accountNonce.toString(),
2719
- batchExecutorAddress: batchExecutor,
2720
- chainId
2721
- };
2722
- }
2723
- async delegateSubmit(input) {
2744
+ /**
2745
+ * Build the delegation-anchor UserOp + obtain paymaster sponsorship
2746
+ * + persist as a pending entry. Mobile must:
2747
+ *
2748
+ * 1. Sign EIP-7702 authorization LOCALLY (Privy `signAuthorization`
2749
+ * with `{contractAddress: batchExecutorAddress, chainId,
2750
+ * nonce: delegationNonce}`) → 65-byte authSig hex.
2751
+ * 2. POST `/delegate/prepare` with `{ chainId, delegationNonce,
2752
+ * authSig }` → this method.
2753
+ * 3. Sign returned `userOpHash` LOCALLY (`signTypedData(typedData)`).
2754
+ * 4. POST `/delegate/submit` with `{ lockId, userOpSig }`.
2755
+ *
2756
+ * v0.7.7 — replaces single-shot delegateSubmit that tried to relay
2757
+ * a UserOp with empty `signature: "0x"` (Simple7702Account's
2758
+ * validateUserOp reverts `ECDSAInvalidSignatureLength` 0xfce698f7).
2759
+ */
2760
+ async delegatePrepare(authenticatedAddress, input) {
2761
+ const { batchExecutor } = getContractAddresses6(input.chainId);
2724
2762
  const fees = await this.cfg.provider.estimateFeesPerGas();
2725
- const result = await handleDelegateSubmit({
2726
- userAddress: input.authenticatedAddress,
2763
+ const lockId = randomUUID();
2764
+ const result = await handleDelegatePrepare({
2765
+ userAddress: authenticatedAddress,
2727
2766
  chainId: input.chainId,
2728
2767
  delegationNonce: input.delegationNonce,
2729
2768
  aaNonce: input.aaNonce,
2730
2769
  authSig: input.authSig,
2731
2770
  fees,
2771
+ lockId,
2772
+ store: this.cfg.pendingUserOpStore,
2773
+ ttlSeconds: 15 * 60,
2774
+ // 15min — match claim/redeem mobile lock duration
2732
2775
  pafiBackendClient: this.cfg.pafiBackendClient,
2733
2776
  onWarning: this.cfg.onWarning
2734
2777
  });
2778
+ return {
2779
+ lockId: result.lockId,
2780
+ userOpHash: result.userOpHash,
2781
+ typedData: result.typedData,
2782
+ expiresInSeconds: result.expiresInSeconds,
2783
+ isSponsored: result.isSponsored,
2784
+ delegationNonce: input.delegationNonce.toString(),
2785
+ batchExecutorAddress: batchExecutor,
2786
+ chainId: input.chainId
2787
+ };
2788
+ }
2789
+ async delegateSubmit(input) {
2790
+ const result = await handleDelegateSubmit({
2791
+ lockId: input.lockId,
2792
+ authenticatedAddress: input.authenticatedAddress,
2793
+ userOpSig: input.userOpSig,
2794
+ store: this.cfg.pendingUserOpStore,
2795
+ pafiBackendClient: this.cfg.pafiBackendClient
2796
+ });
2735
2797
  return { userOpHash: result.userOpHash };
2736
2798
  }
2737
2799
  // ------------------------------ Internal helpers -------------------------
@@ -3372,7 +3434,7 @@ var PafiBackendClient = class {
3372
3434
  };
3373
3435
 
3374
3436
  // src/config.ts
3375
- import { getAddress as getAddress10 } from "viem";
3437
+ import { getAddress as getAddress11 } from "viem";
3376
3438
  import { getContractAddresses as getContractAddresses7 } from "@pafi-dev/core";
3377
3439
  function createIssuerService(config) {
3378
3440
  if (!config.provider) {
@@ -3393,7 +3455,7 @@ function createIssuerService(config) {
3393
3455
  "createIssuerService: at least one of pointTokenAddress / pointTokenAddresses is required"
3394
3456
  );
3395
3457
  }
3396
- const tokenAddresses = rawAddresses.map((a) => getAddress10(a));
3458
+ const tokenAddresses = rawAddresses.map((a) => getAddress11(a));
3397
3459
  const ledger = config.ledger;
3398
3460
  const sessionStore = config.sessionStore ?? new MemorySessionStore();
3399
3461
  const policy = config.policy ?? new DefaultPolicyEngine({ ledger });
@@ -3482,7 +3544,7 @@ function createIssuerService(config) {
3482
3544
  }
3483
3545
 
3484
3546
  // src/issuer-state/validator.ts
3485
- import { getAddress as getAddress11 } from "viem";
3547
+ import { getAddress as getAddress12 } from "viem";
3486
3548
  import {
3487
3549
  POINT_TOKEN_V2_ABI as POINT_TOKEN_V2_ABI3,
3488
3550
  issuerRegistryGetIssuerFlatAbi,
@@ -3513,7 +3575,7 @@ var IssuerStateValidator = class _IssuerStateValidator {
3513
3575
  */
3514
3576
  invalidate(pointToken) {
3515
3577
  if (pointToken) {
3516
- const key = getAddress11(pointToken);
3578
+ const key = getAddress12(pointToken);
3517
3579
  this.pointTokenIssuerCache.delete(key);
3518
3580
  this.stateCache.delete(key);
3519
3581
  this.inflight.delete(key);
@@ -3528,7 +3590,7 @@ var IssuerStateValidator = class _IssuerStateValidator {
3528
3590
  * The issuer field is set at `initialize()` and never changes.
3529
3591
  */
3530
3592
  async getIssuerAddressForPointToken(pointToken) {
3531
- const key = getAddress11(pointToken);
3593
+ const key = getAddress12(pointToken);
3532
3594
  const cached = this.pointTokenIssuerCache.get(key);
3533
3595
  if (cached) return cached;
3534
3596
  const issuer = await this.provider.readContract({
@@ -3536,15 +3598,15 @@ var IssuerStateValidator = class _IssuerStateValidator {
3536
3598
  abi: POINT_TOKEN_V2_ABI3,
3537
3599
  functionName: "issuer"
3538
3600
  });
3539
- this.pointTokenIssuerCache.set(key, getAddress11(issuer));
3540
- return getAddress11(issuer);
3601
+ this.pointTokenIssuerCache.set(key, getAddress12(issuer));
3602
+ return getAddress12(issuer);
3541
3603
  }
3542
3604
  /**
3543
3605
  * Read registry record + totalSupply, with 30s cache and in-flight
3544
3606
  * deduplication. Does NOT throw on inactive/missing — returns raw state.
3545
3607
  */
3546
3608
  async getIssuerState(pointToken) {
3547
- const tokenAddr = getAddress11(pointToken);
3609
+ const tokenAddr = getAddress12(pointToken);
3548
3610
  const now = Date.now();
3549
3611
  const cached = this.stateCache.get(tokenAddr);
3550
3612
  if (cached && cached.expiresAt > now) return cached.value;
@@ -3642,7 +3704,7 @@ var IssuerStateValidator = class _IssuerStateValidator {
3642
3704
  };
3643
3705
 
3644
3706
  // src/index.ts
3645
- var PAFI_ISSUER_SDK_VERSION = true ? "0.7.5" : "dev";
3707
+ var PAFI_ISSUER_SDK_VERSION = true ? "0.7.7" : "dev";
3646
3708
  export {
3647
3709
  AdapterMisconfiguredError,
3648
3710
  AuthError,