@gvnrdao/dh-sdk 0.0.293 → 0.0.295

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.js CHANGED
@@ -3744,7 +3744,7 @@ var require_abstract_coder = __commonJS({
3744
3744
  function toObject2(names2, items, deep) {
3745
3745
  if (names2.indexOf(null) >= 0) {
3746
3746
  return items.map((item, index) => {
3747
- if (item instanceof Result10) {
3747
+ if (item instanceof Result11) {
3748
3748
  return toObject2(getNames2(item), item, deep);
3749
3749
  }
3750
3750
  return item;
@@ -3753,7 +3753,7 @@ var require_abstract_coder = __commonJS({
3753
3753
  return names2.reduce((accum, name, index) => {
3754
3754
  let item = items.getValue(name);
3755
3755
  if (!(name in accum)) {
3756
- if (deep && item instanceof Result10) {
3756
+ if (deep && item instanceof Result11) {
3757
3757
  item = toObject2(getNames2(item), item, deep);
3758
3758
  }
3759
3759
  accum[name] = item;
@@ -3761,7 +3761,7 @@ var require_abstract_coder = __commonJS({
3761
3761
  return accum;
3762
3762
  }, {});
3763
3763
  }
3764
- var Result10 = class _Result extends Array {
3764
+ var Result11 = class _Result extends Array {
3765
3765
  // No longer used; but cannot be removed as it will remove the
3766
3766
  // #private field from the .d.ts which may break backwards
3767
3767
  // compatibility
@@ -3965,7 +3965,7 @@ var require_abstract_coder = __commonJS({
3965
3965
  return new _Result(_guard5, items, keys);
3966
3966
  }
3967
3967
  };
3968
- exports2.Result = Result10;
3968
+ exports2.Result = Result11;
3969
3969
  function checkResultErrors2(result) {
3970
3970
  const errors2 = [];
3971
3971
  const checkErrors = function(path2, object2) {
@@ -32403,8 +32403,8 @@ ${errorReport}`);
32403
32403
  var UTXO_QUERY_MAX_RETRIES = 3;
32404
32404
  var UTXO_QUERY_RETRY_DELAY_MS = 500;
32405
32405
  var QUANTUM_WINDOW_SECONDS2 = 60;
32406
- var DEAD_ZONE_SECONDS = 16;
32407
- var SAFE_EXECUTION_WINDOW_SECONDS = QUANTUM_WINDOW_SECONDS2 - DEAD_ZONE_SECONDS;
32406
+ var DEAD_ZONE_SECONDS2 = 16;
32407
+ var SAFE_EXECUTION_WINDOW_SECONDS = QUANTUM_WINDOW_SECONDS2 - DEAD_ZONE_SECONDS2;
32408
32408
  var SECONDS_PER_DAY = 86400;
32409
32409
  var DAYS_PER_MONTH = 30;
32410
32410
  var LIT_ACTION_ETH_RPC_TIMEOUT_MS = 8e3;
@@ -34943,8 +34943,8 @@ function getMainnetConfig() {
34943
34943
  bitcoinWithdrawalAddressRegistry: MAINNET_CONTRACTS.BitcoinWithdrawalAddressRegistry || ""
34944
34944
  },
34945
34945
  subgraphs: {
34946
- diamondHandsUrl: "https://api.studio.thegraph.com/query/1755201/diamond-hands/v1.0.0-mainnet"
34947
- // Mainnet subgraph (keyless studio endpoint, browser-queryable)
34946
+ diamondHandsUrl: "https://gateway-arbitrum.network.thegraph.com/api/subgraphs/id/CCTPsdYqco2jChDLLBQTbdJWwoukVoMt1cXeR9ti6r9A"
34947
+ // Published Sepolia subgraph on Arbitrum One
34948
34948
  },
34949
34949
  litNetwork: "chipotle",
34950
34950
  debug: false
@@ -90299,7 +90299,7 @@ __export(src_exports, {
90299
90299
  ErrorSeverity: () => ErrorSeverity,
90300
90300
  EventHelpers: () => EventHelpers,
90301
90301
  LOCALHOST_CONTRACTS: () => LOCALHOST_CONTRACTS,
90302
- LRUCache: () => Cache,
90302
+ LRUCache: () => LRUCache,
90303
90303
  LoanCreator: () => LoanCreator,
90304
90304
  LoanQuery: () => LoanQuery,
90305
90305
  LoanStatus: () => LoanStatus,
@@ -108080,8 +108080,11 @@ async function resolveEip1559FeeFields(provider) {
108080
108080
  let maxPriorityFeePerGas = feeData.maxPriorityFeePerGas ?? void 0;
108081
108081
  if (!maxFeePerGas || !maxPriorityFeePerGas) {
108082
108082
  const gasPrice = (await provider.getFeeData()).gasPrice ?? parseUnits("1", "gwei");
108083
- maxFeePerGas = maxFeePerGas ?? gasPrice * 2n;
108084
- maxPriorityFeePerGas = maxPriorityFeePerGas ?? parseUnits("1.5", "gwei");
108083
+ maxFeePerGas = maxFeePerGas || gasPrice * 2n;
108084
+ maxPriorityFeePerGas = maxPriorityFeePerGas || parseUnits("1.5", "gwei");
108085
+ }
108086
+ if (maxFeePerGas < maxPriorityFeePerGas) {
108087
+ maxFeePerGas = maxPriorityFeePerGas;
108085
108088
  }
108086
108089
  return { maxFeePerGas, maxPriorityFeePerGas };
108087
108090
  }
@@ -108186,6 +108189,9 @@ function validateSDKConfig(config) {
108186
108189
 
108187
108190
  // src/utils/quantum-timing.ts
108188
108191
  var QUANTUM_WINDOW_SECONDS = 60;
108192
+ var DEAD_ZONE_SECONDS = 8;
108193
+ var INCLUSION_LATENCY_BUDGET = 16;
108194
+ var POST_BOUNDARY_SKEW_MARGIN = 3;
108189
108195
  function calculateNextQuantumTimestamp() {
108190
108196
  const now = Math.floor(Date.now() / 1e3);
108191
108197
  const currentQuantum = Math.floor(now / QUANTUM_WINDOW_SECONDS) * QUANTUM_WINDOW_SECONDS;
@@ -108214,6 +108220,27 @@ function validateQuantumTiming(signedTimestamp, _bufferSeconds = 30) {
108214
108220
  console.log(" Current quantum:", currentQuantum);
108215
108221
  console.log(" Valid window: PAST/CURRENT/NEXT (180s total)");
108216
108222
  }
108223
+ async function awaitSafeSubmissionWindow(quantumTimestamp, opts = {}) {
108224
+ const now = opts.now ?? (() => Math.floor(Date.now() / 1e3));
108225
+ const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
108226
+ const nowSec = now();
108227
+ const signatureQuantum = Math.floor(quantumTimestamp / QUANTUM_WINDOW_SECONDS) * QUANTUM_WINDOW_SECONDS;
108228
+ const currentQuantum = Math.floor(nowSec / QUANTUM_WINDOW_SECONDS) * QUANTUM_WINDOW_SECONDS;
108229
+ const secsToBoundary = QUANTUM_WINDOW_SECONDS - nowSec % QUANTUM_WINDOW_SECONDS;
108230
+ const isNextQuantumSig = signatureQuantum === currentQuantum + QUANTUM_WINDOW_SECONDS;
108231
+ if (isNextQuantumSig && secsToBoundary <= DEAD_ZONE_SECONDS + INCLUSION_LATENCY_BUDGET) {
108232
+ const target = currentQuantum + QUANTUM_WINDOW_SECONDS + POST_BOUNDARY_SKEW_MARGIN;
108233
+ const waitSeconds = target - nowSec;
108234
+ if (waitSeconds > 0) {
108235
+ console.log(
108236
+ `[Quantum Timing] \u23F3 Deferring send ${waitSeconds}s to clear the quantum dead zone (sig quantum ${signatureQuantum}, current ${currentQuantum}, ${secsToBoundary}s to boundary)`
108237
+ );
108238
+ await sleep(waitSeconds * 1e3);
108239
+ return { waited: true, waitedSeconds: waitSeconds };
108240
+ }
108241
+ }
108242
+ return { waited: false, waitedSeconds: 0 };
108243
+ }
108217
108244
 
108218
108245
  // src/utils/mint-authorization.utils.ts
108219
108246
  var PKP_NFT_ABI = [
@@ -109823,106 +109850,365 @@ function createContractManager(config) {
109823
109850
  }
109824
109851
 
109825
109852
  // src/modules/cache/cache-manager.module.ts
109826
- var Cache = class {
109827
- cache = /* @__PURE__ */ new Map();
109853
+ var LRUCache = class {
109854
+ cache;
109855
+ /**
109856
+ * Audit M-J: singleflight registry for `getOrCompute` / `getOrComputeResult`.
109857
+ * Concurrent cache-miss callers for the same key share one inflight promise
109858
+ * instead of each running `compute()` independently — important when the
109859
+ * compute spends a paid LIT capacity credit or hits a rate-limited upstream.
109860
+ */
109861
+ inflight = /* @__PURE__ */ new Map();
109862
+ inflightResult = /* @__PURE__ */ new Map();
109828
109863
  maxSize;
109829
109864
  ttlMs;
109830
- constructor(config) {
109831
- this.maxSize = config.maxSize;
109832
- this.ttlMs = config.ttlMs;
109865
+ debug;
109866
+ name;
109867
+ // Statistics
109868
+ stats = {
109869
+ hits: 0,
109870
+ misses: 0,
109871
+ evictions: 0
109872
+ };
109873
+ constructor(config = {}) {
109874
+ this.cache = /* @__PURE__ */ new Map();
109875
+ this.maxSize = config.maxSize || 1e3;
109876
+ this.ttlMs = config.ttlMs || 6e4;
109877
+ this.debug = config.debug || false;
109878
+ this.name = config.name || "Cache";
109879
+ if (this.debug) {
109880
+ console.log(
109881
+ `\u{1F4BE} [${this.name}] Initialized: maxSize=${this.maxSize}, ttl=${this.ttlMs}ms`
109882
+ );
109883
+ }
109833
109884
  }
109834
109885
  /**
109835
109886
  * Get value from cache
109887
+ *
109888
+ * Returns null if:
109889
+ * - Key not found
109890
+ * - Entry has expired
109891
+ *
109892
+ * @param key - Cache key
109893
+ * @returns Cached value or null
109836
109894
  */
109837
109895
  get(key) {
109838
109896
  const entry = this.cache.get(key);
109839
109897
  if (!entry) {
109840
- return void 0;
109898
+ this.stats.misses++;
109899
+ if (this.debug) {
109900
+ console.log(`\u274C [${this.name}] Cache MISS: ${String(key)}`);
109901
+ }
109902
+ return null;
109841
109903
  }
109842
- const now = Date.now();
109843
- if (now - entry.timestamp > entry.ttl) {
109904
+ if (this.isExpired(entry)) {
109844
109905
  this.cache.delete(key);
109845
- return void 0;
109906
+ this.stats.misses++;
109907
+ if (this.debug) {
109908
+ const age = Date.now() - entry.timestamp;
109909
+ console.log(`\u23F0 [${this.name}] Cache EXPIRED: ${String(key)} (age: ${age}ms)`);
109910
+ }
109911
+ return null;
109912
+ }
109913
+ entry.hits++;
109914
+ entry.lastAccessed = Date.now();
109915
+ this.cache.set(key, entry);
109916
+ this.stats.hits++;
109917
+ if (this.debug) {
109918
+ const age = Date.now() - entry.timestamp;
109919
+ console.log(
109920
+ `\u2705 [${this.name}] Cache HIT: ${String(key)} (age: ${age}ms, hits: ${entry.hits})`
109921
+ );
109846
109922
  }
109847
109923
  return entry.value;
109848
109924
  }
109925
+ /**
109926
+ * Get value from cache with Result wrapper
109927
+ *
109928
+ * Useful when you want to distinguish between "not found" and "expired"
109929
+ */
109930
+ getResult(key) {
109931
+ const value = this.get(key);
109932
+ if (value === null) {
109933
+ return failure(
109934
+ new SDKError({
109935
+ message: `Cache miss for key: ${String(key)}`,
109936
+ category: "CACHE" /* CACHE */,
109937
+ severity: "LOW" /* LOW */,
109938
+ originalError: new Error("Cache miss")
109939
+ })
109940
+ );
109941
+ }
109942
+ return success(value);
109943
+ }
109849
109944
  /**
109850
109945
  * Set value in cache
109946
+ *
109947
+ * If cache is full, evicts the least recently used entry
109948
+ *
109949
+ * @param key - Cache key
109950
+ * @param value - Value to cache
109951
+ * @param ttl - Optional custom TTL for this entry (ms)
109851
109952
  */
109852
109953
  set(key, value, ttl) {
109853
109954
  if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
109854
- const firstKey = this.cache.keys().next().value;
109855
- if (firstKey) {
109856
- this.cache.delete(firstKey);
109857
- }
109955
+ this.evictLRU();
109858
109956
  }
109859
- this.cache.set(key, {
109957
+ const entry = {
109860
109958
  value,
109861
109959
  timestamp: Date.now(),
109862
- ttl: ttl || this.ttlMs
109863
- });
109960
+ hits: 0,
109961
+ lastAccessed: Date.now()
109962
+ };
109963
+ this.cache.set(key, entry);
109964
+ if (this.debug) {
109965
+ const effectiveTtl = ttl || this.ttlMs;
109966
+ console.log(
109967
+ `\u{1F4BE} [${this.name}] Cache SET: ${String(key)} (ttl: ${effectiveTtl}ms, size: ${this.cache.size}/${this.maxSize})`
109968
+ );
109969
+ }
109864
109970
  }
109865
109971
  /**
109866
- * Check if key exists in cache
109972
+ * Set value in cache with Result wrapper
109973
+ */
109974
+ setResult(key, value, ttl) {
109975
+ try {
109976
+ this.set(key, value, ttl);
109977
+ return success(void 0);
109978
+ } catch (error2) {
109979
+ return failure(
109980
+ new SDKError({
109981
+ message: `Failed to set cache value for key: ${String(key)}`,
109982
+ category: "CACHE" /* CACHE */,
109983
+ severity: "MEDIUM" /* MEDIUM */,
109984
+ originalError: error2 instanceof Error ? error2 : new Error(String(error2))
109985
+ })
109986
+ );
109987
+ }
109988
+ }
109989
+ /**
109990
+ * Check if key exists in cache (without affecting stats)
109867
109991
  */
109868
109992
  has(key) {
109869
- return this.get(key) !== void 0;
109993
+ const entry = this.cache.get(key);
109994
+ return entry !== void 0 && !this.isExpired(entry);
109870
109995
  }
109871
109996
  /**
109872
- * Delete key from cache
109997
+ * Delete specific key from cache
109873
109998
  */
109874
109999
  delete(key) {
109875
- return this.cache.delete(key);
110000
+ const deleted = this.cache.delete(key);
110001
+ if (deleted && this.debug) {
110002
+ console.log(`\u{1F5D1}\uFE0F [${this.name}] Cache DELETE: ${String(key)}`);
110003
+ }
110004
+ return deleted;
109876
110005
  }
109877
110006
  /**
109878
- * Clear all cache entries
110007
+ * Clear entire cache
109879
110008
  */
109880
110009
  clear() {
110010
+ const previousSize = this.cache.size;
109881
110011
  this.cache.clear();
110012
+ this.stats = {
110013
+ hits: 0,
110014
+ misses: 0,
110015
+ evictions: 0
110016
+ };
110017
+ if (this.debug) {
110018
+ console.log(`\u{1F9F9} [${this.name}] Cache CLEARED: removed ${previousSize} entries`);
110019
+ }
109882
110020
  }
109883
110021
  /**
109884
- * Clean expired entries
110022
+ * Get current cache size
110023
+ */
110024
+ size() {
110025
+ return this.cache.size;
110026
+ }
110027
+ /**
110028
+ * Get cache statistics
110029
+ */
110030
+ getStats() {
110031
+ const entries = Array.from(this.cache.values());
110032
+ const timestamps = entries.map((e) => e.timestamp);
110033
+ const total = this.stats.hits + this.stats.misses;
110034
+ const hitRate = total === 0 ? 0 : this.stats.hits / total * 100;
110035
+ return {
110036
+ size: this.cache.size,
110037
+ hits: this.stats.hits,
110038
+ misses: this.stats.misses,
110039
+ evictions: this.stats.evictions,
110040
+ oldestEntry: timestamps.length > 0 ? Math.min(...timestamps) : 0,
110041
+ newestEntry: timestamps.length > 0 ? Math.max(...timestamps) : 0,
110042
+ hitRate
110043
+ };
110044
+ }
110045
+ /**
110046
+ * Get hit rate percentage
110047
+ */
110048
+ getHitRate() {
110049
+ const total = this.stats.hits + this.stats.misses;
110050
+ return total === 0 ? 0 : this.stats.hits / total * 100;
110051
+ }
110052
+ /**
110053
+ * Get all cached keys (for debugging)
110054
+ */
110055
+ getKeys() {
110056
+ return Array.from(this.cache.keys());
110057
+ }
110058
+ /**
110059
+ * Get all cached values (for debugging)
110060
+ */
110061
+ getValues() {
110062
+ return Array.from(this.cache.values()).map((entry) => entry.value);
110063
+ }
110064
+ /**
110065
+ * Get all cache entries with metadata (for debugging)
110066
+ */
110067
+ getEntries() {
110068
+ return Array.from(this.cache.entries()).map(([key, entry]) => ({
110069
+ key,
110070
+ value: entry.value,
110071
+ metadata: {
110072
+ timestamp: entry.timestamp,
110073
+ hits: entry.hits,
110074
+ lastAccessed: entry.lastAccessed
110075
+ }
110076
+ }));
110077
+ }
110078
+ /**
110079
+ * Clean up expired entries
110080
+ *
110081
+ * Useful for periodic maintenance
110082
+ *
110083
+ * @returns Number of entries cleaned
109885
110084
  */
109886
110085
  cleanExpired() {
109887
110086
  const now = Date.now();
109888
- let cleaned = 0;
110087
+ let cleanedCount = 0;
109889
110088
  for (const [key, entry] of this.cache.entries()) {
109890
- if (now - entry.timestamp > entry.ttl) {
110089
+ if (now - entry.timestamp > this.ttlMs) {
109891
110090
  this.cache.delete(key);
109892
- cleaned++;
110091
+ cleanedCount++;
109893
110092
  }
109894
110093
  }
109895
- return cleaned;
110094
+ if (cleanedCount > 0 && this.debug) {
110095
+ console.log(`\u{1F9F9} [${this.name}] Cleaned ${cleanedCount} expired entries`);
110096
+ }
110097
+ return cleanedCount;
109896
110098
  }
109897
110099
  /**
109898
- * Get cache statistics
110100
+ * Check if cache entry is expired
109899
110101
  */
109900
- getStats() {
109901
- return {
109902
- size: this.cache.size,
109903
- maxSize: this.maxSize,
109904
- ttlMs: this.ttlMs
109905
- };
110102
+ isExpired(entry) {
110103
+ return Date.now() - entry.timestamp > this.ttlMs;
110104
+ }
110105
+ /**
110106
+ * Evict least recently used entry
110107
+ */
110108
+ evictLRU() {
110109
+ let oldestKey = null;
110110
+ let oldestAccess = Infinity;
110111
+ for (const [key, entry] of this.cache.entries()) {
110112
+ if (entry.lastAccessed < oldestAccess) {
110113
+ oldestAccess = entry.lastAccessed;
110114
+ oldestKey = key;
110115
+ }
110116
+ }
110117
+ if (oldestKey !== null) {
110118
+ this.cache.delete(oldestKey);
110119
+ this.stats.evictions++;
110120
+ if (this.debug) {
110121
+ const timeSinceAccess = Date.now() - oldestAccess;
110122
+ console.log(
110123
+ `\u267B\uFE0F [${this.name}] Cache EVICT (LRU): ${String(oldestKey)} (last accessed: ${timeSinceAccess}ms ago)`
110124
+ );
110125
+ }
110126
+ }
110127
+ }
110128
+ /**
110129
+ * Get or compute value
110130
+ *
110131
+ * If key exists in cache, returns cached value.
110132
+ * Otherwise, computes value using provided function and caches it.
110133
+ *
110134
+ * @param key - Cache key
110135
+ * @param compute - Function to compute value if not in cache
110136
+ * @param ttl - Optional custom TTL for this entry
110137
+ * @returns Cached or computed value
110138
+ */
110139
+ async getOrCompute(key, compute, ttl) {
110140
+ const cached = this.get(key);
110141
+ if (cached !== null) {
110142
+ return cached;
110143
+ }
110144
+ const existing = this.inflight.get(key);
110145
+ if (existing) {
110146
+ return existing;
110147
+ }
110148
+ const computePromise = (async () => {
110149
+ try {
110150
+ const value = await compute();
110151
+ this.set(key, value, ttl);
110152
+ return value;
110153
+ } finally {
110154
+ this.inflight.delete(key);
110155
+ }
110156
+ })();
110157
+ this.inflight.set(key, computePromise);
110158
+ return computePromise;
110159
+ }
110160
+ /**
110161
+ * Get or compute value with Result wrapper
110162
+ */
110163
+ async getOrComputeResult(key, compute, ttl) {
110164
+ const cached = this.get(key);
110165
+ if (cached !== null) {
110166
+ return success(cached);
110167
+ }
110168
+ const existing = this.inflightResult.get(key);
110169
+ if (existing) {
110170
+ return existing;
110171
+ }
110172
+ const computePromise = (async () => {
110173
+ try {
110174
+ return await compute();
110175
+ } finally {
110176
+ this.inflightResult.delete(key);
110177
+ }
110178
+ })();
110179
+ this.inflightResult.set(key, computePromise);
110180
+ const result = await computePromise;
110181
+ if (result.success) {
110182
+ this.set(key, result.value, ttl);
110183
+ }
110184
+ return result;
109906
110185
  }
109907
110186
  };
109908
110187
  var CacheManager = class {
109909
110188
  caches = /* @__PURE__ */ new Map();
109910
- debug;
109911
- constructor(config = {}) {
109912
- this.debug = config.debug || false;
110189
+ globalConfig;
110190
+ constructor(globalConfig = {}) {
110191
+ this.globalConfig = globalConfig;
109913
110192
  }
109914
110193
  /**
109915
- * Get or create a cache instance
110194
+ * Create or get a named cache
110195
+ *
110196
+ * @param name - Unique cache name
110197
+ * @param config - Optional cache-specific configuration
110198
+ * @returns LRU cache instance
109916
110199
  */
109917
110200
  getCache(name, config) {
109918
- if (this.caches.has(name)) {
109919
- return this.caches.get(name);
110201
+ const existingCache = this.caches.get(name);
110202
+ if (existingCache) {
110203
+ return existingCache;
109920
110204
  }
109921
- const cache = new Cache(config);
110205
+ const mergedConfig = {
110206
+ ...this.globalConfig,
110207
+ ...config,
110208
+ name
110209
+ };
110210
+ const cache = new LRUCache(mergedConfig);
109922
110211
  this.caches.set(name, cache);
109923
- if (this.debug) {
109924
- console.log(`[CacheManager] Created cache: ${name}`, config);
109925
- }
109926
110212
  return cache;
109927
110213
  }
109928
110214
  /**
@@ -109937,11 +110223,11 @@ var CacheManager = class {
109937
110223
  * Clean expired entries from all caches
109938
110224
  */
109939
110225
  cleanAllExpired() {
109940
- let total = 0;
110226
+ let totalCleaned = 0;
109941
110227
  for (const cache of this.caches.values()) {
109942
- total += cache.cleanExpired();
110228
+ totalCleaned += cache.cleanExpired();
109943
110229
  }
109944
- return total;
110230
+ return totalCleaned;
109945
110231
  }
109946
110232
  /**
109947
110233
  * Get statistics for all caches
@@ -109954,11 +110240,16 @@ var CacheManager = class {
109954
110240
  return stats;
109955
110241
  }
109956
110242
  /**
109957
- * Destroy cache manager
110243
+ * Get list of all cache names
109958
110244
  */
109959
- destroy() {
109960
- this.clearAll();
109961
- this.caches.clear();
110245
+ getCacheNames() {
110246
+ return Array.from(this.caches.keys());
110247
+ }
110248
+ /**
110249
+ * Delete a named cache
110250
+ */
110251
+ deleteCache(name) {
110252
+ return this.caches.delete(name);
109962
110253
  }
109963
110254
  };
109964
110255
  function createCacheManager(config) {
@@ -118451,12 +118742,11 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
118451
118742
  {}
118452
118743
  );
118453
118744
  }
118454
- try {
118455
- return await this._requestMintUCDAttempt(request);
118456
- } catch (error2) {
118457
- const errorMsg = error2.message || String(error2);
118745
+ const result = await this._requestMintUCDAttempt(request);
118746
+ if (!result.success && fullRetry < MAX_FULL_RETRIES) {
118747
+ const errorMsg = result.error || "";
118458
118748
  const isStaleTimestamp = errorMsg.includes("quantumTimestamp too old") || errorMsg.includes("Timestamp staleness") || errorMsg.includes("MAX_SKEW") || errorMsg.includes("Quantum window expired") || errorMsg.includes("Too close to quantum boundary") || errorMsg.includes("QuantumOutsideWindow") || errorMsg.includes("Quantum window remaining too low");
118459
- if (isStaleTimestamp && fullRetry < MAX_FULL_RETRIES) {
118749
+ if (isStaleTimestamp) {
118460
118750
  if (this.config.debug) {
118461
118751
  log.warn(
118462
118752
  `\u26A0\uFE0F Timestamp became stale during mint process. Retrying from beginning...`,
@@ -118466,8 +118756,8 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
118466
118756
  await new Promise((resolve) => setTimeout(resolve, 2e3));
118467
118757
  continue;
118468
118758
  }
118469
- throw error2;
118470
118759
  }
118760
+ return result;
118471
118761
  }
118472
118762
  return {
118473
118763
  success: false,
@@ -119089,6 +119379,7 @@ Context: Quantum timestamp=${validationResponse.timestamp}, Position=${request.p
119089
119379
  "0x131d9a21": "QuantumExpired()",
119090
119380
  "0x52ce5d58": "QuantumAlreadyUsed()",
119091
119381
  "0x137f3b70": "InDeadZone()",
119382
+ "0xbe4b82c1": "DeadZoneViolation()",
119092
119383
  "0x3e76a3c9": "QuantumOutsideWindow()",
119093
119384
  "0x62278171": "InvalidValidatorSignature()",
119094
119385
  "0xb9d419a7": "DebtUpdateVerificationFailed()",
@@ -119109,7 +119400,7 @@ Mint/debt diagnostics (LoanOperationsManager mint path \u2192 increaseDebtFromMi
119109
119400
  Contract expects UCD supply increase to match mintAmount+mintFee and debt update to newDebt.
119110
119401
  ` : "";
119111
119402
  let quantumContext = "";
119112
- if (selector === "0x131d9a21" || selector === "0x52ce5d58" || selector === "0x137f3b70" || selector === "0x3e76a3c9") {
119403
+ if (selector === "0x131d9a21" || selector === "0x52ce5d58" || selector === "0x137f3b70" || selector === "0xbe4b82c1" || selector === "0x3e76a3c9") {
119113
119404
  const currentTime2 = Math.floor(Date.now() / 1e3);
119114
119405
  const currentQuantum = Math.floor(currentTime2 / 60) * 60;
119115
119406
  const sigQuantum2 = Math.floor(validationResponse.timestamp / 60) * 60;
@@ -119371,6 +119662,7 @@ Position: ${request.positionId}`
119371
119662
  validationResponse.timestamp,
119372
119663
  signatureHexMint
119373
119664
  ]);
119665
+ await awaitSafeSubmissionWindow(Number(validationResponse.timestamp));
119374
119666
  const fromAddress = await signer.getAddress();
119375
119667
  const estimatedGas = await estimateContractCallGasWithMargin(
119376
119668
  signerProvider,
@@ -119429,6 +119721,7 @@ Position: ${request.positionId}`
119429
119721
  "0x131d9a21": "QuantumExpired()",
119430
119722
  "0x52ce5d58": "QuantumAlreadyUsed()",
119431
119723
  "0x137f3b70": "InDeadZone()",
119724
+ "0xbe4b82c1": "DeadZoneViolation()",
119432
119725
  "0x3e76a3c9": "QuantumOutsideWindow()",
119433
119726
  "0x62278171": "InvalidValidatorSignature()",
119434
119727
  "0xb9d419a7": "DebtUpdateVerificationFailed()",
@@ -119608,6 +119901,7 @@ Error data: ${errorData || "none"}`
119608
119901
  "0x131d9a21": "QuantumExpired() - Signature quantum window has closed",
119609
119902
  "0x52ce5d58": "QuantumAlreadyUsed() - This quantum was already used for this position",
119610
119903
  "0x137f3b70": "InDeadZone() - Timestamp in dead zone (near quantum boundary)",
119904
+ "0xbe4b82c1": "DeadZoneViolation() - Non-current-quantum signature mined in the last 8s of the current quantum",
119611
119905
  "0x3e76a3c9": "QuantumOutsideWindow() - Timestamp not in past/current/next quantum window"
119612
119906
  };
119613
119907
  const errorName = knownErrors[selector] || `Unknown error ${selector}`;
@@ -120676,6 +120970,7 @@ Error data: ${errorData || "none"}`
120676
120970
  );
120677
120971
  }
120678
120972
  const positionManager = positionManagerResult.value;
120973
+ await awaitSafeSubmissionWindow(Number(quantumTimestamp));
120679
120974
  const tx = await positionManager["extendPosition"](
120680
120975
  positionIdBytes32,
120681
120976
  BigInt(_selectedTerm),
@@ -121108,6 +121403,57 @@ Error data: ${errorData || "none"}`
121108
121403
  if (!pauseCheck.ok) {
121109
121404
  return { success: false, error: pauseCheck.error };
121110
121405
  }
121406
+ const MAX_FULL_RETRIES = 3;
121407
+ for (let fullRetry = 1; fullRetry <= MAX_FULL_RETRIES; fullRetry++) {
121408
+ if (fullRetry > 1 && this.config.debug) {
121409
+ log.info(
121410
+ `\u{1F504} Full payment retry ${fullRetry}/${MAX_FULL_RETRIES} due to quantum-timing failure...`,
121411
+ {}
121412
+ );
121413
+ }
121414
+ const result = await this._makePaymentAttempt(request);
121415
+ if (fullRetry < MAX_FULL_RETRIES && this.isRetryablePaymentQuantumFailure(result)) {
121416
+ await new Promise((resolve) => setTimeout(resolve, 2e3));
121417
+ continue;
121418
+ }
121419
+ return result;
121420
+ }
121421
+ return {
121422
+ success: false,
121423
+ error: "Max retries exceeded for payment operation"
121424
+ };
121425
+ } finally {
121426
+ this.invalidateCachesForPosition(request.positionId);
121427
+ this.releaseWriteLock(request.positionId);
121428
+ }
121429
+ }
121430
+ /**
121431
+ * Whether a failed payment attempt is a quantum-timing failure that a fresh
121432
+ * re-sign can fix (safe to retry), versus a terminal failure.
121433
+ *
121434
+ * Retryable: pre-send simulation quantum errors, or an atomic mined revert
121435
+ * (status 0) — no funds moved and the quantum was not recorded on-chain, so a
121436
+ * fresh signature + resubmit is safe.
121437
+ *
121438
+ * NOT retryable: a confirmation timeout — the original tx may still be pending, so
121439
+ * a resubmit could double-pay. It is surfaced (with its tx hash) instead.
121440
+ */
121441
+ isRetryablePaymentQuantumFailure(result) {
121442
+ if (result.success || !result.error)
121443
+ return false;
121444
+ const e = result.error;
121445
+ if (e.includes("Transaction timeout"))
121446
+ return false;
121447
+ return e.includes("DeadZoneViolation") || e.includes("QuantumOutsideWindow") || e.includes("QuantumAlreadyUsed") || e.includes("QuantumExpired") || e === "Transaction reverted";
121448
+ }
121449
+ /**
121450
+ * One payment attempt: user auth → Lit Action authorization → dead-zone gate →
121451
+ * pre-send simulation → broadcast → confirmation. Always resolves to a
121452
+ * PartialPaymentResult (never throws to the caller); the makePayment wrapper owns
121453
+ * the write lock, the pause pre-check, and the bounded re-sign retry loop.
121454
+ */
121455
+ async _makePaymentAttempt(request) {
121456
+ try {
121111
121457
  if (this.config.debug) {
121112
121458
  log.info(`\u{1F4B3} Making payment...`, {});
121113
121459
  log.info(` Request object:`, { request });
@@ -121368,7 +121714,6 @@ Error data: ${errorData || "none"}`
121368
121714
  error: `Failed to get PositionManager: ${positionManagerResult.error.message}`
121369
121715
  };
121370
121716
  }
121371
- const positionManager = positionManagerResult.value;
121372
121717
  if (this.config.debug) {
121373
121718
  log.info(
121374
121719
  ` About to call toBytes32 with: ${request.positionId} (type: ${typeof request.positionId})`
@@ -121428,9 +121773,6 @@ Error data: ${errorData || "none"}`
121428
121773
  currentQuantum
121429
121774
  });
121430
121775
  }
121431
- if (this.config.debug) {
121432
- log.info("\u23ED\uFE0F Skipping dead zone check to isolate BigNumber issue", {});
121433
- }
121434
121776
  if (this.config.debug) {
121435
121777
  log.info("\u{1F50D} Final timestamp validation:", {
121436
121778
  litActionTimestamp: litActionResult.timestamp,
@@ -121497,108 +121839,100 @@ Error data: ${errorData || "none"}`
121497
121839
  quantumTimestamp: quantumTimestamp.toString()
121498
121840
  });
121499
121841
  }
121500
- let tx;
121501
- try {
121502
- if (this.config.debug) {
121503
- log.info("\u{1F50D} Attempting contract interface call to makePayment");
121504
- }
121505
- if (this.config.debug) {
121506
- log.info("\u{1F50D} Contract call parameters:", {
121507
- positionIdBytes32,
121508
- paymentAmountWei: paymentAmountWei.toString(),
121509
- quantumTimestamp: quantumTimestamp.toString(),
121510
- btcPrice: btcPrice.toString(),
121511
- signatureLength: signature.length
121512
- });
121513
- }
121514
- if (typeof quantumTimestamp !== "bigint") {
121515
- throw new Error(
121516
- `quantumTimestamp is not a BigInt: ${typeof quantumTimestamp}, value: ${quantumTimestamp}`
121517
- );
121518
- }
121519
- if (typeof btcPrice !== "bigint") {
121520
- throw new Error(
121521
- `btcPrice is not a BigInt: ${typeof btcPrice}, value: ${btcPrice}`
121522
- );
121523
- }
121524
- if (typeof paymentAmountWei !== "bigint") {
121525
- throw new Error(
121526
- `paymentAmountWei is not a BigNumber: ${typeof paymentAmountWei}, value: ${paymentAmountWei}`
121527
- );
121528
- }
121529
- const paymentAmountStr = paymentAmountWei.toString();
121530
- const quantumTimestampStr = quantumTimestamp.toString();
121531
- const btcPriceStr = btcPrice.toString();
121532
- if (this.config.debug) {
121533
- log.info("\u{1F50D} About to call contract makePayment with:", {
121534
- positionIdBytes32: positionIdBytes32.substring(0, 20) + "...",
121535
- paymentAmountWei: paymentAmountStr,
121536
- quantumTimestamp: quantumTimestampStr,
121537
- btcPrice: btcPriceStr,
121538
- signature: signature.substring(0, 20) + "..."
121539
- });
121540
- }
121541
- if (typeof paymentAmountStr !== "string" || paymentAmountStr === "[object Object]") {
121542
- throw new Error(`Invalid paymentAmount: ${paymentAmountStr}`);
121543
- }
121544
- if (typeof quantumTimestampStr !== "string" || quantumTimestampStr === "[object Object]") {
121545
- throw new Error(`Invalid quantumTimestamp: ${quantumTimestampStr}`);
121546
- }
121547
- if (typeof btcPriceStr !== "string" || btcPriceStr === "[object Object]") {
121548
- throw new Error(`Invalid btcPrice: ${btcPriceStr}`);
121549
- }
121550
- if (this.config.debug) {
121551
- log.info("\u{1F50D} Final parameter validation before contract call:", {
121552
- positionIdBytes32: typeof positionIdBytes32,
121553
- paymentAmountStr: typeof paymentAmountStr + " = " + paymentAmountStr.substring(0, 20),
121554
- quantumTimestampStr: typeof quantumTimestampStr + " = " + quantumTimestampStr.substring(0, 20),
121555
- btcPriceStr: typeof btcPriceStr + " = " + btcPriceStr.substring(0, 20),
121556
- signature: typeof signature + " = " + signature.substring(0, 20)
121842
+ await awaitSafeSubmissionWindow(Number(quantumTimestamp));
121843
+ const paymentAmountStr = paymentAmountWei.toString();
121844
+ const quantumTimestampStr = quantumTimestamp.toString();
121845
+ const btcPriceStr = btcPrice.toString();
121846
+ const paymentContractAddress = this.getContractAddressesOrThrow().positionManager;
121847
+ const paymentSigner = this.getSignerOrThrow();
121848
+ const paymentFrom = await paymentSigner.getAddress();
121849
+ const paymentProvider = contractManager.getProvider();
121850
+ const paymentIface = new Interface([
121851
+ "function makePayment(bytes32 positionId, uint256 paymentAmount, uint256 quantumTimestamp, uint256 btcPrice, bytes calldata paymentValidatorSignature) external returns (bool)"
121852
+ ]);
121853
+ const paymentCalldata = paymentIface.encodeFunctionData("makePayment", [
121854
+ positionIdBytes32,
121855
+ paymentAmountStr,
121856
+ quantumTimestampStr,
121857
+ btcPriceStr,
121858
+ signature
121859
+ ]);
121860
+ const decodePaymentRevert = (e) => {
121861
+ const raw = e?.data ?? e?.info?.error?.data ?? e?.error?.data?.data ?? e?.error?.data;
121862
+ const data = typeof raw === "string" ? raw : void 0;
121863
+ if (!data || !data.startsWith("0x") || data.length < 10)
121864
+ return null;
121865
+ const selector = data.slice(0, 10);
121866
+ const map3 = {
121867
+ "0xbe4b82c1": "DeadZoneViolation()",
121868
+ "0x137f3b70": "InDeadZone()",
121869
+ "0x52ce5d58": "QuantumAlreadyUsed()",
121870
+ "0x3e76a3c9": "QuantumOutsideWindow()",
121871
+ "0x131d9a21": "QuantumExpired()",
121872
+ "0x8baa579f": "InvalidSignature()",
121873
+ "0x62278171": "InvalidValidatorSignature()",
121874
+ "0x3ee5aeb5": "OperationNotAuthorized()",
121875
+ "0x48f5c3ed": "Unauthorized()"
121876
+ };
121877
+ return map3[selector] ?? `Unknown error ${selector}`;
121878
+ };
121879
+ const MAX_DEADZONE_RESIMULATIONS = 3;
121880
+ for (let sim = 1; ; sim++) {
121881
+ try {
121882
+ await paymentProvider.call({
121883
+ to: paymentContractAddress,
121884
+ from: paymentFrom,
121885
+ data: paymentCalldata
121557
121886
  });
121558
- }
121559
- tx = await positionManager["makePayment"](
121560
- positionIdBytes32,
121561
- paymentAmountStr,
121562
- quantumTimestampStr,
121563
- btcPriceStr,
121564
- signature
121565
- );
121566
- if (this.config.debug) {
121567
- log.info("\u2705 Contract call succeeded", { txHash: tx.hash });
121568
- }
121569
- } catch (contractError) {
121570
- if (this.config.debug) {
121571
- log.warn(
121572
- "\u26A0\uFE0F Contract interface failed, falling back to raw transaction",
121573
- {
121574
- error: contractError instanceof Error ? contractError.message : String(contractError)
121887
+ break;
121888
+ } catch (simError) {
121889
+ const decoded = decodePaymentRevert(simError);
121890
+ if (decoded === "DeadZoneViolation()" && sim <= MAX_DEADZONE_RESIMULATIONS) {
121891
+ const nowSec = Math.floor(Date.now() / 1e3);
121892
+ const sigQuantum = Math.floor(Number(quantumTimestamp) / 60) * 60;
121893
+ const realCurrentQuantum = Math.floor(nowSec / 60) * 60;
121894
+ if (sigQuantum === realCurrentQuantum) {
121895
+ if (this.config.debug) {
121896
+ log.info(
121897
+ "\u21AA\uFE0F Simulated DeadZoneViolation is a stale-latest-block artifact (signature is the current quantum) \u2014 proceeding to broadcast",
121898
+ {}
121899
+ );
121900
+ }
121901
+ break;
121575
121902
  }
121576
- );
121577
- }
121578
- const contractAddress = this.getContractAddressesOrThrow().positionManager;
121579
- const iface = new Interface([
121580
- "function makePayment(bytes32 positionId, uint256 paymentAmount, uint256 quantumTimestamp, uint256 btcPrice, bytes calldata paymentValidatorSignature) external returns (bool)"
121581
- ]);
121582
- const calldata = iface.encodeFunctionData("makePayment", [
121583
- positionIdBytes32,
121584
- paymentAmountWei.toString(),
121585
- quantumTimestamp.toString(),
121586
- btcPrice.toString(),
121587
- signature
121588
- ]);
121589
- const signer2 = this.getSignerOrThrow();
121590
- tx = await signer2.sendTransaction({
121591
- to: contractAddress,
121592
- data: calldata,
121593
- value: "0x0",
121594
- gasLimit: 5e5
121595
- });
121596
- if (this.config.debug) {
121597
- log.info("\u2705 Raw transaction fallback succeeded", {
121598
- txHash: tx.hash
121599
- });
121903
+ const gate = await awaitSafeSubmissionWindow(Number(quantumTimestamp));
121904
+ if (gate.waited) {
121905
+ if (this.config.debug) {
121906
+ log.info(
121907
+ `\u{1F501} Re-simulating makePayment after crossing the quantum boundary (attempt ${sim}/${MAX_DEADZONE_RESIMULATIONS})`,
121908
+ {}
121909
+ );
121910
+ }
121911
+ continue;
121912
+ }
121913
+ }
121914
+ const label = decoded ?? simError?.reason ?? simError?.shortMessage ?? (simError instanceof Error ? simError.message : String(simError));
121915
+ if (this.config.debug) {
121916
+ log.error("\u274C makePayment pre-send simulation reverted", { error: label });
121917
+ }
121918
+ return {
121919
+ success: false,
121920
+ error: `Payment would revert (pre-send simulation): ${label}`,
121921
+ positionId: request.positionId,
121922
+ paymentAmountUCD: request.paymentAmount
121923
+ };
121600
121924
  }
121601
121925
  }
121926
+ const MAKE_PAYMENT_GAS_CEILING = 500000n;
121927
+ const tx = await sendEip1559Transaction({
121928
+ signer: paymentSigner,
121929
+ to: paymentContractAddress,
121930
+ data: paymentCalldata,
121931
+ gasLimit: MAKE_PAYMENT_GAS_CEILING
121932
+ });
121933
+ if (this.config.debug) {
121934
+ log.info("\u2705 makePayment broadcast", { txHash: tx.hash });
121935
+ }
121602
121936
  if (this.config.debug) {
121603
121937
  log.info(`\u{1F4E4} Transaction sent: ${tx.hash}`);
121604
121938
  log.info("\u23F3 Waiting for transaction confirmation...");
@@ -121742,9 +122076,6 @@ Error data: ${errorData || "none"}`
121742
122076
  result.effectiveGasPrice = receipt.effectiveGasPrice?.toString() || receipt.gasPrice?.toString();
121743
122077
  }
121744
122078
  return result;
121745
- } finally {
121746
- this.invalidateCachesForPosition(request.positionId);
121747
- this.releaseWriteLock(request.positionId);
121748
122079
  }
121749
122080
  }
121750
122081
  /**
@@ -122306,6 +122637,7 @@ Error data: ${errorData || "none"}`
122306
122637
  utxoVout: withdrawalParams.utxoVout
122307
122638
  });
122308
122639
  }
122640
+ await awaitSafeSubmissionWindow(Number(withdrawalParams.quantumTimestamp));
122309
122641
  let tx;
122310
122642
  try {
122311
122643
  tx = await positionManagerContract["withdrawBTC"](