@pafi-dev/issuer 0.5.15 → 0.5.17

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
@@ -1129,10 +1129,30 @@ interface PTRedeemRequest {
1129
1129
  feeRecipient?: Address;
1130
1130
  }
1131
1131
  interface PTRedeemResponse {
1132
- /** Lock id from the ledger — client polls status with this. */
1132
+ /**
1133
+ * Sponsored path — UserOp with PT fee transfer. BurnRequest signed for
1134
+ * `request.amount - feeAmount`. User holds exactly `request.amount` PT
1135
+ * and the fee comes out of the redeem amount, NOT on top.
1136
+ */
1133
1137
  lockId: string;
1134
- /** Unsigned UserOp — FE attaches paymaster + user signature + submits. */
1135
1138
  userOp: PartialUserOperation;
1139
+ /** = request.amount - feeAmount. What BurnIndexer credits off-chain on sponsored fire. */
1140
+ netCreditAmount: bigint;
1141
+ /**
1142
+ * Fallback path — UserOp with NO fee transfer. BurnRequest signed for
1143
+ * full `request.amount`. User pays gas in ETH directly. Present only
1144
+ * when `feeAmount > 0`.
1145
+ *
1146
+ * Same nonce as sponsored: only one of the two can fire on-chain;
1147
+ * the other lock auto-expires after `expiresInSeconds`. BurnIndexer
1148
+ * matches by burn amount and resolves the correct lock.
1149
+ */
1150
+ fallback?: {
1151
+ lockId: string;
1152
+ userOp: PartialUserOperation;
1153
+ /** = request.amount. Full credit when fallback fires. */
1154
+ netCreditAmount: bigint;
1155
+ };
1136
1156
  /** Seconds until the lock expires if the burn doesn't land. */
1137
1157
  expiresInSeconds: number;
1138
1158
  /** The BurnRequest deadline (unix seconds) — FE uses this to surface a countdown. */
package/dist/index.d.ts CHANGED
@@ -1129,10 +1129,30 @@ interface PTRedeemRequest {
1129
1129
  feeRecipient?: Address;
1130
1130
  }
1131
1131
  interface PTRedeemResponse {
1132
- /** Lock id from the ledger — client polls status with this. */
1132
+ /**
1133
+ * Sponsored path — UserOp with PT fee transfer. BurnRequest signed for
1134
+ * `request.amount - feeAmount`. User holds exactly `request.amount` PT
1135
+ * and the fee comes out of the redeem amount, NOT on top.
1136
+ */
1133
1137
  lockId: string;
1134
- /** Unsigned UserOp — FE attaches paymaster + user signature + submits. */
1135
1138
  userOp: PartialUserOperation;
1139
+ /** = request.amount - feeAmount. What BurnIndexer credits off-chain on sponsored fire. */
1140
+ netCreditAmount: bigint;
1141
+ /**
1142
+ * Fallback path — UserOp with NO fee transfer. BurnRequest signed for
1143
+ * full `request.amount`. User pays gas in ETH directly. Present only
1144
+ * when `feeAmount > 0`.
1145
+ *
1146
+ * Same nonce as sponsored: only one of the two can fire on-chain;
1147
+ * the other lock auto-expires after `expiresInSeconds`. BurnIndexer
1148
+ * matches by burn amount and resolves the correct lock.
1149
+ */
1150
+ fallback?: {
1151
+ lockId: string;
1152
+ userOp: PartialUserOperation;
1153
+ /** = request.amount. Full credit when fallback fires. */
1154
+ netCreditAmount: bigint;
1155
+ };
1136
1156
  /** Seconds until the lock expires if the burn doesn't land. */
1137
1157
  expiresInSeconds: number;
1138
1158
  /** The BurnRequest deadline (unix seconds) — FE uses this to surface a countdown. */
package/dist/index.js CHANGED
@@ -560,13 +560,7 @@ var RelayService = class {
560
560
  err
561
561
  );
562
562
  }
563
- const operations = [
564
- {
565
- target: params.pointTokenAddress,
566
- value: 0n,
567
- data: burnCallData
568
- }
569
- ];
563
+ const operations = [];
570
564
  if (params.feeAmount && params.feeAmount > 0n) {
571
565
  if (!params.feeRecipient) {
572
566
  throw new RelayError(
@@ -590,6 +584,11 @@ var RelayService = class {
590
584
  })
591
585
  });
592
586
  }
587
+ operations.push({
588
+ target: params.pointTokenAddress,
589
+ value: 0n,
590
+ data: burnCallData
591
+ });
593
592
  return buildPartialUserOperation({
594
593
  sender: params.userAddress,
595
594
  nonce: params.aaNonce,
@@ -1373,16 +1372,22 @@ var PTRedeemHandler = class {
1373
1372
  }
1374
1373
  }
1375
1374
  async _handleAfterNonceLock(request, burnNonce) {
1375
+ const fee = request.feeAmount && request.feeAmount > 0n ? request.feeAmount : 0n;
1376
+ if (fee > 0n && fee >= request.amount) {
1377
+ throw new PTRedeemError(
1378
+ "INVALID_AMOUNT",
1379
+ `fee (${fee}) must be strictly less than redeem amount (${request.amount})`
1380
+ );
1381
+ }
1376
1382
  const onChainBalance = await getPointTokenBalance2(
1377
1383
  this.provider,
1378
1384
  this.pointTokenAddress,
1379
1385
  request.userAddress
1380
1386
  );
1381
- const totalRequired = request.amount + (request.feeAmount && request.feeAmount > 0n ? request.feeAmount : 0n);
1382
- if (onChainBalance < totalRequired) {
1387
+ if (onChainBalance < request.amount) {
1383
1388
  throw new PTRedeemError(
1384
1389
  "INVALID_AMOUNT",
1385
- `insufficient on-chain PT balance: have ${onChainBalance}, need ${totalRequired}` + (request.feeAmount && request.feeAmount > 0n ? ` (${request.amount} burn + ${request.feeAmount} fee)` : "")
1390
+ `insufficient on-chain PT balance: have ${onChainBalance}, need ${request.amount}`
1386
1391
  );
1387
1392
  }
1388
1393
  const deadline = BigInt(
@@ -1393,46 +1398,83 @@ var PTRedeemHandler = class {
1393
1398
  chainId: this.chainId,
1394
1399
  verifyingContract: this.domain.verifyingContract ?? this.pointTokenAddress
1395
1400
  };
1396
- const burnRequest = {
1401
+ const sponsoredBurnAmount = request.amount - fee;
1402
+ const sponsoredBurnRequest = {
1397
1403
  from: request.userAddress,
1398
- amount: request.amount,
1404
+ amount: sponsoredBurnAmount,
1399
1405
  nonce: burnNonce,
1400
1406
  deadline
1401
1407
  };
1402
- let burnerSignature;
1408
+ let sponsoredSig;
1403
1409
  try {
1404
- const sig = await signBurnRequest(
1405
- this.burnerSignerWallet,
1406
- domain,
1407
- burnRequest
1408
- );
1409
- burnerSignature = sig.serialized;
1410
+ sponsoredSig = (await signBurnRequest(this.burnerSignerWallet, domain, sponsoredBurnRequest)).serialized;
1410
1411
  } catch (err) {
1411
1412
  throw new PTRedeemError(
1412
1413
  "SIGNING_FAILED",
1413
- `failed to sign BurnRequest: ${err instanceof Error ? err.message : String(err)}`
1414
+ `failed to sign sponsored BurnRequest: ${err instanceof Error ? err.message : String(err)}`
1414
1415
  );
1415
1416
  }
1416
- const lockId = await this.ledger.reservePendingCredit(
1417
+ const sponsoredLockId = await this.ledger.reservePendingCredit(
1417
1418
  request.userAddress,
1418
- request.amount,
1419
+ sponsoredBurnAmount,
1419
1420
  this.redeemLockDurationMs,
1420
1421
  this.pointTokenAddress
1421
1422
  );
1422
- const userOp = this.relayService.prepareBurn({
1423
+ const sponsoredUserOp = this.relayService.prepareBurn({
1423
1424
  mode: "burnWithSig",
1424
1425
  userAddress: request.userAddress,
1425
1426
  aaNonce: request.aaNonce,
1426
1427
  pointTokenAddress: this.pointTokenAddress,
1427
1428
  batchExecutorAddress: this.batchExecutorAddress,
1428
- burnRequest,
1429
- burnerSignature,
1430
- feeAmount: request.feeAmount,
1429
+ burnRequest: sponsoredBurnRequest,
1430
+ burnerSignature: sponsoredSig,
1431
+ feeAmount: fee,
1431
1432
  feeRecipient: request.feeRecipient
1432
1433
  });
1434
+ let fallback = void 0;
1435
+ if (fee > 0n) {
1436
+ const fallbackBurnRequest = {
1437
+ from: request.userAddress,
1438
+ amount: request.amount,
1439
+ nonce: burnNonce,
1440
+ deadline
1441
+ };
1442
+ let fallbackSig;
1443
+ try {
1444
+ fallbackSig = (await signBurnRequest(this.burnerSignerWallet, domain, fallbackBurnRequest)).serialized;
1445
+ } catch (err) {
1446
+ throw new PTRedeemError(
1447
+ "SIGNING_FAILED",
1448
+ `failed to sign fallback BurnRequest: ${err instanceof Error ? err.message : String(err)}`
1449
+ );
1450
+ }
1451
+ const fallbackLockId = await this.ledger.reservePendingCredit(
1452
+ request.userAddress,
1453
+ request.amount,
1454
+ this.redeemLockDurationMs,
1455
+ this.pointTokenAddress
1456
+ );
1457
+ const fallbackUserOp = this.relayService.prepareBurn({
1458
+ mode: "burnWithSig",
1459
+ userAddress: request.userAddress,
1460
+ aaNonce: request.aaNonce,
1461
+ pointTokenAddress: this.pointTokenAddress,
1462
+ batchExecutorAddress: this.batchExecutorAddress,
1463
+ burnRequest: fallbackBurnRequest,
1464
+ burnerSignature: fallbackSig
1465
+ // No feeAmount/feeRecipient — fallback is fee-free.
1466
+ });
1467
+ fallback = {
1468
+ lockId: fallbackLockId,
1469
+ userOp: fallbackUserOp,
1470
+ netCreditAmount: request.amount
1471
+ };
1472
+ }
1433
1473
  return {
1434
- lockId,
1435
- userOp,
1474
+ lockId: sponsoredLockId,
1475
+ userOp: sponsoredUserOp,
1476
+ netCreditAmount: sponsoredBurnAmount,
1477
+ fallback,
1436
1478
  expiresInSeconds: Math.floor(this.redeemLockDurationMs / 1e3),
1437
1479
  signatureDeadline: deadline
1438
1480
  };