@palindromepay/sdk 2.1.7 → 2.1.9

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.
@@ -101,6 +101,8 @@ export interface PalindromePaySDKConfig {
101
101
  /** Apollo client for subgraph queries (required) */
102
102
  apolloClient: ApolloClient;
103
103
  chain?: Chain;
104
+ /** Use Base Sepolia testnet contract (default: false = Base mainnet) */
105
+ testnet?: boolean;
104
106
  /** Cache TTL in milliseconds (default: 5000) */
105
107
  cacheTTL?: number;
106
108
  /** Maximum cache entries before LRU eviction (default: 1000) */
@@ -267,6 +269,14 @@ export declare class PalindromePaySDK {
267
269
  * Execute an async operation with retry logic.
268
270
  */
269
271
  private withRetry;
272
+ /**
273
+ * Execute a contract read with retry on transient RPC failures.
274
+ *
275
+ * Idempotent reads are safe to retry, so they honour the same
276
+ * enableRetry/maxRetries/retryDelay config as writes. On a persistent
277
+ * failure the original error is propagated unchanged (no API change).
278
+ */
279
+ private read;
270
280
  /**
271
281
  * Extract error message from unknown error type.
272
282
  */
@@ -358,13 +368,35 @@ export declare class PalindromePaySDK {
358
368
  */
359
369
  predictWalletAddress(escrowId: bigint): Promise<Address>;
360
370
  /**
361
- * Get raw escrow data from contract
371
+ * Build the LRU cache key for an escrow's raw on-chain data.
372
+ */
373
+ private escrowCacheKey;
374
+ /**
375
+ * Drop all cached data for an escrow. Called after any write that may
376
+ * change the escrow's on-chain state so subsequent reads from this SDK
377
+ * instance never observe stale data.
378
+ */
379
+ private invalidateEscrow;
380
+ /**
381
+ * Get raw escrow data from contract.
382
+ *
383
+ * Results are cached in the LRU cache for `cacheTTL` ms to de-duplicate
384
+ * rapid repeated reads (e.g. a UI rendering several components for the same
385
+ * escrow). Writes that mutate the escrow invalidate this entry, and callers
386
+ * that require strictly fresh data (all write pre-flight checks) pass
387
+ * `forceRefresh = true`.
388
+ *
389
+ * @param escrowId - The escrow ID
390
+ * @param forceRefresh - Bypass the cache and read fresh from chain
362
391
  */
363
- getEscrowById(escrowId: bigint): Promise<RawEscrowData>;
392
+ getEscrowById(escrowId: bigint, forceRefresh?: boolean): Promise<RawEscrowData>;
364
393
  /**
365
- * Get parsed escrow data
394
+ * Get parsed escrow data.
395
+ *
396
+ * @param escrowId - The escrow ID
397
+ * @param forceRefresh - Bypass the cache and read fresh from chain
366
398
  */
367
- getEscrowByIdParsed(escrowId: bigint): Promise<EscrowData>;
399
+ getEscrowByIdParsed(escrowId: bigint, forceRefresh?: boolean): Promise<EscrowData>;
368
400
  /**
369
401
  * Get next escrow ID
370
402
  */
@@ -249,7 +249,9 @@ class PalindromePaySDK {
249
249
  };
250
250
  /** Cached fee basis points (lazily computed from contract) */
251
251
  this.cachedFeeBps = null;
252
- this.contractAddress = config_1.CONFIG.DEFAULT_CONTRACT_ADDRESS;
252
+ this.contractAddress = config.testnet
253
+ ? config_1.CONFIG.TESTNET_CONTRACT_ADDRESS
254
+ : config_1.CONFIG.CONTRACT_ADDRESS;
253
255
  this.abiEscrow = PalindromePay_json_1.default.abi;
254
256
  this.abiWallet = PalindromePayWallet_json_1.default.abi;
255
257
  this.abiERC20 = USDT_json_1.default.abi;
@@ -335,21 +337,24 @@ class PalindromePaySDK {
335
337
  * Wait for transaction receipt with timeout and retry logic.
336
338
  */
337
339
  async waitForReceipt(hash) {
338
- const receipt = await this.withRetry(async () => {
339
- try {
340
- return await this.publicClient.waitForTransactionReceipt({
341
- hash,
342
- timeout: this.receiptTimeout,
343
- });
344
- }
345
- catch (error) {
346
- const errorMessage = isViemError(error) ? error.message : String(error);
347
- if (errorMessage.includes("timed out") || errorMessage.includes("timeout")) {
348
- throw new SDKError(`Transaction receipt timeout after ${this.receiptTimeout}ms`, SDKErrorCode.TRANSACTION_FAILED, { hash });
349
- }
350
- throw error;
340
+ // Retry the raw receipt wait so transient timeouts get another attempt.
341
+ // The timeout is only converted to a (non-retryable) SDKError after all
342
+ // attempts are exhausted — throwing the SDKError inside withRetry would
343
+ // short-circuit the retry loop (see withRetry's errorName === "SDKError" guard).
344
+ let receipt;
345
+ try {
346
+ receipt = await this.withRetry(() => this.publicClient.waitForTransactionReceipt({
347
+ hash,
348
+ timeout: this.receiptTimeout,
349
+ }), "waitForTransactionReceipt");
350
+ }
351
+ catch (error) {
352
+ const errorMessage = isViemError(error) ? error.message : String(error);
353
+ if (errorMessage.includes("timed out") || errorMessage.includes("timeout")) {
354
+ throw new SDKError(`Transaction receipt timeout after ${this.receiptTimeout}ms`, SDKErrorCode.TRANSACTION_FAILED, { hash });
351
355
  }
352
- }, "waitForTransactionReceipt");
356
+ throw error;
357
+ }
353
358
  // Check receipt status (post-Byzantium: status 'reverted' means on-chain failure)
354
359
  if (receipt.status === "reverted") {
355
360
  throw new SDKError(`Transaction reverted on-chain (hash: ${hash})`, SDKErrorCode.TRANSACTION_FAILED, { hash, blockNumber: receipt.blockNumber });
@@ -386,6 +391,16 @@ class PalindromePaySDK {
386
391
  }
387
392
  throw lastError ?? new SDKError(`${operationName} failed after ${this.maxRetries} attempts`, SDKErrorCode.RPC_ERROR);
388
393
  }
394
+ /**
395
+ * Execute a contract read with retry on transient RPC failures.
396
+ *
397
+ * Idempotent reads are safe to retry, so they honour the same
398
+ * enableRetry/maxRetries/retryDelay config as writes. On a persistent
399
+ * failure the original error is propagated unchanged (no API change).
400
+ */
401
+ async read(params) {
402
+ return this.withRetry(async () => (await (0, actions_1.readContract)(this.publicClient, params)), `read:${params.functionName}`);
403
+ }
389
404
  /**
390
405
  * Extract error message from unknown error type.
391
406
  */
@@ -688,34 +703,67 @@ class PalindromePaySDK {
688
703
  * Calls the contract's computeWalletAddress function - single source of truth
689
704
  */
690
705
  async predictWalletAddress(escrowId) {
691
- const predicted = await (0, actions_1.readContract)(this.publicClient, {
706
+ return this.read({
692
707
  address: this.contractAddress,
693
708
  abi: PalindromePay_json_1.default.abi,
694
709
  functionName: "computeWalletAddress",
695
710
  args: [escrowId],
696
711
  });
697
- return predicted;
698
712
  }
699
713
  // ==========================================================================
700
714
  // ESCROW DATA READING
701
715
  // ==========================================================================
702
716
  /**
703
- * Get raw escrow data from contract
717
+ * Build the LRU cache key for an escrow's raw on-chain data.
704
718
  */
705
- async getEscrowById(escrowId) {
706
- const raw = await (0, actions_1.readContract)(this.publicClient, {
719
+ escrowCacheKey(escrowId) {
720
+ return `escrow-${escrowId}`;
721
+ }
722
+ /**
723
+ * Drop all cached data for an escrow. Called after any write that may
724
+ * change the escrow's on-chain state so subsequent reads from this SDK
725
+ * instance never observe stale data.
726
+ */
727
+ invalidateEscrow(escrowId) {
728
+ this.escrowCache.delete(this.escrowCacheKey(escrowId));
729
+ this.escrowCache.delete(`status-${escrowId}`);
730
+ }
731
+ /**
732
+ * Get raw escrow data from contract.
733
+ *
734
+ * Results are cached in the LRU cache for `cacheTTL` ms to de-duplicate
735
+ * rapid repeated reads (e.g. a UI rendering several components for the same
736
+ * escrow). Writes that mutate the escrow invalidate this entry, and callers
737
+ * that require strictly fresh data (all write pre-flight checks) pass
738
+ * `forceRefresh = true`.
739
+ *
740
+ * @param escrowId - The escrow ID
741
+ * @param forceRefresh - Bypass the cache and read fresh from chain
742
+ */
743
+ async getEscrowById(escrowId, forceRefresh = false) {
744
+ const cacheKey = this.escrowCacheKey(escrowId);
745
+ if (!forceRefresh) {
746
+ const cached = this.getCacheValue(cacheKey);
747
+ if (cached)
748
+ return cached;
749
+ }
750
+ const raw = await this.read({
707
751
  address: this.contractAddress,
708
752
  abi: this.abiEscrow,
709
753
  functionName: "getEscrow",
710
754
  args: [escrowId],
711
755
  });
756
+ this.setCacheValue(cacheKey, raw);
712
757
  return raw;
713
758
  }
714
759
  /**
715
- * Get parsed escrow data
760
+ * Get parsed escrow data.
761
+ *
762
+ * @param escrowId - The escrow ID
763
+ * @param forceRefresh - Bypass the cache and read fresh from chain
716
764
  */
717
- async getEscrowByIdParsed(escrowId) {
718
- const raw = await this.getEscrowById(escrowId);
765
+ async getEscrowByIdParsed(escrowId, forceRefresh = false) {
766
+ const raw = await this.getEscrowById(escrowId, forceRefresh);
719
767
  return {
720
768
  token: raw.token,
721
769
  buyer: raw.buyer,
@@ -739,7 +787,7 @@ class PalindromePaySDK {
739
787
  * Get next escrow ID
740
788
  */
741
789
  async getNextEscrowId() {
742
- return (0, actions_1.readContract)(this.publicClient, {
790
+ return this.read({
743
791
  address: this.contractAddress,
744
792
  abi: this.abiEscrow,
745
793
  functionName: "nextEscrowId",
@@ -759,7 +807,7 @@ class PalindromePaySDK {
759
807
  * @returns The bitmap as a bigint
760
808
  */
761
809
  async getNonceBitmap(escrowId, signer, wordIndex = 0n) {
762
- return this.publicClient.readContract({
810
+ return this.read({
763
811
  address: this.contractAddress,
764
812
  abi: this.abiEscrow,
765
813
  functionName: "getNonceBitmap",
@@ -959,7 +1007,7 @@ class PalindromePaySDK {
959
1007
  * Get dispute submission status
960
1008
  */
961
1009
  async getDisputeSubmissionStatus(escrowId) {
962
- const status = await this.publicClient.readContract({
1010
+ const status = await this.read({
963
1011
  address: this.contractAddress,
964
1012
  abi: this.abiEscrow,
965
1013
  functionName: "disputeStatus",
@@ -977,7 +1025,7 @@ class PalindromePaySDK {
977
1025
  if (this.tokenDecimalsCache.has(tokenAddress)) {
978
1026
  return this.tokenDecimalsCache.get(tokenAddress);
979
1027
  }
980
- const decimals = await this.publicClient.readContract({
1028
+ const decimals = await this.read({
981
1029
  address: tokenAddress,
982
1030
  abi: this.abiERC20,
983
1031
  functionName: "decimals",
@@ -986,7 +1034,7 @@ class PalindromePaySDK {
986
1034
  return decimals;
987
1035
  }
988
1036
  async getTokenBalance(account, tokenAddress) {
989
- return this.publicClient.readContract({
1037
+ return this.read({
990
1038
  address: tokenAddress,
991
1039
  abi: this.abiERC20,
992
1040
  functionName: "balanceOf",
@@ -994,7 +1042,7 @@ class PalindromePaySDK {
994
1042
  });
995
1043
  }
996
1044
  async getTokenAllowance(owner, spender, tokenAddress) {
997
- return this.publicClient.readContract({
1045
+ return this.read({
998
1046
  address: tokenAddress,
999
1047
  abi: this.abiERC20,
1000
1048
  functionName: "allowance",
@@ -1299,7 +1347,7 @@ class PalindromePaySDK {
1299
1347
  */
1300
1348
  async deposit(walletClient, escrowId) {
1301
1349
  assertWalletClient(walletClient);
1302
- const deal = await this.getEscrowByIdParsed(escrowId);
1350
+ const deal = await this.getEscrowByIdParsed(escrowId, true);
1303
1351
  // Verify caller and state using helpers
1304
1352
  this.verifyBuyer(walletClient.account.address, deal);
1305
1353
  this.verifyState(deal, EscrowState.AWAITING_PAYMENT, "deposit");
@@ -1320,6 +1368,7 @@ class PalindromePaySDK {
1320
1368
  args: [escrowId, buyerWalletSig],
1321
1369
  });
1322
1370
  await this.waitForReceipt(hash);
1371
+ this.invalidateEscrow(escrowId);
1323
1372
  // Verify buyer signature is valid on-chain after deposit
1324
1373
  let buyerSigValid = false;
1325
1374
  try {
@@ -1367,7 +1416,7 @@ class PalindromePaySDK {
1367
1416
  */
1368
1417
  async acceptEscrow(walletClient, escrowId) {
1369
1418
  assertWalletClient(walletClient);
1370
- const deal = await this.getEscrowByIdParsed(escrowId);
1419
+ const deal = await this.getEscrowByIdParsed(escrowId, true);
1371
1420
  // Verify caller and state using helpers
1372
1421
  this.verifySeller(walletClient.account.address, deal);
1373
1422
  this.verifyState(deal, EscrowState.AWAITING_DELIVERY, "accept");
@@ -1390,6 +1439,7 @@ class PalindromePaySDK {
1390
1439
  args: [escrowId, sellerWalletSig],
1391
1440
  });
1392
1441
  await this.waitForReceipt(hash);
1442
+ this.invalidateEscrow(escrowId);
1393
1443
  // Verify seller signature is valid on-chain after accept
1394
1444
  let sellerSigValid = false;
1395
1445
  try {
@@ -1438,7 +1488,7 @@ class PalindromePaySDK {
1438
1488
  */
1439
1489
  async confirmDelivery(walletClient, escrowId) {
1440
1490
  assertWalletClient(walletClient);
1441
- const deal = await this.getEscrowByIdParsed(escrowId);
1491
+ const deal = await this.getEscrowByIdParsed(escrowId, true);
1442
1492
  // Verify caller and state using helpers
1443
1493
  this.verifyBuyer(walletClient.account.address, deal);
1444
1494
  this.verifyState(deal, EscrowState.AWAITING_DELIVERY, "confirm delivery");
@@ -1457,6 +1507,7 @@ class PalindromePaySDK {
1457
1507
  args: [escrowId, buyerWalletSig],
1458
1508
  });
1459
1509
  await this.waitForReceipt(hash);
1510
+ this.invalidateEscrow(escrowId);
1460
1511
  return hash;
1461
1512
  }
1462
1513
  /**
@@ -1490,6 +1541,7 @@ class PalindromePaySDK {
1490
1541
  args: [escrowId, coordSignature, deadline, nonce, buyerWalletSig],
1491
1542
  });
1492
1543
  await this.waitForReceipt(hash);
1544
+ this.invalidateEscrow(escrowId);
1493
1545
  return hash;
1494
1546
  }
1495
1547
  /**
@@ -1547,7 +1599,7 @@ class PalindromePaySDK {
1547
1599
  */
1548
1600
  async requestCancel(walletClient, escrowId) {
1549
1601
  assertWalletClient(walletClient);
1550
- const deal = await this.getEscrowByIdParsed(escrowId);
1602
+ const deal = await this.getEscrowByIdParsed(escrowId, true);
1551
1603
  // Verify caller is buyer or seller
1552
1604
  const isBuyer = addressEquals(walletClient.account.address, deal.buyer);
1553
1605
  const isSeller = addressEquals(walletClient.account.address, deal.seller);
@@ -1573,6 +1625,7 @@ class PalindromePaySDK {
1573
1625
  args: [escrowId, walletSig],
1574
1626
  });
1575
1627
  await this.waitForReceipt(hash);
1628
+ this.invalidateEscrow(escrowId);
1576
1629
  return hash;
1577
1630
  }
1578
1631
  /**
@@ -1596,7 +1649,7 @@ class PalindromePaySDK {
1596
1649
  */
1597
1650
  async cancelByTimeout(walletClient, escrowId) {
1598
1651
  assertWalletClient(walletClient);
1599
- const deal = await this.getEscrowByIdParsed(escrowId);
1652
+ const deal = await this.getEscrowByIdParsed(escrowId, true);
1600
1653
  // Verify caller using helper
1601
1654
  this.verifyBuyer(walletClient.account.address, deal);
1602
1655
  // Cancel by timeout
@@ -1607,6 +1660,7 @@ class PalindromePaySDK {
1607
1660
  args: [escrowId],
1608
1661
  });
1609
1662
  await this.waitForReceipt(hash);
1663
+ this.invalidateEscrow(escrowId);
1610
1664
  return hash;
1611
1665
  }
1612
1666
  /**
@@ -1636,7 +1690,7 @@ class PalindromePaySDK {
1636
1690
  */
1637
1691
  async autoRelease(walletClient, escrowId) {
1638
1692
  assertWalletClient(walletClient);
1639
- const deal = await this.getEscrowByIdParsed(escrowId);
1693
+ const deal = await this.getEscrowByIdParsed(escrowId, true);
1640
1694
  // Verify caller and state using helpers
1641
1695
  this.verifySeller(walletClient.account.address, deal);
1642
1696
  this.verifyState(deal, EscrowState.AWAITING_DELIVERY, "auto-release");
@@ -1664,6 +1718,7 @@ class PalindromePaySDK {
1664
1718
  args: [escrowId],
1665
1719
  });
1666
1720
  await this.waitForReceipt(hash);
1721
+ this.invalidateEscrow(escrowId);
1667
1722
  return hash;
1668
1723
  }
1669
1724
  // ==========================================================================
@@ -1699,6 +1754,7 @@ class PalindromePaySDK {
1699
1754
  args: [escrowId],
1700
1755
  });
1701
1756
  await this.waitForReceipt(hash);
1757
+ this.invalidateEscrow(escrowId);
1702
1758
  return hash;
1703
1759
  }
1704
1760
  /**
@@ -1731,6 +1787,7 @@ class PalindromePaySDK {
1731
1787
  args: [escrowId, signature, deadline, nonce],
1732
1788
  });
1733
1789
  await this.waitForReceipt(hash);
1790
+ this.invalidateEscrow(escrowId);
1734
1791
  return hash;
1735
1792
  }
1736
1793
  /**
@@ -1768,6 +1825,7 @@ class PalindromePaySDK {
1768
1825
  args: [escrowId, role, ipfsHash],
1769
1826
  });
1770
1827
  await this.waitForReceipt(hash);
1828
+ this.invalidateEscrow(escrowId);
1771
1829
  return hash;
1772
1830
  }
1773
1831
  /**
@@ -1797,7 +1855,7 @@ class PalindromePaySDK {
1797
1855
  */
1798
1856
  async submitArbiterDecision(walletClient, escrowId, resolution, ipfsHash) {
1799
1857
  assertWalletClient(walletClient);
1800
- const deal = await this.getEscrowByIdParsed(escrowId);
1858
+ const deal = await this.getEscrowByIdParsed(escrowId, true);
1801
1859
  // Verify caller using helper
1802
1860
  this.verifyArbiter(walletClient.account.address, deal);
1803
1861
  // Sign wallet authorization
@@ -1815,6 +1873,7 @@ class PalindromePaySDK {
1815
1873
  args: [escrowId, resolution, ipfsHash, arbiterWalletSig],
1816
1874
  });
1817
1875
  await this.waitForReceipt(hash);
1876
+ this.invalidateEscrow(escrowId);
1818
1877
  return hash;
1819
1878
  }
1820
1879
  // ==========================================================================
@@ -1842,7 +1901,7 @@ class PalindromePaySDK {
1842
1901
  */
1843
1902
  async withdraw(walletClient, escrowId) {
1844
1903
  assertWalletClient(walletClient);
1845
- const deal = await this.getEscrowByIdParsed(escrowId);
1904
+ const deal = await this.getEscrowByIdParsed(escrowId, true);
1846
1905
  // Verify final state
1847
1906
  if (![EscrowState.COMPLETE, EscrowState.REFUNDED, EscrowState.CANCELED].includes(deal.state)) {
1848
1907
  throw new SDKError(`Cannot withdraw in state: ${this.STATE_NAMES[deal.state]}`, SDKErrorCode.INVALID_STATE);
@@ -1855,6 +1914,7 @@ class PalindromePaySDK {
1855
1914
  args: [],
1856
1915
  });
1857
1916
  await this.waitForReceipt(hash);
1917
+ this.invalidateEscrow(escrowId);
1858
1918
  return hash;
1859
1919
  }
1860
1920
  /**
@@ -1862,7 +1922,7 @@ class PalindromePaySDK {
1862
1922
  */
1863
1923
  async getWalletSignatureCount(escrowId) {
1864
1924
  const deal = await this.getEscrowByIdParsed(escrowId);
1865
- const count = await this.publicClient.readContract({
1925
+ const count = await this.read({
1866
1926
  address: deal.wallet,
1867
1927
  abi: this.abiWallet,
1868
1928
  functionName: "getValidSignatureCount",
@@ -1874,7 +1934,7 @@ class PalindromePaySDK {
1874
1934
  */
1875
1935
  async getWalletBalance(escrowId) {
1876
1936
  const deal = await this.getEscrowByIdParsed(escrowId);
1877
- return this.publicClient.readContract({
1937
+ return this.read({
1878
1938
  address: deal.wallet,
1879
1939
  abi: this.abiWallet,
1880
1940
  functionName: "getBalance",
@@ -2837,7 +2897,7 @@ class PalindromePaySDK {
2837
2897
  if (!forceRefresh && this.feeReceiverCache) {
2838
2898
  return this.feeReceiverCache;
2839
2899
  }
2840
- this.feeReceiverCache = await this.publicClient.readContract({
2900
+ this.feeReceiverCache = await this.read({
2841
2901
  address: this.contractAddress,
2842
2902
  abi: this.abiEscrow,
2843
2903
  functionName: "FEE_RECEIVER",
package/dist/config.d.ts CHANGED
@@ -3,8 +3,8 @@ import { Address } from "viem";
3
3
  * Default configuration for Palindrome Pay SDK
4
4
  */
5
5
  export declare const CONFIG: {
6
- /**
7
- * Default PalindromePay contract address (Base mainnet)
8
- */
9
- readonly DEFAULT_CONTRACT_ADDRESS: Address;
6
+ /** PalindromePay contract address (Base mainnet) */
7
+ readonly CONTRACT_ADDRESS: Address;
8
+ /** PalindromePay contract address (Base Sepolia testnet) */
9
+ readonly TESTNET_CONTRACT_ADDRESS: Address;
10
10
  };
package/dist/config.js CHANGED
@@ -5,8 +5,8 @@ exports.CONFIG = void 0;
5
5
  * Default configuration for Palindrome Pay SDK
6
6
  */
7
7
  exports.CONFIG = {
8
- /**
9
- * Default PalindromePay contract address (Base mainnet)
10
- */
11
- DEFAULT_CONTRACT_ADDRESS: "0x47631c5Efe9AA709A020638B51E05b07e32FAF43",
8
+ /** PalindromePay contract address (Base mainnet) */
9
+ CONTRACT_ADDRESS: "0x47631c5Efe9AA709A020638B51E05b07e32FAF43",
10
+ /** PalindromePay contract address (Base Sepolia testnet) */
11
+ TESTNET_CONTRACT_ADDRESS: "0x2de68eec06080d7bc947c484aaff903e75bc08ea",
12
12
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@palindromepay/sdk",
3
- "version": "2.1.7",
3
+ "version": "2.1.9",
4
4
  "description": "TypeScript SDK for PalindromeCryptoEscrow - Secure blockchain escrow with buyer/seller protection",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",