@palindromepay/sdk 1.9.9 → 2.0.0

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
+ * Uses CREATE2 address computation: keccak256(0xff ++ deployer ++ salt ++ initCodeHash)
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,28 @@ class PalindromePaySDK {
644
671
  // ==========================================================================
645
672
  /**
646
673
  * Predict the wallet address for a given escrow ID (before creation)
674
+ * Uses CREATE2 address computation: keccak256(0xff ++ deployer ++ salt ++ initCodeHash)
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({
652
- address: this.contractAddress,
653
- abi: this.abiEscrow,
654
- functionName: "computeWalletAddress",
655
- args: [escrowId],
656
- });
657
- return walletAddress;
677
+ // Salt is keccak256 of the escrowId padded to 32 bytes
678
+ const salt = (0, viem_1.keccak256)((0, viem_1.encodeAbiParameters)([{ type: "uint256" }], [escrowId]));
679
+ // Get wallet bytecode from the ABI JSON
680
+ const walletBytecode = PalindromePayWallet_json_1.default.bytecode;
681
+ // Encode constructor arguments: (address _escrowContract, uint256 _escrowId)
682
+ const encodedArgs = (0, viem_1.encodeAbiParameters)([{ type: "address" }, { type: "uint256" }], [this.contractAddress, escrowId]);
683
+ // initCode = bytecode + encoded constructor args
684
+ const initCode = (0, viem_1.concat)([walletBytecode, encodedArgs]);
685
+ const initCodeHash = (0, viem_1.keccak256)(initCode);
686
+ // CREATE2 address: keccak256(0xff ++ deployer ++ salt ++ initCodeHash)[12:]
687
+ const data = (0, viem_1.concat)([
688
+ "0xff",
689
+ this.contractAddress,
690
+ salt,
691
+ initCodeHash,
692
+ ]);
693
+ const hash = (0, viem_1.keccak256)(data);
694
+ // Take the last 20 bytes (address is derived from bytes 12-31)
695
+ return (0, viem_1.getAddress)(`0x${hash.slice(26)}`);
658
696
  }
659
697
  // ==========================================================================
660
698
  // ESCROW DATA READING
@@ -1045,6 +1083,11 @@ class PalindromePaySDK {
1045
1083
  const predictedWallet = await this.predictWalletAddress(nextId);
1046
1084
  // Sign wallet authorization
1047
1085
  const sellerWalletSig = await this.signWalletAuthorization(walletClient, predictedWallet, nextId);
1086
+ // Pre-validate signature before submitting transaction
1087
+ const isValidSig = await this.verifyWalletSignature(sellerWalletSig, predictedWallet, nextId, walletClient.account.address);
1088
+ if (!isValidSig) {
1089
+ throw new SDKError("Seller wallet signature is invalid - cannot create escrow", SDKErrorCode.SIGNATURE_INVALID);
1090
+ }
1048
1091
  // Create escrow
1049
1092
  const hash = await this.resilientWriteContract(walletClient, {
1050
1093
  address: this.contractAddress,
@@ -1073,7 +1116,30 @@ class PalindromePaySDK {
1073
1116
  });
1074
1117
  const escrowId = events[0]?.args?.escrowId ?? nextId;
1075
1118
  const deal = await this.getEscrowByIdParsed(escrowId);
1076
- return { escrowId, txHash: hash, walletAddress: deal.wallet };
1119
+ // Verify seller signature is valid on-chain after creation
1120
+ let sellerSigValid = false;
1121
+ try {
1122
+ sellerSigValid = await this.publicClient.readContract({
1123
+ address: deal.wallet,
1124
+ abi: this.abiWallet,
1125
+ functionName: "isSignatureValid",
1126
+ args: [walletClient.account.address],
1127
+ });
1128
+ }
1129
+ catch (error) {
1130
+ this.log('warn', 'Failed to verify seller signature after escrow creation', {
1131
+ escrowId: escrowId.toString(),
1132
+ error: error instanceof Error ? error.message : String(error),
1133
+ });
1134
+ }
1135
+ if (!sellerSigValid) {
1136
+ this.log('error', 'Seller wallet signature is invalid after escrow creation', {
1137
+ escrowId: escrowId.toString(),
1138
+ seller: walletClient.account.address,
1139
+ wallet: deal.wallet,
1140
+ });
1141
+ }
1142
+ return { escrowId, txHash: hash, walletAddress: deal.wallet, signatureValid: sellerSigValid };
1077
1143
  }
1078
1144
  /**
1079
1145
  * Create a new escrow and deposit funds as the buyer (single transaction)
@@ -1136,6 +1202,11 @@ class PalindromePaySDK {
1136
1202
  const predictedWallet = await this.predictWalletAddress(nextId);
1137
1203
  // Sign wallet authorization
1138
1204
  const buyerWalletSig = await this.signWalletAuthorization(walletClient, predictedWallet, nextId);
1205
+ // Pre-validate signature before submitting transaction
1206
+ const isValidSig = await this.verifyWalletSignature(buyerWalletSig, predictedWallet, nextId, walletClient.account.address);
1207
+ if (!isValidSig) {
1208
+ throw new SDKError("Buyer wallet signature is invalid - cannot create escrow", SDKErrorCode.SIGNATURE_INVALID);
1209
+ }
1139
1210
  // Create escrow and deposit
1140
1211
  const hash = await this.resilientWriteContract(walletClient, {
1141
1212
  address: this.contractAddress,
@@ -1164,7 +1235,30 @@ class PalindromePaySDK {
1164
1235
  });
1165
1236
  const escrowId = events[0]?.args?.escrowId ?? nextId;
1166
1237
  const deal = await this.getEscrowByIdParsed(escrowId);
1167
- return { escrowId, txHash: hash, walletAddress: deal.wallet };
1238
+ // Verify buyer signature is valid on-chain after creation
1239
+ let buyerSigValid = false;
1240
+ try {
1241
+ buyerSigValid = await this.publicClient.readContract({
1242
+ address: deal.wallet,
1243
+ abi: this.abiWallet,
1244
+ functionName: "isSignatureValid",
1245
+ args: [walletClient.account.address],
1246
+ });
1247
+ }
1248
+ catch (error) {
1249
+ this.log('warn', 'Failed to verify buyer signature after escrow creation', {
1250
+ escrowId: escrowId.toString(),
1251
+ error: error instanceof Error ? error.message : String(error),
1252
+ });
1253
+ }
1254
+ if (!buyerSigValid) {
1255
+ this.log('error', 'Buyer wallet signature is invalid after escrow creation', {
1256
+ escrowId: escrowId.toString(),
1257
+ buyer: walletClient.account.address,
1258
+ wallet: deal.wallet,
1259
+ });
1260
+ }
1261
+ return { escrowId, txHash: hash, walletAddress: deal.wallet, signatureValid: buyerSigValid };
1168
1262
  }
1169
1263
  // ==========================================================================
1170
1264
  // DEPOSIT
@@ -1181,7 +1275,7 @@ class PalindromePaySDK {
1181
1275
  *
1182
1276
  * @param walletClient - The buyer's wallet client (must have account connected)
1183
1277
  * @param escrowId - The escrow ID to deposit into
1184
- * @returns Transaction hash
1278
+ * @returns Object containing transaction hash and signature validity
1185
1279
  * @throws {SDKError} WALLET_NOT_CONNECTED - If wallet client is not connected
1186
1280
  * @throws {SDKError} NOT_BUYER - If caller is not the designated buyer
1187
1281
  * @throws {SDKError} INVALID_STATE - If escrow is not in AWAITING_PAYMENT state
@@ -1195,8 +1289,13 @@ class PalindromePaySDK {
1195
1289
  this.verifyState(deal, EscrowState.AWAITING_PAYMENT, "deposit");
1196
1290
  // Approve token spending
1197
1291
  await this.approveTokenIfNeeded(walletClient, deal.token, this.contractAddress, deal.amount);
1198
- // Sign wallet authorization
1292
+ // Sign wallet authorization using the wallet address from the escrow
1199
1293
  const buyerWalletSig = await this.signWalletAuthorization(walletClient, deal.wallet, escrowId);
1294
+ // Pre-validate signature before submitting transaction
1295
+ const isValidSig = await this.verifyWalletSignature(buyerWalletSig, deal.wallet, escrowId, walletClient.account.address);
1296
+ if (!isValidSig) {
1297
+ throw new SDKError("Buyer wallet signature is invalid - cannot deposit", SDKErrorCode.SIGNATURE_INVALID);
1298
+ }
1200
1299
  // Deposit
1201
1300
  const hash = await this.resilientWriteContract(walletClient, {
1202
1301
  address: this.contractAddress,
@@ -1205,7 +1304,30 @@ class PalindromePaySDK {
1205
1304
  args: [escrowId, buyerWalletSig],
1206
1305
  });
1207
1306
  await this.waitForReceipt(hash);
1208
- return hash;
1307
+ // Verify buyer signature is valid on-chain after deposit
1308
+ let buyerSigValid = false;
1309
+ try {
1310
+ buyerSigValid = await this.publicClient.readContract({
1311
+ address: deal.wallet,
1312
+ abi: this.abiWallet,
1313
+ functionName: "isSignatureValid",
1314
+ args: [walletClient.account.address],
1315
+ });
1316
+ }
1317
+ catch (error) {
1318
+ this.log('warn', 'Failed to verify buyer signature after deposit', {
1319
+ escrowId: escrowId.toString(),
1320
+ error: error instanceof Error ? error.message : String(error),
1321
+ });
1322
+ }
1323
+ if (!buyerSigValid) {
1324
+ this.log('error', 'Buyer wallet signature is invalid after deposit', {
1325
+ escrowId: escrowId.toString(),
1326
+ buyer: walletClient.account.address,
1327
+ wallet: deal.wallet,
1328
+ });
1329
+ }
1330
+ return { txHash: hash, signatureValid: buyerSigValid };
1209
1331
  }
1210
1332
  // ==========================================================================
1211
1333
  // ACCEPT ESCROW (for buyer-created escrows)
@@ -1221,7 +1343,7 @@ class PalindromePaySDK {
1221
1343
  *
1222
1344
  * @param walletClient - The seller's wallet client (must have account connected)
1223
1345
  * @param escrowId - The escrow ID to accept
1224
- * @returns Transaction hash
1346
+ * @returns Object containing transaction hash and signature validity
1225
1347
  * @throws {SDKError} WALLET_NOT_CONNECTED - If wallet client is not connected
1226
1348
  * @throws {SDKError} NOT_SELLER - If caller is not the designated seller
1227
1349
  * @throws {SDKError} INVALID_STATE - If escrow is not in AWAITING_DELIVERY state
@@ -1237,8 +1359,13 @@ class PalindromePaySDK {
1237
1359
  if (deal.sellerWalletSig && deal.sellerWalletSig !== "0x") {
1238
1360
  throw new SDKError("Escrow already accepted", SDKErrorCode.ALREADY_ACCEPTED);
1239
1361
  }
1240
- // Sign wallet authorization
1362
+ // Sign wallet authorization using the wallet address from the escrow
1241
1363
  const sellerWalletSig = await this.signWalletAuthorization(walletClient, deal.wallet, escrowId);
1364
+ // Pre-validate signature before submitting transaction
1365
+ const isValidSig = await this.verifyWalletSignature(sellerWalletSig, deal.wallet, escrowId, walletClient.account.address);
1366
+ if (!isValidSig) {
1367
+ throw new SDKError("Seller wallet signature is invalid - cannot accept escrow", SDKErrorCode.SIGNATURE_INVALID);
1368
+ }
1242
1369
  // Accept escrow
1243
1370
  const hash = await this.resilientWriteContract(walletClient, {
1244
1371
  address: this.contractAddress,
@@ -1247,7 +1374,30 @@ class PalindromePaySDK {
1247
1374
  args: [escrowId, sellerWalletSig],
1248
1375
  });
1249
1376
  await this.waitForReceipt(hash);
1250
- return hash;
1377
+ // Verify seller signature is valid on-chain after accept
1378
+ let sellerSigValid = false;
1379
+ try {
1380
+ sellerSigValid = await this.publicClient.readContract({
1381
+ address: deal.wallet,
1382
+ abi: this.abiWallet,
1383
+ functionName: "isSignatureValid",
1384
+ args: [walletClient.account.address],
1385
+ });
1386
+ }
1387
+ catch (error) {
1388
+ this.log('warn', 'Failed to verify seller signature after accept', {
1389
+ escrowId: escrowId.toString(),
1390
+ error: error instanceof Error ? error.message : String(error),
1391
+ });
1392
+ }
1393
+ if (!sellerSigValid) {
1394
+ this.log('error', 'Seller wallet signature is invalid after accept', {
1395
+ escrowId: escrowId.toString(),
1396
+ seller: walletClient.account.address,
1397
+ wallet: deal.wallet,
1398
+ });
1399
+ }
1400
+ return { txHash: hash, signatureValid: sellerSigValid };
1251
1401
  }
1252
1402
  // ==========================================================================
1253
1403
  // CONFIRM DELIVERY
@@ -1278,6 +1428,11 @@ class PalindromePaySDK {
1278
1428
  this.verifyState(deal, EscrowState.AWAITING_DELIVERY, "confirm delivery");
1279
1429
  // Sign wallet authorization
1280
1430
  const buyerWalletSig = await this.signWalletAuthorization(walletClient, deal.wallet, escrowId);
1431
+ // Pre-validate signature before submitting transaction
1432
+ const isValidSig = await this.verifyWalletSignature(buyerWalletSig, deal.wallet, escrowId, walletClient.account.address);
1433
+ if (!isValidSig) {
1434
+ throw new SDKError("Buyer wallet signature is invalid - cannot confirm delivery", SDKErrorCode.SIGNATURE_INVALID);
1435
+ }
1281
1436
  // Confirm delivery
1282
1437
  const hash = await this.resilientWriteContract(walletClient, {
1283
1438
  address: this.contractAddress,
@@ -1389,6 +1544,11 @@ class PalindromePaySDK {
1389
1544
  }
1390
1545
  // Sign wallet authorization
1391
1546
  const walletSig = await this.signWalletAuthorization(walletClient, deal.wallet, escrowId);
1547
+ // Pre-validate signature before submitting transaction
1548
+ const isValidSig = await this.verifyWalletSignature(walletSig, deal.wallet, escrowId, walletClient.account.address);
1549
+ if (!isValidSig) {
1550
+ throw new SDKError("Wallet signature is invalid - cannot request cancel", SDKErrorCode.SIGNATURE_INVALID);
1551
+ }
1392
1552
  // Request cancel
1393
1553
  const hash = await this.resilientWriteContract(walletClient, {
1394
1554
  address: this.contractAddress,
@@ -1626,6 +1786,11 @@ class PalindromePaySDK {
1626
1786
  this.verifyArbiter(walletClient.account.address, deal);
1627
1787
  // Sign wallet authorization
1628
1788
  const arbiterWalletSig = await this.signWalletAuthorization(walletClient, deal.wallet, escrowId);
1789
+ // Pre-validate signature before submitting transaction
1790
+ const isValidSig = await this.verifyWalletSignature(arbiterWalletSig, deal.wallet, escrowId, walletClient.account.address);
1791
+ if (!isValidSig) {
1792
+ throw new SDKError("Arbiter wallet signature is invalid - cannot submit decision", SDKErrorCode.SIGNATURE_INVALID);
1793
+ }
1629
1794
  // Submit decision
1630
1795
  const hash = await this.resilientWriteContract(walletClient, {
1631
1796
  address: this.contractAddress,