@palindromepay/sdk 1.9.9 → 2.0.1

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.
@@ -86,7 +86,8 @@ export declare enum SDKErrorCode {
86
86
  RPC_ERROR = "RPC_ERROR",
87
87
  EVIDENCE_ALREADY_SUBMITTED = "EVIDENCE_ALREADY_SUBMITTED",
88
88
  ESCROW_NOT_FOUND = "ESCROW_NOT_FOUND",
89
- ALREADY_ACCEPTED = "ALREADY_ACCEPTED"
89
+ ALREADY_ACCEPTED = "ALREADY_ACCEPTED",
90
+ SIGNATURE_INVALID = "SIGNATURE_INVALID"
90
91
  }
91
92
  export type EscrowWalletClient = WalletClient<Transport, Chain, Account>;
92
93
  export declare class SDKError extends Error {
@@ -331,6 +332,11 @@ export declare class PalindromePaySDK {
331
332
  * Used for: deposit, confirmDelivery, requestCancel, submitArbiterDecision
332
333
  */
333
334
  signWalletAuthorization(walletClient: EscrowWalletClient, walletAddress: Address, escrowId: bigint): Promise<Hex>;
335
+ /**
336
+ * Verify a wallet authorization signature locally before submitting transaction
337
+ * This ensures the signature is cryptographically valid for the expected signer
338
+ */
339
+ private verifyWalletSignature;
334
340
  /**
335
341
  * Sign a confirm delivery message (for gasless meta-tx)
336
342
  */
@@ -349,6 +355,7 @@ export declare class PalindromePaySDK {
349
355
  isSignatureDeadlineExpired(deadline: bigint, safetySeconds?: number): boolean;
350
356
  /**
351
357
  * Predict the wallet address for a given escrow ID (before creation)
358
+ * Calls the contract's computeWalletAddress function - single source of truth
352
359
  */
353
360
  predictWalletAddress(escrowId: bigint): Promise<Address>;
354
361
  /**
@@ -488,6 +495,7 @@ export declare class PalindromePaySDK {
488
495
  escrowId: bigint;
489
496
  txHash: Hex;
490
497
  walletAddress: Address;
498
+ signatureValid: boolean;
491
499
  }>;
492
500
  /**
493
501
  * Create a new escrow and deposit funds as the buyer (single transaction)
@@ -517,6 +525,7 @@ export declare class PalindromePaySDK {
517
525
  escrowId: bigint;
518
526
  txHash: Hex;
519
527
  walletAddress: Address;
528
+ signatureValid: boolean;
520
529
  }>;
521
530
  /**
522
531
  * Deposit funds into an existing escrow as the buyer
@@ -530,13 +539,16 @@ export declare class PalindromePaySDK {
530
539
  *
531
540
  * @param walletClient - The buyer's wallet client (must have account connected)
532
541
  * @param escrowId - The escrow ID to deposit into
533
- * @returns Transaction hash
542
+ * @returns Object containing transaction hash and signature validity
534
543
  * @throws {SDKError} WALLET_NOT_CONNECTED - If wallet client is not connected
535
544
  * @throws {SDKError} NOT_BUYER - If caller is not the designated buyer
536
545
  * @throws {SDKError} INVALID_STATE - If escrow is not in AWAITING_PAYMENT state
537
546
  * @throws {SDKError} INSUFFICIENT_BALANCE - If buyer has insufficient token balance
538
547
  */
539
- deposit(walletClient: EscrowWalletClient, escrowId: bigint): Promise<Hex>;
548
+ deposit(walletClient: EscrowWalletClient, escrowId: bigint): Promise<{
549
+ txHash: Hex;
550
+ signatureValid: boolean;
551
+ }>;
540
552
  /**
541
553
  * Accept an escrow as the seller (for buyer-created escrows)
542
554
  *
@@ -548,13 +560,16 @@ export declare class PalindromePaySDK {
548
560
  *
549
561
  * @param walletClient - The seller's wallet client (must have account connected)
550
562
  * @param escrowId - The escrow ID to accept
551
- * @returns Transaction hash
563
+ * @returns Object containing transaction hash and signature validity
552
564
  * @throws {SDKError} WALLET_NOT_CONNECTED - If wallet client is not connected
553
565
  * @throws {SDKError} NOT_SELLER - If caller is not the designated seller
554
566
  * @throws {SDKError} INVALID_STATE - If escrow is not in AWAITING_DELIVERY state
555
567
  * @throws {SDKError} ALREADY_ACCEPTED - If escrow was already accepted
556
568
  */
557
- acceptEscrow(walletClient: EscrowWalletClient, escrowId: bigint): Promise<Hex>;
569
+ acceptEscrow(walletClient: EscrowWalletClient, escrowId: bigint): Promise<{
570
+ txHash: Hex;
571
+ signatureValid: boolean;
572
+ }>;
558
573
  /**
559
574
  * Confirm delivery and release funds to the seller
560
575
  *
@@ -130,6 +130,7 @@ var SDKErrorCode;
130
130
  SDKErrorCode["EVIDENCE_ALREADY_SUBMITTED"] = "EVIDENCE_ALREADY_SUBMITTED";
131
131
  SDKErrorCode["ESCROW_NOT_FOUND"] = "ESCROW_NOT_FOUND";
132
132
  SDKErrorCode["ALREADY_ACCEPTED"] = "ALREADY_ACCEPTED";
133
+ SDKErrorCode["SIGNATURE_INVALID"] = "SIGNATURE_INVALID";
133
134
  })(SDKErrorCode || (exports.SDKErrorCode = SDKErrorCode = {}));
134
135
  class SDKError extends Error {
135
136
  constructor(message, code, details) {
@@ -573,6 +574,32 @@ class PalindromePaySDK {
573
574
  validateSignature(signature, "wallet authorization signature");
574
575
  return signature;
575
576
  }
577
+ /**
578
+ * Verify a wallet authorization signature locally before submitting transaction
579
+ * This ensures the signature is cryptographically valid for the expected signer
580
+ */
581
+ async verifyWalletSignature(signature, walletAddress, escrowId, expectedSigner) {
582
+ try {
583
+ return await (0, viem_1.verifyTypedData)({
584
+ address: expectedSigner,
585
+ domain: this.getWalletDomain(walletAddress),
586
+ types: this.walletAuthorizationTypes,
587
+ primaryType: "WalletAuthorization",
588
+ message: {
589
+ escrowId,
590
+ wallet: walletAddress,
591
+ participant: expectedSigner,
592
+ },
593
+ signature,
594
+ });
595
+ }
596
+ catch (error) {
597
+ this.log('warn', 'Signature verification failed', {
598
+ error: error instanceof Error ? error.message : String(error),
599
+ });
600
+ return false;
601
+ }
602
+ }
576
603
  /**
577
604
  * Sign a confirm delivery message (for gasless meta-tx)
578
605
  */
@@ -644,17 +671,16 @@ class PalindromePaySDK {
644
671
  // ==========================================================================
645
672
  /**
646
673
  * Predict the wallet address for a given escrow ID (before creation)
674
+ * Calls the contract's computeWalletAddress function - single source of truth
647
675
  */
648
676
  async predictWalletAddress(escrowId) {
649
- // Use the contract's computeWalletAddress function for accurate prediction
650
- // This ensures the SDK always matches the contract's CREATE2 computation
651
- const walletAddress = await this.publicClient.readContract({
677
+ const predicted = await (0, actions_1.readContract)(this.publicClient, {
652
678
  address: this.contractAddress,
653
- abi: this.abiEscrow,
679
+ abi: PalindromePay_json_1.default.abi,
654
680
  functionName: "computeWalletAddress",
655
681
  args: [escrowId],
656
682
  });
657
- return walletAddress;
683
+ return predicted;
658
684
  }
659
685
  // ==========================================================================
660
686
  // ESCROW DATA READING
@@ -1045,6 +1071,11 @@ class PalindromePaySDK {
1045
1071
  const predictedWallet = await this.predictWalletAddress(nextId);
1046
1072
  // Sign wallet authorization
1047
1073
  const sellerWalletSig = await this.signWalletAuthorization(walletClient, predictedWallet, nextId);
1074
+ // Pre-validate signature before submitting transaction
1075
+ const isValidSig = await this.verifyWalletSignature(sellerWalletSig, predictedWallet, nextId, walletClient.account.address);
1076
+ if (!isValidSig) {
1077
+ throw new SDKError("Seller wallet signature is invalid - cannot create escrow", SDKErrorCode.SIGNATURE_INVALID);
1078
+ }
1048
1079
  // Create escrow
1049
1080
  const hash = await this.resilientWriteContract(walletClient, {
1050
1081
  address: this.contractAddress,
@@ -1073,7 +1104,30 @@ class PalindromePaySDK {
1073
1104
  });
1074
1105
  const escrowId = events[0]?.args?.escrowId ?? nextId;
1075
1106
  const deal = await this.getEscrowByIdParsed(escrowId);
1076
- return { escrowId, txHash: hash, walletAddress: deal.wallet };
1107
+ // Verify seller signature is valid on-chain after creation
1108
+ let sellerSigValid = false;
1109
+ try {
1110
+ sellerSigValid = await this.publicClient.readContract({
1111
+ address: deal.wallet,
1112
+ abi: this.abiWallet,
1113
+ functionName: "isSignatureValid",
1114
+ args: [walletClient.account.address],
1115
+ });
1116
+ }
1117
+ catch (error) {
1118
+ this.log('warn', 'Failed to verify seller signature after escrow creation', {
1119
+ escrowId: escrowId.toString(),
1120
+ error: error instanceof Error ? error.message : String(error),
1121
+ });
1122
+ }
1123
+ if (!sellerSigValid) {
1124
+ this.log('error', 'Seller wallet signature is invalid after escrow creation', {
1125
+ escrowId: escrowId.toString(),
1126
+ seller: walletClient.account.address,
1127
+ wallet: deal.wallet,
1128
+ });
1129
+ }
1130
+ return { escrowId, txHash: hash, walletAddress: deal.wallet, signatureValid: sellerSigValid };
1077
1131
  }
1078
1132
  /**
1079
1133
  * Create a new escrow and deposit funds as the buyer (single transaction)
@@ -1136,6 +1190,11 @@ class PalindromePaySDK {
1136
1190
  const predictedWallet = await this.predictWalletAddress(nextId);
1137
1191
  // Sign wallet authorization
1138
1192
  const buyerWalletSig = await this.signWalletAuthorization(walletClient, predictedWallet, nextId);
1193
+ // Pre-validate signature before submitting transaction
1194
+ const isValidSig = await this.verifyWalletSignature(buyerWalletSig, predictedWallet, nextId, walletClient.account.address);
1195
+ if (!isValidSig) {
1196
+ throw new SDKError("Buyer wallet signature is invalid - cannot create escrow", SDKErrorCode.SIGNATURE_INVALID);
1197
+ }
1139
1198
  // Create escrow and deposit
1140
1199
  const hash = await this.resilientWriteContract(walletClient, {
1141
1200
  address: this.contractAddress,
@@ -1164,7 +1223,30 @@ class PalindromePaySDK {
1164
1223
  });
1165
1224
  const escrowId = events[0]?.args?.escrowId ?? nextId;
1166
1225
  const deal = await this.getEscrowByIdParsed(escrowId);
1167
- return { escrowId, txHash: hash, walletAddress: deal.wallet };
1226
+ // Verify buyer signature is valid on-chain after creation
1227
+ let buyerSigValid = false;
1228
+ try {
1229
+ buyerSigValid = await this.publicClient.readContract({
1230
+ address: deal.wallet,
1231
+ abi: this.abiWallet,
1232
+ functionName: "isSignatureValid",
1233
+ args: [walletClient.account.address],
1234
+ });
1235
+ }
1236
+ catch (error) {
1237
+ this.log('warn', 'Failed to verify buyer signature after escrow creation', {
1238
+ escrowId: escrowId.toString(),
1239
+ error: error instanceof Error ? error.message : String(error),
1240
+ });
1241
+ }
1242
+ if (!buyerSigValid) {
1243
+ this.log('error', 'Buyer wallet signature is invalid after escrow creation', {
1244
+ escrowId: escrowId.toString(),
1245
+ buyer: walletClient.account.address,
1246
+ wallet: deal.wallet,
1247
+ });
1248
+ }
1249
+ return { escrowId, txHash: hash, walletAddress: deal.wallet, signatureValid: buyerSigValid };
1168
1250
  }
1169
1251
  // ==========================================================================
1170
1252
  // DEPOSIT
@@ -1181,7 +1263,7 @@ class PalindromePaySDK {
1181
1263
  *
1182
1264
  * @param walletClient - The buyer's wallet client (must have account connected)
1183
1265
  * @param escrowId - The escrow ID to deposit into
1184
- * @returns Transaction hash
1266
+ * @returns Object containing transaction hash and signature validity
1185
1267
  * @throws {SDKError} WALLET_NOT_CONNECTED - If wallet client is not connected
1186
1268
  * @throws {SDKError} NOT_BUYER - If caller is not the designated buyer
1187
1269
  * @throws {SDKError} INVALID_STATE - If escrow is not in AWAITING_PAYMENT state
@@ -1195,8 +1277,13 @@ class PalindromePaySDK {
1195
1277
  this.verifyState(deal, EscrowState.AWAITING_PAYMENT, "deposit");
1196
1278
  // Approve token spending
1197
1279
  await this.approveTokenIfNeeded(walletClient, deal.token, this.contractAddress, deal.amount);
1198
- // Sign wallet authorization
1280
+ // Sign wallet authorization using the wallet address from the escrow
1199
1281
  const buyerWalletSig = await this.signWalletAuthorization(walletClient, deal.wallet, escrowId);
1282
+ // Pre-validate signature before submitting transaction
1283
+ const isValidSig = await this.verifyWalletSignature(buyerWalletSig, deal.wallet, escrowId, walletClient.account.address);
1284
+ if (!isValidSig) {
1285
+ throw new SDKError("Buyer wallet signature is invalid - cannot deposit", SDKErrorCode.SIGNATURE_INVALID);
1286
+ }
1200
1287
  // Deposit
1201
1288
  const hash = await this.resilientWriteContract(walletClient, {
1202
1289
  address: this.contractAddress,
@@ -1205,7 +1292,30 @@ class PalindromePaySDK {
1205
1292
  args: [escrowId, buyerWalletSig],
1206
1293
  });
1207
1294
  await this.waitForReceipt(hash);
1208
- return hash;
1295
+ // Verify buyer signature is valid on-chain after deposit
1296
+ let buyerSigValid = false;
1297
+ try {
1298
+ buyerSigValid = await this.publicClient.readContract({
1299
+ address: deal.wallet,
1300
+ abi: this.abiWallet,
1301
+ functionName: "isSignatureValid",
1302
+ args: [walletClient.account.address],
1303
+ });
1304
+ }
1305
+ catch (error) {
1306
+ this.log('warn', 'Failed to verify buyer signature after deposit', {
1307
+ escrowId: escrowId.toString(),
1308
+ error: error instanceof Error ? error.message : String(error),
1309
+ });
1310
+ }
1311
+ if (!buyerSigValid) {
1312
+ this.log('error', 'Buyer wallet signature is invalid after deposit', {
1313
+ escrowId: escrowId.toString(),
1314
+ buyer: walletClient.account.address,
1315
+ wallet: deal.wallet,
1316
+ });
1317
+ }
1318
+ return { txHash: hash, signatureValid: buyerSigValid };
1209
1319
  }
1210
1320
  // ==========================================================================
1211
1321
  // ACCEPT ESCROW (for buyer-created escrows)
@@ -1221,7 +1331,7 @@ class PalindromePaySDK {
1221
1331
  *
1222
1332
  * @param walletClient - The seller's wallet client (must have account connected)
1223
1333
  * @param escrowId - The escrow ID to accept
1224
- * @returns Transaction hash
1334
+ * @returns Object containing transaction hash and signature validity
1225
1335
  * @throws {SDKError} WALLET_NOT_CONNECTED - If wallet client is not connected
1226
1336
  * @throws {SDKError} NOT_SELLER - If caller is not the designated seller
1227
1337
  * @throws {SDKError} INVALID_STATE - If escrow is not in AWAITING_DELIVERY state
@@ -1237,8 +1347,13 @@ class PalindromePaySDK {
1237
1347
  if (deal.sellerWalletSig && deal.sellerWalletSig !== "0x") {
1238
1348
  throw new SDKError("Escrow already accepted", SDKErrorCode.ALREADY_ACCEPTED);
1239
1349
  }
1240
- // Sign wallet authorization
1350
+ // Sign wallet authorization using the wallet address from the escrow
1241
1351
  const sellerWalletSig = await this.signWalletAuthorization(walletClient, deal.wallet, escrowId);
1352
+ // Pre-validate signature before submitting transaction
1353
+ const isValidSig = await this.verifyWalletSignature(sellerWalletSig, deal.wallet, escrowId, walletClient.account.address);
1354
+ if (!isValidSig) {
1355
+ throw new SDKError("Seller wallet signature is invalid - cannot accept escrow", SDKErrorCode.SIGNATURE_INVALID);
1356
+ }
1242
1357
  // Accept escrow
1243
1358
  const hash = await this.resilientWriteContract(walletClient, {
1244
1359
  address: this.contractAddress,
@@ -1247,7 +1362,30 @@ class PalindromePaySDK {
1247
1362
  args: [escrowId, sellerWalletSig],
1248
1363
  });
1249
1364
  await this.waitForReceipt(hash);
1250
- return hash;
1365
+ // Verify seller signature is valid on-chain after accept
1366
+ let sellerSigValid = false;
1367
+ try {
1368
+ sellerSigValid = await this.publicClient.readContract({
1369
+ address: deal.wallet,
1370
+ abi: this.abiWallet,
1371
+ functionName: "isSignatureValid",
1372
+ args: [walletClient.account.address],
1373
+ });
1374
+ }
1375
+ catch (error) {
1376
+ this.log('warn', 'Failed to verify seller signature after accept', {
1377
+ escrowId: escrowId.toString(),
1378
+ error: error instanceof Error ? error.message : String(error),
1379
+ });
1380
+ }
1381
+ if (!sellerSigValid) {
1382
+ this.log('error', 'Seller wallet signature is invalid after accept', {
1383
+ escrowId: escrowId.toString(),
1384
+ seller: walletClient.account.address,
1385
+ wallet: deal.wallet,
1386
+ });
1387
+ }
1388
+ return { txHash: hash, signatureValid: sellerSigValid };
1251
1389
  }
1252
1390
  // ==========================================================================
1253
1391
  // CONFIRM DELIVERY
@@ -1278,6 +1416,11 @@ class PalindromePaySDK {
1278
1416
  this.verifyState(deal, EscrowState.AWAITING_DELIVERY, "confirm delivery");
1279
1417
  // Sign wallet authorization
1280
1418
  const buyerWalletSig = await this.signWalletAuthorization(walletClient, deal.wallet, escrowId);
1419
+ // Pre-validate signature before submitting transaction
1420
+ const isValidSig = await this.verifyWalletSignature(buyerWalletSig, deal.wallet, escrowId, walletClient.account.address);
1421
+ if (!isValidSig) {
1422
+ throw new SDKError("Buyer wallet signature is invalid - cannot confirm delivery", SDKErrorCode.SIGNATURE_INVALID);
1423
+ }
1281
1424
  // Confirm delivery
1282
1425
  const hash = await this.resilientWriteContract(walletClient, {
1283
1426
  address: this.contractAddress,
@@ -1389,6 +1532,11 @@ class PalindromePaySDK {
1389
1532
  }
1390
1533
  // Sign wallet authorization
1391
1534
  const walletSig = await this.signWalletAuthorization(walletClient, deal.wallet, escrowId);
1535
+ // Pre-validate signature before submitting transaction
1536
+ const isValidSig = await this.verifyWalletSignature(walletSig, deal.wallet, escrowId, walletClient.account.address);
1537
+ if (!isValidSig) {
1538
+ throw new SDKError("Wallet signature is invalid - cannot request cancel", SDKErrorCode.SIGNATURE_INVALID);
1539
+ }
1392
1540
  // Request cancel
1393
1541
  const hash = await this.resilientWriteContract(walletClient, {
1394
1542
  address: this.contractAddress,
@@ -1626,6 +1774,11 @@ class PalindromePaySDK {
1626
1774
  this.verifyArbiter(walletClient.account.address, deal);
1627
1775
  // Sign wallet authorization
1628
1776
  const arbiterWalletSig = await this.signWalletAuthorization(walletClient, deal.wallet, escrowId);
1777
+ // Pre-validate signature before submitting transaction
1778
+ const isValidSig = await this.verifyWalletSignature(arbiterWalletSig, deal.wallet, escrowId, walletClient.account.address);
1779
+ if (!isValidSig) {
1780
+ throw new SDKError("Arbiter wallet signature is invalid - cannot submit decision", SDKErrorCode.SIGNATURE_INVALID);
1781
+ }
1629
1782
  // Submit decision
1630
1783
  const hash = await this.resilientWriteContract(walletClient, {
1631
1784
  address: this.contractAddress,